Merge branch 'beta' into no-non-type-export

This commit is contained in:
NightKev 2025-06-15 01:24:56 -07:00 committed by GitHub
commit d3416df4b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
231 changed files with 1394 additions and 1627 deletions

View File

@ -16,7 +16,7 @@ module.exports = {
{ {
name: "only-type-imports", name: "only-type-imports",
severity: "error", severity: "error",
comment: "Files in enums and @types may only use type imports.", comment: "Files in 'enums/' and '@types/' must only use type imports.",
from: { from: {
path: ["(^|/)src/@types", "(^|/)src/enums"], path: ["(^|/)src/@types", "(^|/)src/enums"],
}, },
@ -26,7 +26,7 @@ module.exports = {
}, },
{ {
name: "no-circular-at-runtime", name: "no-circular-at-runtime",
severity: "warn", severity: "error",
comment: comment:
"This dependency is part of a circular relationship. You might want to revise " + "This dependency is part of a circular relationship. You might want to revise " +
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
@ -46,7 +46,7 @@ module.exports = {
"add an exception for it in your dependency-cruiser configuration. By default " + "add an exception for it in your dependency-cruiser configuration. By default " +
"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " +
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.", "files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
severity: "warn", severity: "error",
from: { from: {
orphan: true, orphan: true,
pathNot: [ pathNot: [
@ -54,8 +54,7 @@ module.exports = {
"[.]d[.]ts$", // TypeScript declaration files "[.]d[.]ts$", // TypeScript declaration files
"(^|/)tsconfig[.]json$", // TypeScript config "(^|/)tsconfig[.]json$", // TypeScript config
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs "(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
// anything in src/@types "(^|/)test/.+[.]setup[.]ts", // Vitest setup files
"(^|/)src/@types/",
], ],
}, },
to: {}, to: {},
@ -65,7 +64,7 @@ module.exports = {
comment: comment:
"A module depends on a node core module that has been deprecated. Find an alternative - these are " + "A module depends on a node core module that has been deprecated. Find an alternative - these are " +
"bound to exist - node doesn't deprecate lightly.", "bound to exist - node doesn't deprecate lightly.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
dependencyTypes: ["core"], dependencyTypes: ["core"],
@ -98,7 +97,7 @@ module.exports = {
comment: comment:
"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " + "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " +
"version of that module, or find an alternative. Deprecated modules are a security risk.", "version of that module, or find an alternative. Deprecated modules are a security risk.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
dependencyTypes: ["deprecated"], dependencyTypes: ["deprecated"],
@ -134,7 +133,7 @@ module.exports = {
"Likely this module depends on an external ('npm') package that occurs more than once " + "Likely this module depends on an external ('npm') package that occurs more than once " +
"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " +
"maintenance problems later on.", "maintenance problems later on.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
moreThanOneDependencyType: true, moreThanOneDependencyType: true,
@ -145,7 +144,7 @@ module.exports = {
}, },
}, },
/* rules you might want to tweak for your specific situation: */ // rules you might want to tweak for your specific situation:
{ {
name: "not-to-spec", name: "not-to-spec",
@ -200,7 +199,7 @@ module.exports = {
"in your package.json. This makes sense if your package is e.g. a plugin, but in " + "in your package.json. This makes sense if your package is e.g. a plugin, but in " +
"other cases - maybe not so much. If the use of a peer dependency is intentional " + "other cases - maybe not so much. If the use of a peer dependency is intentional " +
"add an exception to your dependency-cruiser configuration.", "add an exception to your dependency-cruiser configuration.",
severity: "warn", severity: "error",
from: {}, from: {},
to: { to: {
dependencyTypes: ["npm-peer"], dependencyTypes: ["npm-peer"],
@ -208,6 +207,7 @@ module.exports = {
}, },
], ],
options: { options: {
exclude: ["src/plugins/vite/*", "src/vite.env.d.ts"],
/* Which modules not to follow further when encountered */ /* Which modules not to follow further when encountered */
doNotFollow: { doNotFollow: {
/* path: an array of regular expressions in strings to match against */ /* path: an array of regular expressions in strings to match against */
@ -247,7 +247,7 @@ module.exports = {
true: also detect dependencies that only exist before typescript-to-javascript compilation true: also detect dependencies that only exist before typescript-to-javascript compilation
"specify": for each dependency identify whether it only exists before compilation or also after "specify": for each dependency identify whether it only exists before compilation or also after
*/ */
// tsPreCompilationDeps: false, tsPreCompilationDeps: true,
/* list of extensions to scan that aren't javascript or compile-to-javascript. /* list of extensions to scan that aren't javascript or compile-to-javascript.
Empty by default. Only put extensions in here that you want to take into Empty by default. Only put extensions in here that you want to take into

2
.github/CODEOWNERS vendored
View File

@ -8,7 +8,7 @@
# Art Team # Art Team
/public/**/*.png @pagefaultgames/art-team /public/**/*.png @pagefaultgames/art-team
/public/**/*.json @pagefaultgames/art-team /public/**/*.json @pagefaultgames/art-team
/public/images @pagefaultgames/art-team /public/images @pagefaultgames/art-team
/public/battle-anims @pagefaultgames/art-team /public/battle-anims @pagefaultgames/art-team

View File

@ -2,25 +2,28 @@
<!-- Feel free to look at other PRs for examples --> <!-- Feel free to look at other PRs for examples -->
<!-- <!--
Make sure the title includes categorization (choose the one that best fits): Make sure the title includes categorization (choose the one that best fits):
- [Bug]: If the PR is primarily a bug fix - [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality - [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality - [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items - [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters - [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests - [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements - [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx - [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites - [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance - [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges - [Challenge]: If the PR is adding or modifying challenges
- [Refactor]: If the PR is primarily rewriting existing code - [Refactor]: If the PR is primarily rewriting existing code
- [Docs]: If the PR is just adding or modifying documentation (such as tsdocs/code comments) - [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
- [GitHub]: For changes to GitHub workflows/templates/etc - [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales PR)
- [Misc]: If no other category fits the PR - [Docs]: If the PR is adding or modifying documentation (such as tsdocs/code comments)
- [GitHub]: For changes to GitHub workflows/templates/etc
- [Misc]: If no other category fits the PR
--> -->
<!-- <!--
Make sure that this PR is not overlapping with someone else's work Make sure that this PR is not overlapping with someone else's work
Please try to keep the PR self-contained (and small) Please try to keep the PR self-contained (and small!)
--> -->
## What are the changes the user will see? ## What are the changes the user will see?
@ -66,11 +69,11 @@ Do the reviewers need to do something special in order to test your changes?
- [ ] Have I provided a clear explanation of the changes? - [ ] Have I provided a clear explanation of the changes?
- [ ] Have I tested the changes manually? - [ ] Have I tested the changes manually?
- [ ] Are all unit tests still passing? (`npm run test:silent`) - [ ] Are all unit tests still passing? (`npm run test:silent`)
- [ ] Have I created new automated tests (`npm run create-test`) or updated existing tests related to the PR's changes? - [ ] Have I created new automated tests (`npm run test:create`) or updated existing tests related to the PR's changes?
- [ ] Have I provided screenshots/videos of the changes (if applicable)? - [ ] Have I provided screenshots/videos of the changes (if applicable)?
- [ ] Have I made sure that any UI change works for both UI themes (default and legacy)? - [ ] Have I made sure that any UI change works for both UI themes (default and legacy)?
Are there any localization additions or changes? If so: Are there any localization additions or changes? If so:
- [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo? - [ ] Has a locales PR been created on the [locales](https://github.com/pagefaultgames/pokerogue-locales) repo?
- [ ] If so, please leave a link to it here: - [ ] If so, please leave a link to it here:
- [ ] Has the translation team been contacted for proofreading/translation? - [ ] Has the translation team been contacted for proofreading/translation?

View File

@ -35,7 +35,7 @@ jobs:
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Deploy build on server - name: Deploy build on server
if: github.event_name == 'push' && github.ref_name == 'main' if: github.event_name == 'push' && github.ref_name == 'main'
run: | run: |
rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }} rsync --del --no-times --checksum -vrm dist/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DESTINATION_DIR }}
ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json" ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json"
- name: Purge Cloudflare Cache - name: Purge Cloudflare Cache

42
.github/workflows/linting.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: Linting
on:
push:
branches:
- main
- beta
pull_request:
branches:
- main
- beta
merge_group:
types: [checks_requested]
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install Node.js dependencies
run: npm ci
- name: Run ESLint
run: npm run eslint-ci
- name: Lint with Biome
run: npm run biome-ci
- name: Check dependencies with depcruise
run: npm run depcruise

View File

@ -1,41 +0,0 @@
name: Biome Code Quality
on:
# Trigger the workflow on push or pull request,
# but only for the main branch
push:
branches:
- main # Trigger on push events to the main branch
- beta # Trigger on push events to the beta branch
pull_request:
branches:
- main # Trigger on pull request events targeting the main branch
- beta # Trigger on pull request events targeting the beta branch
merge_group:
types: [checks_requested]
jobs:
run-linters: # Define a job named "run-linters"
name: Run linters # Human-readable name for the job
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
steps:
- name: Check out Git repository # Step to check out the repository
uses: actions/checkout@v4 # Use the checkout action version 4
with:
submodules: 'recursive'
- name: Set up Node.js # Step to set up Node.js environment
uses: actions/setup-node@v4 # Use the setup-node action version 4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies
- name: eslint # Step to run linters
run: npm run eslint-ci
- name: Lint with Biome # Step to run linters
run: npm run biome-ci

View File

@ -47,8 +47,8 @@
"correctness": { "correctness": {
"noUndeclaredVariables": "off", "noUndeclaredVariables": "off",
"noUnusedVariables": "error", "noUnusedVariables": "error",
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error "noSwitchDeclarations": "error",
"noVoidTypeReturn": "warn", // TODO: Refactor and make this an error "noVoidTypeReturn": "error",
"noUnusedImports": "error" "noUnusedImports": "error"
}, },
"style": { "style": {
@ -85,7 +85,7 @@
"useLiteralKeys": "off", "useLiteralKeys": "off",
"noForEach": "off", // Foreach vs for of is not that simple. "noForEach": "off", // Foreach vs for of is not that simple.
"noUselessSwitchCase": "off", // Explicit > Implicit "noUselessSwitchCase": "off", // Explicit > Implicit
"noUselessConstructor": "warn", // TODO: Refactor and make this an error "noUselessConstructor": "error",
"noBannedTypes": "warn" // TODO: Refactor and make this an error "noBannedTypes": "warn" // TODO: Refactor and make this an error
}, },
"nursery": { "nursery": {

2
global.d.ts vendored
View File

@ -7,7 +7,7 @@ declare global {
* Only used in testing. * Only used in testing.
* Can technically be undefined/null but for ease of use we are going to assume it is always defined. * Can technically be undefined/null but for ease of use we are going to assume it is always defined.
* Used to load i18n files exclusively. * Used to load i18n files exclusively.
* *
* To set up your own server in a test see `game_data.test.ts` * To set up your own server in a test see `game_data.test.ts`
*/ */
var server: SetupServerApi; var server: SetupServerApi;

View File

@ -18,9 +18,9 @@
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "eslint-ci": "eslint .",
"biome": "biome check --write --changed --no-errors-on-unmatched", "biome": "biome check --write --changed --no-errors-on-unmatched",
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --changed --no-errors-on-unmatched", "biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
"docs": "typedoc", "docs": "typedoc",
"depcruise": "depcruise src", "depcruise": "depcruise src test",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"postinstall": "npx lefthook install && npx lefthook run post-merge", "postinstall": "npx lefthook install && npx lefthook run post-merge",
"update-version:patch": "npm version patch --force --no-git-tag-version", "update-version:patch": "npm version patch --force --no-git-tag-version",

View File

@ -29,4 +29,4 @@ export type ModifierString = keyof ModifierConstructorMap;
export type ModifierPool = { export type ModifierPool = {
[tier: string]: WeightedModifierType[]; [tier: string]: WeightedModifierType[];
} };

View File

@ -1635,6 +1635,9 @@ export default class BattleScene extends SceneBase {
case SpeciesId.TATSUGIRI: case SpeciesId.TATSUGIRI:
case SpeciesId.PALDEA_TAUROS: case SpeciesId.PALDEA_TAUROS:
return randSeedInt(species.forms.length); return randSeedInt(species.forms.length);
case SpeciesId.MAUSHOLD:
case SpeciesId.DUDUNSPARCE:
return !randSeedInt(4) ? 1 : 0;
case SpeciesId.PIKACHU: case SpeciesId.PIKACHU:
if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) {
return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30 return 0; // Ban Cosplay and Partner Pika from Trainers before wave 30

View File

@ -9,6 +9,7 @@ import {
randSeedInt, randSeedInt,
type Constructor, type Constructor,
randSeedFloat, randSeedFloat,
coerceArray,
} from "#app/utils/common"; } from "#app/utils/common";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { GroundedTag } from "#app/data/battler-tags"; import { GroundedTag } from "#app/data/battler-tags";
@ -4689,7 +4690,7 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) { constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) {
super(true); super(true);
this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [immuneTagTypes]; this.immuneTagTypes = coerceArray(immuneTagTypes);
} }
override canApplyPreApplyBattlerTag( override canApplyPreApplyBattlerTag(
@ -6694,7 +6695,7 @@ export class FlinchStatStageChangeAbAttr extends FlinchEffectAbAttr {
constructor(stats: BattleStat[], stages: number) { constructor(stats: BattleStat[], stages: number) {
super(); super();
this.stats = Array.isArray(stats) ? stats : [stats]; this.stats = stats;
this.stages = stages; this.stages = stages;
} }

View File

@ -1,12 +1,18 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { allMoves } from "./data-lists"; import { allMoves } from "#app/data/data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common"; import {
type nil,
getFrameMs,
getEnumKeys,
getEnumValues,
animationFileName,
coerceArray,
isNullOrUndefined,
} from "#app/utils/common";
import type { BattlerIndex } from "#enums/battler-index"; import type { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils/common";
import Phaser from "phaser"; import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common"; import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
@ -520,7 +526,7 @@ function logMissingMoveAnim(move: MoveId, ...optionalParams: any[]) {
* @param encounterAnim one or more animations to fetch * @param encounterAnim one or more animations to fetch
*/ */
export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> { export async function initEncounterAnims(encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim]; const anims = coerceArray(encounterAnim);
const encounterAnimNames = getEnumKeys(EncounterAnim); const encounterAnimNames = getEnumKeys(EncounterAnim);
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = []; const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) { for (const anim of anims) {
@ -845,7 +851,7 @@ export abstract class BattleAnim {
return; return;
} }
const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(SubstituteTag) : null; const targetSubstitute = !!onSubstitute && user !== target ? target.getTag(BattlerTagType.SUBSTITUTE) : null;
const userSprite = user.getSprite(); const userSprite = user.getSprite();
const targetSprite = targetSubstitute?.sprite ?? target.getSprite(); const targetSprite = targetSubstitute?.sprite ?? target.getSprite();

View File

@ -21,7 +21,7 @@ import type { MoveEffectPhase } from "#app/phases/move-effect-phase";
import type { MovePhase } from "#app/phases/move-phase"; import type { MovePhase } from "#app/phases/move-phase";
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common"; import { BooleanHolder, coerceArray, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -50,7 +50,7 @@ export class BattlerTag {
isBatonPassable = false, isBatonPassable = false,
) { ) {
this.tagType = tagType; this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType]; this.lapseTypes = coerceArray(lapseType);
this.turnCount = turnCount; this.turnCount = turnCount;
this.sourceMove = sourceMove!; // TODO: is this bang correct? this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.sourceId = sourceId; this.sourceId = sourceId;
@ -125,16 +125,6 @@ export interface TerrainBattlerTag {
* Players and enemies should not be allowed to select restricted moves. * Players and enemies should not be allowed to select restricted moves.
*/ */
export abstract class MoveRestrictionBattlerTag extends BattlerTag { export abstract class MoveRestrictionBattlerTag extends BattlerTag {
constructor(
tagType: BattlerTagType,
lapseType: BattlerTagLapseType | BattlerTagLapseType[],
turnCount: number,
sourceMove?: MoveId,
sourceId?: number,
) {
super(tagType, lapseType, turnCount, sourceMove, sourceId);
}
/** @override */ /** @override */
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) { if (lapseType === BattlerTagLapseType.PRE_MOVE) {
@ -1470,16 +1460,6 @@ export class WrapTag extends DamagingTrapTag {
} }
export abstract class VortexTrapTag extends DamagingTrapTag { export abstract class VortexTrapTag extends DamagingTrapTag {
constructor(
tagType: BattlerTagType,
commonAnim: CommonAnim,
turnCount: number,
sourceMove: MoveId,
sourceId: number,
) {
super(tagType, commonAnim, turnCount, sourceMove, sourceId);
}
getTrapMessage(pokemon: Pokemon): string { getTrapMessage(pokemon: Pokemon): string {
return i18next.t("battlerTags:vortexOnTrap", { return i18next.t("battlerTags:vortexOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),

View File

@ -2156,6 +2156,7 @@ export class PlantHealAttr extends WeatherHealAttr {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
case WeatherType.HAIL: case WeatherType.HAIL:
case WeatherType.SNOW: case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return 0.25; return 0.25;
default: default:
@ -4157,6 +4158,7 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr {
case WeatherType.SANDSTORM: case WeatherType.SANDSTORM:
case WeatherType.HAIL: case WeatherType.HAIL:
case WeatherType.SNOW: case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
power.value *= 0.5; power.value *= 0.5;
return true; return true;

View File

@ -11,10 +11,7 @@ import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requir
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { ModifierTier } from "#enums/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import { import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
getPlayerModifierTypeOptions,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type"; import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -7,10 +7,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { ModifierTier } from "#enums/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { MusicPreference } from "#app/system/settings/settings"; import { MusicPreference } from "#app/system/settings/settings";
import type { ModifierTypeOption } from "#app/modifier/modifier-type"; import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import { import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
getPlayerModifierTypeOptions,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type"; import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

@ -11,7 +11,7 @@ import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { isNullOrUndefined } from "#app/utils/common"; import { coerceArray, isNullOrUndefined } from "#app/utils/common";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -272,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) { constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super(); super();
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay]; this.requiredTimeOfDay = coerceArray(timeOfDay);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -294,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
constructor(weather: WeatherType | WeatherType[]) { constructor(weather: WeatherType | WeatherType[]) {
super(); super();
this.requiredWeather = Array.isArray(weather) ? weather : [weather]; this.requiredWeather = coerceArray(weather);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -360,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
constructor(heldItem: string | string[], minNumberOfItems = 1) { constructor(heldItem: string | string[], minNumberOfItems = 1) {
super(); super();
this.minNumberOfItems = minNumberOfItems; this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = coerceArray(heldItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -426,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredSpecies = Array.isArray(species) ? species : [species]; this.requiredSpecies = coerceArray(species);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -466,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredNature = Array.isArray(nature) ? nature : [nature]; this.requiredNature = coerceArray(nature);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -504,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.excludeFainted = excludeFainted; this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredType = Array.isArray(type) ? type : [type]; this.requiredType = coerceArray(type);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -558,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [moves]; this.requiredMoves = coerceArray(moves);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -609,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove]; this.requiredMoves = coerceArray(learnableMove);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -665,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities]; this.requiredAbilities = coerceArray(abilities);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -710,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect]; this.requiredStatusEffect = coerceArray(statusEffect);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -785,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem]; this.requiredFormChangeItem = coerceArray(formChangeItem);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -843,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems]; this.requiredEvolutionItem = coerceArray(evolutionItems);
} }
override meetsRequirement(): boolean { override meetsRequirement(): boolean {
@ -908,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem]; this.requiredHeldItemModifiers = coerceArray(heldItem);
this.requireTransferable = requireTransferable; this.requireTransferable = requireTransferable;
} }
@ -972,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
super(); super();
this.minNumberOfPokemon = minNumberOfPokemon; this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery; this.invertQuery = invertQuery;
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes]; this.requiredHeldItemTypes = coerceArray(heldItemTypes);
this.requireTransferable = requireTransferable; this.requireTransferable = requireTransferable;
} }

View File

@ -2,7 +2,7 @@ import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encoun
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "../moves/pokemon-move"; import type { PokemonMove } from "../moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common"; import { capitalizeFirstLetter, coerceArray, isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
@ -717,7 +717,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withAnimations( withAnimations(
...encounterAnimations: EncounterAnim[] ...encounterAnimations: EncounterAnim[]
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> { ): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations]; const animations = coerceArray(encounterAnimations);
return Object.assign(this, { encounterAnimations: animations }); return Object.assign(this, { encounterAnimations: animations });
} }
@ -729,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedGameModes( withDisallowedGameModes(
...disallowedGameModes: GameModes[] ...disallowedGameModes: GameModes[]
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> { ): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes]; const gameModes = coerceArray(disallowedGameModes);
return Object.assign(this, { disallowedGameModes: gameModes }); return Object.assign(this, { disallowedGameModes: gameModes });
} }
@ -741,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedChallenges( withDisallowedChallenges(
...disallowedChallenges: Challenges[] ...disallowedChallenges: Challenges[]
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> { ): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges]; const challenges = coerceArray(disallowedChallenges);
return Object.assign(this, { disallowedChallenges: challenges }); return Object.assign(this, { disallowedChallenges: challenges });
} }

View File

@ -1,7 +1,7 @@
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { isNullOrUndefined } from "#app/utils/common"; import { coerceArray, isNullOrUndefined } from "#app/utils/common";
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) { constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
super(); super();
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves]; this.requiredMoves = coerceArray(requiredMoves);
this.excludeLevelMoves = options.excludeLevelMoves ?? false; this.excludeLevelMoves = options.excludeLevelMoves ?? false;
this.excludeTmMoves = options.excludeTmMoves ?? false; this.excludeTmMoves = options.excludeTmMoves ?? false;

View File

@ -25,7 +25,7 @@ import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-optio
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler"; import { PartyUiMode } from "#app/ui/party-ui-handler";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt, randomString, randSeedItem, coerceArray } from "#app/utils/common";
import type { BattlerTagType } from "#enums/battler-tag-type"; import type { BattlerTagType } from "#enums/battler-tag-type";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";
import type { TrainerType } from "#enums/trainer-type"; import type { TrainerType } from "#enums/trainer-type";
@ -449,7 +449,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
* @param moves * @param moves
*/ */
export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) { export function loadCustomMovesForEncounter(moves: MoveId | MoveId[]) {
moves = Array.isArray(moves) ? moves : [moves]; moves = coerceArray(moves);
return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves)); return Promise.all(moves.map(move => initMoveAnim(move))).then(() => loadMoveAnimAssets(moves));
} }
@ -792,7 +792,7 @@ export function setEncounterRewards(
* @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue * @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue
*/ */
export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) { export function setEncounterExp(participantId: number | number[], baseExpValue: number, useWaveIndex = true) {
const participantIds = Array.isArray(participantId) ? participantId : [participantId]; const participantIds = coerceArray(participantId);
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => { globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds)); globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));

View File

@ -1,5 +1,5 @@
import i18next from "i18next"; import i18next from "i18next";
import type { Constructor } from "#app/utils/common"; import { coerceArray, type Constructor } from "#app/utils/common";
import type { TimeOfDay } from "#enums/time-of-day"; import type { TimeOfDay } from "#enums/time-of-day";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
@ -125,10 +125,7 @@ export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigg
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) { constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super(); super();
if (!Array.isArray(statusEffects)) { this.statusEffects = coerceArray(statusEffects);
statusEffects = [statusEffects];
}
this.statusEffects = statusEffects;
this.invert = invert; this.invert = invert;
// this.description = i18next.t("pokemonEvolutions:Forms.statusEffect"); // this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
} }

View File

@ -1,7 +1,14 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "../data-lists"; import { modifierTypes } from "../data-lists";
import { PokemonMove } from "../moves/pokemon-move"; import { PokemonMove } from "../moves/pokemon-move";
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common"; import {
toReadableString,
isNullOrUndefined,
randSeedItem,
randSeedInt,
coerceArray,
randSeedIntRange,
} from "#app/utils/common";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms"; import { tmSpecies } from "#app/data/balance/tms";
@ -554,10 +561,7 @@ export class TrainerConfig {
this.speciesPools = evilAdminTrainerPools[poolName]; this.speciesPools = evilAdminTrainerPools[poolName];
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_"); const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
@ -620,10 +624,7 @@ export class TrainerConfig {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5); this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
} }
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) { this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
}); });
if (!isNullOrUndefined(specialtyType)) { if (!isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType)); this.setSpeciesFilter(p => p.isOfType(specialtyType));
@ -668,12 +669,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool. // Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
}); });
// If specialty type is provided, set species filter and specialty type. // If specialty type is provided, set species filter and specialty type.
@ -729,12 +726,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species. // Set up party members with their corresponding species.
signatureSpecies.forEach((speciesPool, s) => { signatureSpecies.forEach((speciesPool, s) => {
// Ensure speciesPool is an array.
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
// Set a function to get a random party member from the species pool. // Set a function to get a random party member from the species pool.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool)); this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
}); });
// Set species filter and specialty type if provided, otherwise filter by base total. // Set species filter and specialty type if provided, otherwise filter by base total.

View File

@ -1,13 +0,0 @@
// biome-ignore lint/correctness/noUnusedImports: Used in tsdoc
import type ConfirmUiHandler from "#app/ui/confirm-ui-handler";
/**
* Used by {@linkcode ConfirmUiHandler} to determine whether the cursor should start on Yes or No
*/
export const ConfirmUiMode = Object.freeze({
/** Start cursor on Yes */
DEFAULT_YES: 1,
/** Start cursor on No */
DEFAULT_NO: 2
});
export type ConfirmUiMode = typeof ConfirmUiMode[keyof typeof ConfirmUiMode];

View File

@ -262,7 +262,7 @@ export class Arena {
return 5; return 5;
} }
break; break;
case SpeciesId.LYCANROC: case SpeciesId.LYCANROC: {
const timeOfDay = this.getTimeOfDay(); const timeOfDay = this.getTimeOfDay();
switch (timeOfDay) { switch (timeOfDay) {
case TimeOfDay.DAY: case TimeOfDay.DAY:
@ -274,6 +274,7 @@ export class Arena {
return 1; return 1;
} }
break; break;
}
} }
return 0; return 0;

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import Pokemon from "./pokemon"; import Pokemon from "./pokemon";
import { fixedInt, randInt } from "#app/utils/common"; import { fixedInt, coerceArray, randInt } from "#app/utils/common";
export default class PokemonSpriteSparkleHandler { export default class PokemonSpriteSparkleHandler {
private sprites: Set<Phaser.GameObjects.Sprite>; private sprites: Set<Phaser.GameObjects.Sprite>;
@ -57,9 +57,7 @@ export default class PokemonSpriteSparkleHandler {
} }
add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void { add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
if (!Array.isArray(sprites)) { sprites = coerceArray(sprites);
sprites = [sprites];
}
for (const s of sprites) { for (const s of sprites) {
if (this.sprites.has(s)) { if (this.sprites.has(s)) {
continue; continue;
@ -69,9 +67,7 @@ export default class PokemonSpriteSparkleHandler {
} }
remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void { remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
if (!Array.isArray(sprites)) { sprites = coerceArray(sprites);
sprites = [sprites];
}
for (const s of sprites) { for (const s of sprites) {
this.sprites.delete(s); this.sprites.delete(s);
} }

View File

@ -41,6 +41,7 @@ import {
type nil, type nil,
type Constructor, type Constructor,
randSeedIntRange, randSeedIntRange,
coerceArray,
} from "#app/utils/common"; } from "#app/utils/common";
import type { TypeDamageMultiplier } from "#app/data/type"; import type { TypeDamageMultiplier } from "#app/data/type";
import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type"; import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
@ -1774,9 +1775,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
let overrideArray: MoveId | Array<MoveId> = this.isPlayer() let overrideArray: MoveId | Array<MoveId> = this.isPlayer()
? Overrides.MOVESET_OVERRIDE ? Overrides.MOVESET_OVERRIDE
: Overrides.OPP_MOVESET_OVERRIDE; : Overrides.OPP_MOVESET_OVERRIDE;
if (!Array.isArray(overrideArray)) { overrideArray = coerceArray(overrideArray);
overrideArray = [overrideArray];
}
if (overrideArray.length > 0) { if (overrideArray.length > 0) {
if (!this.isPlayer()) { if (!this.isPlayer()) {
this.moveset = []; this.moveset = [];
@ -4386,14 +4385,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// biome-ignore lint: there are a ton of issues.. // biome-ignore lint: there are a ton of issues..
faintCry(callback: Function): void { faintCry(callback: Function): void {
if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) { if (this.fusionSpecies && this.getSpeciesForm() !== this.getFusionSpeciesForm()) {
return this.fusionFaintCry(callback); this.fusionFaintCry(callback);
return;
} }
const key = this.species.getCryKey(this.formIndex); const key = this.species.getCryKey(this.formIndex);
let rate = 0.85; let rate = 0.85;
const cry = globalScene.playSound(key, { rate: rate }) as AnySound; const cry = globalScene.playSound(key, { rate: rate }) as AnySound;
if (!cry || globalScene.fieldVolume === 0) { if (!cry || globalScene.fieldVolume === 0) {
return callback(); callback();
return;
} }
const sprite = this.getSprite(); const sprite = this.getSprite();
const tintSprite = this.getTintSprite(); const tintSprite = this.getTintSprite();
@ -4461,7 +4462,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
rate: rate, rate: rate,
}) as AnySound; }) as AnySound;
if (!cry || !fusionCry || globalScene.fieldVolume === 0) { if (!cry || !fusionCry || globalScene.fieldVolume === 0) {
return callback(); callback();
return;
} }
fusionCry.stop(); fusionCry.stop();
duration = Math.min(duration, fusionCry.totalDuration * 1000); duration = Math.min(duration, fusionCry.totalDuration * 1000);

View File

@ -2393,8 +2393,6 @@ export interface ModifierPool {
[tier: string]: WeightedModifierType[]; [tier: string]: WeightedModifierType[];
} }
const modifierPool: ModifierPool = {};
let modifierPoolThresholds = {}; let modifierPoolThresholds = {};
let ignoredPoolIndexes = {}; let ignoredPoolIndexes = {};
@ -2859,7 +2857,7 @@ function getNewModifierTypeOption(
} }
tier += upgradeCount; tier += upgradeCount;
while (tier && (!modifierPool.hasOwnProperty(tier) || !modifierPool[tier].length)) { while (tier && (!pool.hasOwnProperty(tier) || !pool[tier].length)) {
tier--; tier--;
if (upgradeCount) { if (upgradeCount) {
upgradeCount--; upgradeCount--;
@ -2870,7 +2868,7 @@ function getNewModifierTypeOption(
if (tier < ModifierTier.MASTER && allowLuckUpgrades) { if (tier < ModifierTier.MASTER && allowLuckUpgrades) {
const partyLuckValue = getPartyLuckValue(party); const partyLuckValue = getPartyLuckValue(party);
const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4));
while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) { while (pool.hasOwnProperty(tier + upgradeCount + 1) && pool[tier + upgradeCount + 1].length) {
if (randSeedInt(upgradeOdds) < 4) { if (randSeedInt(upgradeOdds) < 4) {
upgradeCount++; upgradeCount++;
} else { } else {
@ -2920,6 +2918,7 @@ function getNewModifierTypeOption(
} }
export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType { export function getDefaultModifierTypeForTier(tier: ModifierTier): ModifierType {
const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER);
let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || ModifierTier.COMMON][0]; let modifierType: ModifierType | WeightedModifierType = modifierPool[tier || ModifierTier.COMMON][0];
if (modifierType instanceof WeightedModifierType) { if (modifierType instanceof WeightedModifierType) {
modifierType = (modifierType as WeightedModifierType).modifierType; modifierType = (modifierType as WeightedModifierType).modifierType;

View File

@ -272,7 +272,7 @@ class DefaultOverrides {
/** /**
* Set all non-scripted waves to use the selected battle type. * Set all non-scripted waves to use the selected battle type.
* *
* Ignored if set to {@linkcode BattleType.TRAINER} and `DISABLE_STANDARD_TRAINERS_OVERRIDE` is `true`. * Ignored if set to {@linkcode BattleType.TRAINER} and `DISABLE_STANDARD_TRAINERS_OVERRIDE` is `true`.
*/ */
readonly BATTLE_TYPE_OVERRIDE: Exclude<BattleType, BattleType.CLEAR> | null = null; readonly BATTLE_TYPE_OVERRIDE: Exclude<BattleType, BattleType.CLEAR> | null = null;
@ -298,4 +298,4 @@ export type RandomTrainerOverride = {
} }
/** The type of the {@linkcode DefaultOverrides} class */ /** The type of the {@linkcode DefaultOverrides} class */
export type OverridesType = typeof DefaultOverrides; export type OverridesType = typeof DefaultOverrides;

View File

@ -12,7 +12,7 @@ import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase"; import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import type { Constructor } from "#app/utils/common"; import { coerceArray, type Constructor } from "#app/utils/common";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase"; import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import type { DynamicPhaseType } from "#enums/dynamic-phase-type"; import type { DynamicPhaseType } from "#enums/dynamic-phase-type";
import { EggHatchPhase } from "#app/phases/egg-hatch-phase"; import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
@ -438,9 +438,7 @@ export class PhaseManager {
* @returns boolean if a targetPhase was found and added * @returns boolean if a targetPhase was found and added
*/ */
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean { prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) { phase = coerceArray(phase);
phase = [phase];
}
const target = PHASES[targetPhase]; const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target); const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target);
@ -460,9 +458,7 @@ export class PhaseManager {
* @returns `true` if a `targetPhase` was found to append to * @returns `true` if a `targetPhase` was found to append to
*/ */
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean { appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean {
if (!Array.isArray(phase)) { phase = coerceArray(phase);
phase = [phase];
}
const target = PHASES[targetPhase]; const target = PHASES[targetPhase];
const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph))); const targetIndex = this.phaseQueue.findIndex(ph => ph instanceof target && (!condition || condition(ph)));

View File

@ -1,8 +1,5 @@
import { ModifierTier } from "#enums/modifier-tier"; import { ModifierTier } from "#enums/modifier-tier";
import { import { regenerateModifierPoolThresholds, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type";
regenerateModifierPoolThresholds,
getEnemyBuffModifierForWave,
} from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type"; import { ModifierPoolType } from "#enums/modifier-pool-type";
import { EnemyPersistentModifier } from "#app/modifier/modifier"; import { EnemyPersistentModifier } from "#app/modifier/modifier";
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";

View File

@ -149,7 +149,7 @@ export class CommandPhase extends FieldPhase {
switch (command) { switch (command) {
case Command.TERA: case Command.TERA:
case Command.FIGHT: case Command.FIGHT: {
let useStruggle = false; let useStruggle = false;
const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined; const turnMove: TurnMove | undefined = args.length === 2 ? (args[1] as TurnMove) : undefined;
if ( if (
@ -233,7 +233,8 @@ export class CommandPhase extends FieldPhase {
); );
} }
break; break;
case Command.BALL: }
case Command.BALL: {
const notInDex = const notInDex =
globalScene globalScene
.getEnemyField() .getEnemyField()
@ -337,8 +338,9 @@ export class CommandPhase extends FieldPhase {
} }
} }
break; break;
}
case Command.POKEMON: case Command.POKEMON:
case Command.RUN: case Command.RUN: {
const isSwitch = command === Command.POKEMON; const isSwitch = command === Command.POKEMON;
const { currentBattle, arena } = globalScene; const { currentBattle, arena } = globalScene;
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
@ -445,6 +447,7 @@ export class CommandPhase extends FieldPhase {
} }
} }
break; break;
}
} }
if (success) { if (success) {

View File

@ -12,7 +12,6 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { ConfirmUiMode } from "#enums/confirm-ui-mode";
import { LearnMoveType } from "#enums/learn-move-type"; import { LearnMoveType } from "#enums/learn-move-type";
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
@ -164,10 +163,6 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
globalScene.ui.setMode(this.messageMode); globalScene.ui.setMode(this.messageMode);
this.replaceMoveCheck(move, pokemon); this.replaceMoveCheck(move, pokemon);
}, },
false,
0,
0,
ConfirmUiMode.DEFAULT_NO,
); );
} }

View File

@ -135,7 +135,8 @@ export class MovePhase extends BattlePhase {
this.showMoveText(); this.showMoveText();
this.showFailedText(); this.showFailedText();
} }
return this.end(); this.end();
return;
} }
this.pokemon.turnData.acted = true; this.pokemon.turnData.acted = true;
@ -310,7 +311,8 @@ export class MovePhase extends BattlePhase {
if (fail) { if (fail) {
this.showMoveText(); this.showMoveText();
this.showFailedText(); this.showFailedText();
return this.end(); this.end();
return;
} }
} }

View File

@ -53,7 +53,8 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstituteAddAnim(): void { private doSubstituteAddAnim(): void {
const substitute = this.pokemon.getTag(SubstituteTag); const substitute = this.pokemon.getTag(SubstituteTag);
if (isNullOrUndefined(substitute)) { if (isNullOrUndefined(substitute)) {
return this.end(); this.end();
return;
} }
const getSprite = () => { const getSprite = () => {
@ -116,12 +117,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstitutePreMoveAnim(): void { private doSubstitutePreMoveAnim(): void {
if (this.fieldAssets.length !== 1) { if (this.fieldAssets.length !== 1) {
return this.end(); this.end();
return;
} }
const subSprite = this.fieldAssets[0]; const subSprite = this.fieldAssets[0];
if (subSprite === undefined) { if (subSprite === undefined) {
return this.end(); this.end();
return;
} }
globalScene.tweens.add({ globalScene.tweens.add({
@ -145,12 +148,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstitutePostMoveAnim(): void { private doSubstitutePostMoveAnim(): void {
if (this.fieldAssets.length !== 1) { if (this.fieldAssets.length !== 1) {
return this.end(); this.end();
return;
} }
const subSprite = this.fieldAssets[0]; const subSprite = this.fieldAssets[0];
if (subSprite === undefined) { if (subSprite === undefined) {
return this.end(); this.end();
return;
} }
globalScene.tweens.add({ globalScene.tweens.add({
@ -174,12 +179,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doSubstituteRemoveAnim(): void { private doSubstituteRemoveAnim(): void {
if (this.fieldAssets.length !== 1) { if (this.fieldAssets.length !== 1) {
return this.end(); this.end();
return;
} }
const subSprite = this.fieldAssets[0]; const subSprite = this.fieldAssets[0];
if (subSprite === undefined) { if (subSprite === undefined) {
return this.end(); this.end();
return;
} }
const getSprite = () => { const getSprite = () => {
@ -244,12 +251,14 @@ export class PokemonAnimPhase extends BattlePhase {
private doCommanderApplyAnim(): void { private doCommanderApplyAnim(): void {
if (!globalScene.currentBattle?.double) { if (!globalScene.currentBattle?.double) {
return this.end(); this.end();
return;
} }
const dondozo = this.pokemon.getAlly(); const dondozo = this.pokemon.getAlly();
if (dondozo?.species?.speciesId !== SpeciesId.DONDOZO) { if (dondozo?.species?.speciesId !== SpeciesId.DONDOZO) {
return this.end(); this.end();
return;
} }
const tatsugiriX = this.pokemon.x + this.pokemon.getSprite().x; const tatsugiriX = this.pokemon.x + this.pokemon.getSprite().x;
@ -329,7 +338,8 @@ export class PokemonAnimPhase extends BattlePhase {
const tatsugiri = this.pokemon.getAlly(); const tatsugiri = this.pokemon.getAlly();
if (isNullOrUndefined(tatsugiri)) { if (isNullOrUndefined(tatsugiri)) {
console.warn("Aborting COMMANDER_REMOVE anim: Tatsugiri is undefined"); console.warn("Aborting COMMANDER_REMOVE anim: Tatsugiri is undefined");
return this.end(); this.end();
return;
} }
const tatsuSprite = globalScene.addPokemonSprite( const tatsuSprite = globalScene.addPokemonSprite(

View File

@ -29,7 +29,8 @@ export class PokemonTransformPhase extends PokemonPhase {
const target = globalScene.getField(true).find(p => p.getBattlerIndex() === this.targetIndex); const target = globalScene.getField(true).find(p => p.getBattlerIndex() === this.targetIndex);
if (!target) { if (!target) {
return this.end(); this.end();
return;
} }
user.summonData.speciesForm = target.getSpeciesForm(); user.summonData.speciesForm = target.getSpeciesForm();

View File

@ -29,7 +29,8 @@ export class QuietFormChangePhase extends BattlePhase {
super.start(); super.start();
if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) { if (this.pokemon.formIndex === this.pokemon.species.forms.findIndex(f => f.formKey === this.formChange.formKey)) {
return this.end(); this.end();
return;
} }
const preName = getPokemonNameWithAffix(this.pokemon); const preName = getPokemonNameWithAffix(this.pokemon);

View File

@ -51,7 +51,7 @@ float hue2rgb(float f1, float f2, float hue) {
vec3 rgb2hsl(vec3 color) { vec3 rgb2hsl(vec3 color) {
vec3 hsl; vec3 hsl;
float fmin = min(min(color.r, color.g), color.b); float fmin = min(min(color.r, color.g), color.b);
float fmax = max(max(color.r, color.g), color.b); float fmax = max(max(color.r, color.g), color.b);
float delta = fmax - fmin; float delta = fmax - fmin;
@ -66,7 +66,7 @@ vec3 rgb2hsl(vec3 color) {
hsl.y = delta / (fmax + fmin); hsl.y = delta / (fmax + fmin);
else else
hsl.y = delta / (2.0 - fmax - fmin); hsl.y = delta / (2.0 - fmax - fmin);
float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;
@ -89,24 +89,24 @@ vec3 rgb2hsl(vec3 color) {
vec3 hsl2rgb(vec3 hsl) { vec3 hsl2rgb(vec3 hsl) {
vec3 rgb; vec3 rgb;
if (hsl.y == 0.0) if (hsl.y == 0.0)
rgb = vec3(hsl.z); rgb = vec3(hsl.z);
else { else {
float f2; float f2;
if (hsl.z < 0.5) if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y); f2 = hsl.z * (1.0 + hsl.y);
else else
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);
float f1 = 2.0 * hsl.z - f2; float f1 = 2.0 * hsl.z - f2;
rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0));
rgb.g = hue2rgb(f1, f2, hsl.x); rgb.g = hue2rgb(f1, f2, hsl.x);
rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0)); rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0));
} }
return rgb; return rgb;
} }

View File

@ -83,7 +83,7 @@ vec3 rgb2hsl(vec3 color) {
hsl.y = delta / (fmax + fmin); hsl.y = delta / (fmax + fmin);
else else
hsl.y = delta / (2.0 - fmax - fmin); hsl.y = delta / (2.0 - fmax - fmin);
float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta; float deltaR = (((fmax - color.r) / 6.0) + (delta / 2.0)) / delta;
float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta; float deltaG = (((fmax - color.g) / 6.0) + (delta / 2.0)) / delta;
float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta; float deltaB = (((fmax - color.b) / 6.0) + (delta / 2.0)) / delta;
@ -106,24 +106,24 @@ vec3 rgb2hsl(vec3 color) {
vec3 hsl2rgb(vec3 hsl) { vec3 hsl2rgb(vec3 hsl) {
vec3 rgb; vec3 rgb;
if (hsl.y == 0.0) if (hsl.y == 0.0)
rgb = vec3(hsl.z); rgb = vec3(hsl.z);
else { else {
float f2; float f2;
if (hsl.z < 0.5) if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y); f2 = hsl.z * (1.0 + hsl.y);
else else
f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z); f2 = (hsl.z + hsl.y) - (hsl.y * hsl.z);
float f1 = 2.0 * hsl.z - f2; float f1 = 2.0 * hsl.z - f2;
rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0)); rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0));
rgb.g = hue2rgb(f1, f2, hsl.x); rgb.g = hue2rgb(f1, f2, hsl.x);
rgb.b= hue2rgb(f1, f2, hsl.x - (1.0/3.0)); rgb.b= hue2rgb(f1, f2, hsl.x - (1.0/3.0));
} }
return rgb; return rgb;
} }

View File

@ -1,10 +1,8 @@
import { coerceArray } from "#app/utils/common";
let manifest: object; let manifest: object;
export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin { export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin {
constructor(scene: Phaser.Scene) {
super(scene);
}
get manifest() { get manifest() {
return manifest; return manifest;
} }
@ -14,9 +12,7 @@ export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin
} }
addFile(file): void { addFile(file): void {
if (!Array.isArray(file)) { file = coerceArray(file);
file = [file];
}
file.forEach(item => { file.forEach(item => {
if (manifest) { if (manifest) {

View File

@ -174,7 +174,24 @@ export async function initI18n(): Promise<void> {
"es-MX": ["es-ES", "en"], "es-MX": ["es-ES", "en"],
default: ["en"], default: ["en"],
}, },
supportedLngs: ["en", "es-ES", "es-MX", "fr", "it", "de", "zh-CN", "zh-TW", "pt-BR", "ko", "ja", "ca", "da", "tr", "ro", "ru"], supportedLngs: [
"en",
"es-ES",
"es-MX",
"fr",
"it",
"de",
"zh-CN",
"zh-TW",
"pt-BR",
"ko",
"ja",
"ca",
"da",
"tr",
"ro",
"ru",
],
backend: { backend: {
loadPath(lng: string, [ns]: string[]) { loadPath(lng: string, [ns]: string[]) {
let fileName: string; let fileName: string;

View File

@ -41,9 +41,9 @@ export function minifyJsonPlugin(basePath: string | string[], recursive?: boolea
}, },
async closeBundle() { async closeBundle() {
console.log("Minifying JSON files..."); console.log("Minifying JSON files...");
const basePathes = Array.isArray(basePath) ? basePath : [basePath]; const basePaths = Array.isArray(basePath) ? basePath : [basePath];
basePathes.forEach(basePath => { basePaths.forEach(basePath => {
const baseDir = path.resolve(buildDir, basePath); const baseDir = path.resolve(buildDir, basePath);
if (fs.existsSync(baseDir)) { if (fs.existsSync(baseDir)) {
applyToDir(baseDir, recursive); applyToDir(baseDir, recursive);

View File

@ -1,3 +1,5 @@
import { coerceArray } from "#app/utils/common";
export const legacyCompatibleImages: string[] = []; export const legacyCompatibleImages: string[] = [];
export class SceneBase extends Phaser.Scene { export class SceneBase extends Phaser.Scene {
@ -88,9 +90,7 @@ export class SceneBase extends Phaser.Scene {
} else { } else {
folder += "/"; folder += "/";
} }
if (!Array.isArray(filenames)) { filenames = coerceArray(filenames);
filenames = [filenames];
}
for (const f of filenames as string[]) { for (const f of filenames as string[]) {
this.load.audio(folder + key, this.getCachedUrl(`audio/${folder}${f}`)); this.load.audio(folder + key, this.getCachedUrl(`audio/${folder}${f}`));
} }

View File

@ -32,7 +32,10 @@ const pressAction = i18next.t("settings:pressActionToAssign");
export const settingGamepadOptions = { export const settingGamepadOptions = {
[SettingGamepad.Controller]: [i18next.t("settings:controllerDefault"), i18next.t("settings:controllerChange")], [SettingGamepad.Controller]: [i18next.t("settings:controllerDefault"), i18next.t("settings:controllerChange")],
[SettingGamepad.Gamepad_Support]: [i18next.t("settings:gamepadSupportAuto"), i18next.t("settings:gamepadSupportDisabled")], [SettingGamepad.Gamepad_Support]: [
i18next.t("settings:gamepadSupportAuto"),
i18next.t("settings:gamepadSupportDisabled"),
],
[SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction], [SettingGamepad.Button_Up]: [`KEY ${Button.UP.toString()}`, pressAction],
[SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction], [SettingGamepad.Button_Down]: [`KEY ${Button.DOWN.toString()}`, pressAction],
[SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction], [SettingGamepad.Button_Left]: [`KEY ${Button.LEFT.toString()}`, pressAction],

View File

@ -959,7 +959,7 @@ export function setSetting(setting: string, value: number): boolean {
}, },
{ {
label: "Türkçe (Needs Help)", label: "Türkçe (Needs Help)",
handler: () => changeLocaleHandler("tr") handler: () => changeLocaleHandler("tr"),
}, },
{ {
label: "Русский (Needs Help)", label: "Русский (Needs Help)",
@ -967,11 +967,11 @@ export function setSetting(setting: string, value: number): boolean {
}, },
{ {
label: "Dansk (Needs Help)", label: "Dansk (Needs Help)",
handler: () => changeLocaleHandler("da") handler: () => changeLocaleHandler("da"),
}, },
{ {
label: "Română (Needs Help)", label: "Română (Needs Help)",
handler: () => changeLocaleHandler("ro") handler: () => changeLocaleHandler("ro"),
}, },
{ {
label: i18next.t("settings:back"), label: i18next.t("settings:back"),

View File

@ -56,10 +56,6 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
protected defaultTextStyle: TextStyle = TextStyle.WINDOW; protected defaultTextStyle: TextStyle = TextStyle.WINDOW;
protected textContent: string; protected textContent: string;
constructor(mode: UiMode | null) {
super(mode);
}
abstract getWindowWidth(): number; abstract getWindowWidth(): number;
getWindowHeight(): number { getWindowHeight(): number {

View File

@ -69,7 +69,7 @@ export default class AdminUiHandler extends FormModalUiHandler {
case AdminMode.SEARCH: case AdminMode.SEARCH:
inputFieldConfigs.push({ label: "Username" }); inputFieldConfigs.push({ label: "Username" });
break; break;
case AdminMode.ADMIN: case AdminMode.ADMIN: {
const adminResult = this.adminResult ?? { const adminResult = this.adminResult ?? {
username: "", username: "",
discordId: "", discordId: "",
@ -90,6 +90,7 @@ export default class AdminUiHandler extends FormModalUiHandler {
inputFieldConfigs.push({ label: "Last played", isReadOnly: true }); inputFieldConfigs.push({ label: "Last played", isReadOnly: true });
inputFieldConfigs.push({ label: "Registered", isReadOnly: true }); inputFieldConfigs.push({ label: "Registered", isReadOnly: true });
break; break;
}
} }
return inputFieldConfigs; return inputFieldConfigs;
} }

View File

@ -4,11 +4,8 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { ConfirmUiMode } from "#enums/confirm-ui-mode";
export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
private confirmUiMode: ConfirmUiMode;
public static readonly windowWidth: number = 48; public static readonly windowWidth: number = 48;
private switchCheck: boolean; private switchCheck: boolean;
@ -108,16 +105,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
this.optionSelectContainer.setPosition(globalScene.game.canvas.width / 6 - 1 + xOffset, -48 + yOffset); this.optionSelectContainer.setPosition(globalScene.game.canvas.width / 6 - 1 + xOffset, -48 + yOffset);
this.confirmUiMode = args.length >= 6 ? (args[5] as ConfirmUiMode) : ConfirmUiMode.DEFAULT_YES; this.setCursor(this.switchCheck ? this.switchCheckCursor : 0);
switch (this.confirmUiMode) {
case ConfirmUiMode.DEFAULT_YES:
this.setCursor(this.switchCheck ? this.switchCheckCursor : 0);
break;
case ConfirmUiMode.DEFAULT_NO:
this.setCursor(this.switchCheck ? this.switchCheckCursor : 1);
break;
}
return true; return true;
} }

View File

@ -169,12 +169,13 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container {
entryContainer.add(scoreLabel); entryContainer.add(scoreLabel);
switch (this.category) { switch (this.category) {
case ScoreboardCategory.DAILY: case ScoreboardCategory.DAILY: {
const waveLabel = addTextObject(68, 0, wave, TextStyle.WINDOW, { const waveLabel = addTextObject(68, 0, wave, TextStyle.WINDOW, {
fontSize: "54px", fontSize: "54px",
}); });
entryContainer.add(waveLabel); entryContainer.add(waveLabel);
break; break;
}
case ScoreboardCategory.WEEKLY: case ScoreboardCategory.WEEKLY:
scoreLabel.x -= 16; scoreLabel.x -= 16;
break; break;

View File

@ -131,7 +131,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
gachaInfoContainer.add(gachaUpLabel); gachaInfoContainer.add(gachaUpLabel);
switch (gachaType as GachaType) { switch (gachaType as GachaType) {
case GachaType.LEGENDARY: case GachaType.LEGENDARY: {
if (["de", "es-ES"].includes(currentLanguage)) { if (["de", "es-ES"].includes(currentLanguage)) {
gachaUpLabel.setAlign("center"); gachaUpLabel.setAlign("center");
gachaUpLabel.setY(0); gachaUpLabel.setY(0);
@ -152,6 +152,7 @@ export default class EggGachaUiHandler extends MessageUiHandler {
gachaInfoContainer.add(pokemonIcon); gachaInfoContainer.add(pokemonIcon);
break; break;
}
case GachaType.MOVE: case GachaType.MOVE:
if (["de", "es-ES", "fr", "pt-BR", "ru"].includes(currentLanguage)) { if (["de", "es-ES", "fr", "pt-BR", "ru"].includes(currentLanguage)) {
gachaUpLabel.setAlign("center"); gachaUpLabel.setAlign("center");
@ -623,11 +624,12 @@ export default class EggGachaUiHandler extends MessageUiHandler {
updateGachaInfo(gachaType: GachaType): void { updateGachaInfo(gachaType: GachaType): void {
const infoContainer = this.gachaInfoContainers[gachaType]; const infoContainer = this.gachaInfoContainers[gachaType];
switch (gachaType as GachaType) { switch (gachaType as GachaType) {
case GachaType.LEGENDARY: case GachaType.LEGENDARY: {
const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(new Date().getTime())); const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(new Date().getTime()));
const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite;
pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false));
break; break;
}
} }
} }

View File

@ -686,7 +686,7 @@ export default class MenuUiHandler extends MessageUiHandler {
error = true; error = true;
} }
break; break;
case MenuOptions.LOG_OUT: case MenuOptions.LOG_OUT: {
success = true; success = true;
const doLogout = () => { const doLogout = () => {
ui.setMode(UiMode.LOADING, { ui.setMode(UiMode.LOADING, {
@ -718,6 +718,7 @@ export default class MenuUiHandler extends MessageUiHandler {
doLogout(); doLogout();
} }
break; break;
}
} }
} else if (button === Button.CANCEL) { } else if (button === Button.CANCEL) {
success = true; success = true;

View File

@ -1385,7 +1385,7 @@ export default class PartyUiHandler extends MessageUiHandler {
case PartyOption.MOVE_1: case PartyOption.MOVE_1:
case PartyOption.MOVE_2: case PartyOption.MOVE_2:
case PartyOption.MOVE_3: case PartyOption.MOVE_3:
case PartyOption.MOVE_4: case PartyOption.MOVE_4: {
const move = pokemon.moveset[option - PartyOption.MOVE_1]; const move = pokemon.moveset[option - PartyOption.MOVE_1];
if (this.showMovePp) { if (this.showMovePp) {
const maxPP = move.getMovePp(); const maxPP = move.getMovePp();
@ -1395,7 +1395,8 @@ export default class PartyUiHandler extends MessageUiHandler {
optionName = move.getName(); optionName = move.getName();
} }
break; break;
default: }
default: {
const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); const formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon);
if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) {
const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM];
@ -1410,6 +1411,7 @@ export default class PartyUiHandler extends MessageUiHandler {
} }
} }
break; break;
}
} }
} else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) {
const learnableLevelMoves = pokemon.getLearnableLevelMoves(); const learnableLevelMoves = pokemon.getLearnableLevelMoves();

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { fixedInt } from "#app/utils/common"; import { fixedInt, coerceArray } from "#app/utils/common";
export enum PokemonIconAnimMode { export enum PokemonIconAnimMode {
NONE, NONE,
@ -49,9 +49,7 @@ export default class PokemonIconAnimHandler {
} }
addOrUpdate(icons: PokemonIcon | PokemonIcon[], mode: PokemonIconAnimMode): void { addOrUpdate(icons: PokemonIcon | PokemonIcon[], mode: PokemonIconAnimMode): void {
if (!Array.isArray(icons)) { icons = coerceArray(icons);
icons = [icons];
}
for (const i of icons) { for (const i of icons) {
if (this.icons.has(i) && this.icons.get(i) === mode) { if (this.icons.has(i) && this.icons.get(i) === mode) {
continue; continue;
@ -66,9 +64,7 @@ export default class PokemonIconAnimHandler {
} }
remove(icons: PokemonIcon | PokemonIcon[]): void { remove(icons: PokemonIcon | PokemonIcon[]): void {
if (!Array.isArray(icons)) { icons = coerceArray(icons);
icons = [icons];
}
for (const i of icons) { for (const i of icons) {
if (this.toggled) { if (this.toggled) {
const icon = this.icons.get(i); const icon = this.icons.get(i);

View File

@ -567,7 +567,7 @@ export default class RunInfoUiHandler extends UiHandler {
case GameModes.SPLICED_ENDLESS: case GameModes.SPLICED_ENDLESS:
modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false);
break; break;
case GameModes.CHALLENGE: case GameModes.CHALLENGE: {
modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); modeText.appendText(`${i18next.t("gameMode:challenge")}`, false);
modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `);
modeText.setWrapMode(1); // wrap by word modeText.setWrapMode(1); // wrap by word
@ -582,6 +582,7 @@ export default class RunInfoUiHandler extends UiHandler {
} }
} }
break; break;
}
case GameModes.ENDLESS: case GameModes.ENDLESS:
modeText.appendText(`${i18next.t("gameMode:endless")}`, false); modeText.appendText(`${i18next.t("gameMode:endless")}`, false);
break; break;
@ -687,7 +688,7 @@ export default class RunInfoUiHandler extends UiHandler {
case Challenges.SINGLE_GENERATION: case Challenges.SINGLE_GENERATION:
rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`));
break; break;
case Challenges.SINGLE_TYPE: case Challenges.SINGLE_TYPE: {
const typeRule = PokemonType[this.runInfo.challenges[i].value - 1]; const typeRule = PokemonType[this.runInfo.challenges[i].value - 1];
const typeTextColor = `[color=${TypeColor[typeRule]}]`; const typeTextColor = `[color=${TypeColor[typeRule]}]`;
const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`;
@ -695,16 +696,18 @@ export default class RunInfoUiHandler extends UiHandler {
typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]";
rules.push(typeText); rules.push(typeText);
break; break;
}
case Challenges.INVERSE_BATTLE: case Challenges.INVERSE_BATTLE:
rules.push(i18next.t("challenges:inverseBattle.shortName")); rules.push(i18next.t("challenges:inverseBattle.shortName"));
break; break;
default: default: {
const localisationKey = Challenges[this.runInfo.challenges[i].id] const localisationKey = Challenges[this.runInfo.challenges[i].id]
.split("_") .split("_")
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join(""); .join("");
rules.push(i18next.t(`challenges:${localisationKey}.name`)); rules.push(i18next.t(`challenges:${localisationKey}.name`));
break; break;
}
} }
} }
} }

View File

@ -126,6 +126,11 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
); );
this.actionsBg.setOrigin(0, 0); this.actionsBg.setOrigin(0, 0);
/*
* If there isn't enough space to fit all the icons and texts, there will be an overlap
* This currently doesn't happen, but it's something to keep in mind.
*/
const iconAction = globalScene.add.sprite(0, 0, "keyboard"); const iconAction = globalScene.add.sprite(0, 0, "keyboard");
iconAction.setOrigin(0, -0.1); iconAction.setOrigin(0, -0.1);
iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4); iconAction.setPositionRelative(this.actionsBg, this.navigationContainer.width - 32, 4);
@ -137,7 +142,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
const iconCancel = globalScene.add.sprite(0, 0, "keyboard"); const iconCancel = globalScene.add.sprite(0, 0, "keyboard");
iconCancel.setOrigin(0, -0.1); iconCancel.setOrigin(0, -0.1);
iconCancel.setPositionRelative(this.actionsBg, this.navigationContainer.width - 100, 4); iconCancel.setPositionRelative(this.actionsBg, actionText.x - 28, 4);
this.navigationIcons["BUTTON_CANCEL"] = iconCancel; this.navigationIcons["BUTTON_CANCEL"] = iconCancel;
const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL);
@ -146,7 +151,7 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
const iconReset = globalScene.add.sprite(0, 0, "keyboard"); const iconReset = globalScene.add.sprite(0, 0, "keyboard");
iconReset.setOrigin(0, -0.1); iconReset.setOrigin(0, -0.1);
iconReset.setPositionRelative(this.actionsBg, this.navigationContainer.width - 180, 4); iconReset.setPositionRelative(this.actionsBg, cancelText.x - 28, 4);
this.navigationIcons["BUTTON_HOME"] = iconReset; this.navigationIcons["BUTTON_HOME"] = iconReset;
const resetText = addTextObject(0, 0, i18next.t("settings:reset"), TextStyle.SETTINGS_LABEL); const resetText = addTextObject(0, 0, i18next.t("settings:reset"), TextStyle.SETTINGS_LABEL);

View File

@ -94,7 +94,7 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler {
const iconCancel = globalScene.add.sprite(0, 0, "keyboard"); const iconCancel = globalScene.add.sprite(0, 0, "keyboard");
iconCancel.setOrigin(0, -0.1); iconCancel.setOrigin(0, -0.1);
iconCancel.setPositionRelative(actionsBg, this.navigationContainer.width - 100, 4); iconCancel.setPositionRelative(actionsBg, actionText.x - 28, 4);
this.navigationIcons["BUTTON_CANCEL"] = iconCancel; this.navigationIcons["BUTTON_CANCEL"] = iconCancel;
const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL); const cancelText = addTextObject(0, 0, i18next.t("settings:back"), TextStyle.SETTINGS_LABEL);
@ -332,12 +332,13 @@ export default class AbstractSettingsUiHandler extends MessageUiHandler {
case Button.CYCLE_SHINY: case Button.CYCLE_SHINY:
success = this.navigationContainer.navigate(button); success = this.navigationContainer.navigate(button);
break; break;
case Button.ACTION: case Button.ACTION: {
const setting: Setting = this.settings[cursor]; const setting: Setting = this.settings[cursor];
if (setting?.activatable) { if (setting?.activatable) {
success = this.activateSetting(setting); success = this.activateSetting(setting);
} }
break; break;
}
} }
} }

View File

@ -98,7 +98,7 @@ export default class MoveTouchControlsHandler {
<div id="cancelButton" class="button">${i18next.t("settings:touchCancel")}</div> <div id="cancelButton" class="button">${i18next.t("settings:touchCancel")}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="orientation-label"> <div class="orientation-label">
${i18next.t("settings:orientation")} ${i18next.t("settings:orientation")}
<span id="orientation"> <span id="orientation">
${this.isLandscapeMode ? i18next.t("settings:landscape") : i18next.t("settings:portrait")} ${this.isLandscapeMode ? i18next.t("settings:landscape") : i18next.t("settings:portrait")}

View File

@ -1763,7 +1763,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} }
} else if (this.randomCursorObj.visible) { } else if (this.randomCursorObj.visible) {
switch (button) { switch (button) {
case Button.ACTION: case Button.ACTION: {
if (this.starterSpecies.length >= 6) { if (this.starterSpecies.length >= 6) {
error = true; error = true;
break; break;
@ -1815,6 +1815,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
} }
}); });
break; break;
}
case Button.UP: case Button.UP:
this.randomCursorObj.setVisible(false); this.randomCursorObj.setVisible(false);
this.filterBarCursor = this.filterBar.numFilters - 1; this.filterBarCursor = this.filterBar.numFilters - 1;

View File

@ -10,10 +10,6 @@ import { UiMode } from "#enums/ui-mode";
export default class TestDialogueUiHandler extends FormModalUiHandler { export default class TestDialogueUiHandler extends FormModalUiHandler {
keys: string[]; keys: string[];
constructor(mode) {
super(mode);
}
setup() { setup() {
super.setup(); super.setup();

View File

@ -611,3 +611,12 @@ export function getShinyDescriptor(variant: Variant): string {
return i18next.t("common:commonShiny"); return i18next.t("common:commonShiny");
} }
} }
/**
* If the input isn't already an array, turns it into one.
* @returns An array with the same type as the type of the input
*/
export function coerceArray<T>(input: T): T extends any[] ? T : [T];
export function coerceArray<T>(input: T): T | [T] {
return Array.isArray(input) ? input : [input];
}

View File

@ -50,5 +50,5 @@ describe("Ability Timing", () => {
await game.phaseInterceptor.to("MessagePhase"); await game.phaseInterceptor.to("MessagePhase");
expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 })); expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 }));
}, 5000); });
}); });

View File

@ -26,11 +26,12 @@ describe("Abilities - Battery", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("double"); game.override
game.override.enemySpecies(SpeciesId.SHUCKLE); .battleStyle("double")
game.override.enemyAbility(AbilityId.BALL_FETCH); .enemySpecies(SpeciesId.SHUCKLE)
game.override.moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]); .enemyAbility(AbilityId.BALL_FETCH)
game.override.enemyMoveset(MoveId.SPLASH); .moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM])
.enemyMoveset(MoveId.SPLASH);
}); });
it("raises the power of allies' special moves by 30%", async () => { it("raises the power of allies' special moves by 30%", async () => {

View File

@ -47,7 +47,7 @@ describe("Abilities - Beast Boost", () => {
await game.phaseInterceptor.to("VictoryPhase"); await game.phaseInterceptor.to("VictoryPhase");
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(1); expect(playerPokemon.getStatStage(Stat.DEF)).toBe(1);
}, 20000); });
it("should use in-battle overriden stats when determining the stat stage to raise by 1", async () => { it("should use in-battle overriden stats when determining the stat stage to raise by 1", async () => {
game.override.enemyMoveset([MoveId.GUARD_SPLIT]); game.override.enemyMoveset([MoveId.GUARD_SPLIT]);
@ -66,7 +66,7 @@ describe("Abilities - Beast Boost", () => {
await game.phaseInterceptor.to("VictoryPhase"); await game.phaseInterceptor.to("VictoryPhase");
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, 20000); });
it("should have order preference in case of stat ties", async () => { it("should have order preference in case of stat ties", async () => {
// Order preference follows the order of EFFECTIVE_STAT // Order preference follows the order of EFFECTIVE_STAT
@ -84,5 +84,5 @@ describe("Abilities - Beast Boost", () => {
await game.phaseInterceptor.to("VictoryPhase"); await game.phaseInterceptor.to("VictoryPhase");
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, 20000); });
}); });

View File

@ -36,7 +36,7 @@ describe("Abilities - Contrary", () => {
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000); });
describe("With Clear Body", () => { describe("With Clear Body", () => {
it("should apply positive effects", async () => { it("should apply positive effects", async () => {

View File

@ -24,10 +24,11 @@ describe("Abilities - COSTAR", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("double"); game.override
game.override.ability(AbilityId.COSTAR); .battleStyle("double")
game.override.moveset([MoveId.SPLASH, MoveId.NASTY_PLOT]); .ability(AbilityId.COSTAR)
game.override.enemyMoveset(MoveId.SPLASH); .moveset([MoveId.SPLASH, MoveId.NASTY_PLOT])
.enemyMoveset(MoveId.SPLASH);
}); });
test("ability copies positive stat stages", async () => { test("ability copies positive stat stages", async () => {

View File

@ -66,8 +66,7 @@ describe("Abilities - Disguise", () => {
}); });
it("takes no damage from the first hit of a multihit move and transforms to Busted form, then takes damage from the second hit", async () => { it("takes no damage from the first hit of a multihit move and transforms to Busted form, then takes damage from the second hit", async () => {
game.override.moveset([MoveId.SURGING_STRIKES]); game.override.moveset([MoveId.SURGING_STRIKES]).enemyLevel(5);
game.override.enemyLevel(5);
await game.classicMode.startBattle(); await game.classicMode.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!; const mimikyu = game.scene.getEnemyPokemon()!;
@ -106,8 +105,7 @@ describe("Abilities - Disguise", () => {
}); });
it("persists form change when switched out", async () => { it("persists form change when switched out", async () => {
game.override.enemyMoveset([MoveId.SHADOW_SNEAK]); game.override.enemyMoveset([MoveId.SHADOW_SNEAK]).starterSpecies(0);
game.override.starterSpecies(0);
await game.classicMode.startBattle([SpeciesId.MIMIKYU, SpeciesId.FURRET]); await game.classicMode.startBattle([SpeciesId.MIMIKYU, SpeciesId.FURRET]);
@ -131,8 +129,7 @@ describe("Abilities - Disguise", () => {
}); });
it("persists form change when wave changes with no arena reset", async () => { it("persists form change when wave changes with no arena reset", async () => {
game.override.starterSpecies(0); game.override.starterSpecies(0).starterForms({
game.override.starterForms({
[SpeciesId.MIMIKYU]: bustedForm, [SpeciesId.MIMIKYU]: bustedForm,
}); });
await game.classicMode.startBattle([SpeciesId.FURRET, SpeciesId.MIMIKYU]); await game.classicMode.startBattle([SpeciesId.FURRET, SpeciesId.MIMIKYU]);
@ -148,11 +145,12 @@ describe("Abilities - Disguise", () => {
}); });
it("reverts to Disguised form on arena reset", async () => { it("reverts to Disguised form on arena reset", async () => {
game.override.startingWave(4); game.override
game.override.starterSpecies(SpeciesId.MIMIKYU); .startingWave(4)
game.override.starterForms({ .starterSpecies(SpeciesId.MIMIKYU)
[SpeciesId.MIMIKYU]: bustedForm, .starterForms({
}); [SpeciesId.MIMIKYU]: bustedForm,
});
await game.classicMode.startBattle(); await game.classicMode.startBattle();
@ -168,11 +166,12 @@ describe("Abilities - Disguise", () => {
}); });
it("reverts to Disguised form on biome change when fainted", async () => { it("reverts to Disguised form on biome change when fainted", async () => {
game.override.startingWave(10); game.override
game.override.starterSpecies(0); .startingWave(10)
game.override.starterForms({ .starterSpecies(0)
[SpeciesId.MIMIKYU]: bustedForm, .starterForms({
}); [SpeciesId.MIMIKYU]: bustedForm,
});
await game.classicMode.startBattle([SpeciesId.MIMIKYU, SpeciesId.FURRET]); await game.classicMode.startBattle([SpeciesId.MIMIKYU, SpeciesId.FURRET]);
@ -206,8 +205,7 @@ describe("Abilities - Disguise", () => {
}); });
it("activates when Aerilate circumvents immunity to the move's base type", async () => { it("activates when Aerilate circumvents immunity to the move's base type", async () => {
game.override.ability(AbilityId.AERILATE); game.override.ability(AbilityId.AERILATE).moveset([MoveId.TACKLE]);
game.override.moveset([MoveId.TACKLE]);
await game.classicMode.startBattle(); await game.classicMode.startBattle();

View File

@ -44,7 +44,7 @@ describe("Abilities - Flash Fire", () => {
game.move.select(MoveId.SPLASH); game.move.select(MoveId.SPLASH);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(blissey.hp).toBe(blissey.getMaxHp()); expect(blissey.hp).toBe(blissey.getMaxHp());
}, 20000); });
it("not activate if the Pokémon is protected from the Fire-type move", async () => { it("not activate if the Pokémon is protected from the Fire-type move", async () => {
game.override.enemyMoveset([MoveId.EMBER]).moveset([MoveId.PROTECT]); game.override.enemyMoveset([MoveId.EMBER]).moveset([MoveId.PROTECT]);
@ -55,7 +55,7 @@ describe("Abilities - Flash Fire", () => {
game.move.select(MoveId.PROTECT); game.move.select(MoveId.PROTECT);
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeUndefined(); expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeUndefined();
}, 20000); });
it("activated by Will-O-Wisp", async () => { it("activated by Will-O-Wisp", async () => {
game.override.enemyMoveset([MoveId.WILL_O_WISP]).moveset(MoveId.SPLASH); game.override.enemyMoveset([MoveId.WILL_O_WISP]).moveset(MoveId.SPLASH);
@ -70,11 +70,10 @@ describe("Abilities - Flash Fire", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeDefined(); expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeDefined();
}, 20000); });
it("activated after being frozen", async () => { it("activated after being frozen", async () => {
game.override.enemyMoveset([MoveId.EMBER]).moveset(MoveId.SPLASH); game.override.enemyMoveset([MoveId.EMBER]).moveset(MoveId.SPLASH).statusEffect(StatusEffect.FREEZE);
game.override.statusEffect(StatusEffect.FREEZE);
await game.classicMode.startBattle([SpeciesId.BLISSEY]); await game.classicMode.startBattle([SpeciesId.BLISSEY]);
const blissey = game.scene.getPlayerPokemon()!; const blissey = game.scene.getPlayerPokemon()!;
@ -83,7 +82,7 @@ describe("Abilities - Flash Fire", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeDefined(); expect(blissey!.getTag(BattlerTagType.FIRE_BOOST)).toBeDefined();
}, 20000); });
it("not passing with baton pass", async () => { it("not passing with baton pass", async () => {
game.override.enemyMoveset([MoveId.EMBER]).moveset([MoveId.BATON_PASS]); game.override.enemyMoveset([MoveId.EMBER]).moveset([MoveId.BATON_PASS]);
@ -99,11 +98,14 @@ describe("Abilities - Flash Fire", () => {
const chansey = game.scene.getPlayerPokemon()!; const chansey = game.scene.getPlayerPokemon()!;
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(SpeciesId.CHANSEY); expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(SpeciesId.CHANSEY);
expect(chansey!.getTag(BattlerTagType.FIRE_BOOST)).toBeUndefined(); expect(chansey!.getTag(BattlerTagType.FIRE_BOOST)).toBeUndefined();
}, 20000); });
it("boosts Fire-type move when the ability is activated", async () => { it("boosts Fire-type move when the ability is activated", async () => {
game.override.enemyMoveset([MoveId.FIRE_PLEDGE]).moveset([MoveId.EMBER, MoveId.SPLASH]); game.override
game.override.enemyAbility(AbilityId.FLASH_FIRE).ability(AbilityId.NONE); .enemyMoveset([MoveId.FIRE_PLEDGE])
.moveset([MoveId.EMBER, MoveId.SPLASH])
.enemyAbility(AbilityId.FLASH_FIRE)
.ability(AbilityId.NONE);
await game.classicMode.startBattle([SpeciesId.BLISSEY]); await game.classicMode.startBattle([SpeciesId.BLISSEY]);
const blissey = game.scene.getPlayerPokemon()!; const blissey = game.scene.getPlayerPokemon()!;
const initialHP = 1000; const initialHP = 1000;
@ -124,12 +126,15 @@ describe("Abilities - Flash Fire", () => {
const flashFireDmg = initialHP - blissey.hp; const flashFireDmg = initialHP - blissey.hp;
expect(flashFireDmg).toBeGreaterThan(originalDmg); expect(flashFireDmg).toBeGreaterThan(originalDmg);
}, 20000); });
it("still activates regardless of accuracy check", async () => { it("still activates regardless of accuracy check", async () => {
game.override.moveset(MoveId.FIRE_PLEDGE).enemyMoveset(MoveId.EMBER); game.override
game.override.enemyAbility(AbilityId.NONE).ability(AbilityId.FLASH_FIRE); .moveset(MoveId.FIRE_PLEDGE)
game.override.enemySpecies(SpeciesId.BLISSEY); .enemyMoveset(MoveId.EMBER)
.enemyAbility(AbilityId.NONE)
.ability(AbilityId.FLASH_FIRE)
.enemySpecies(SpeciesId.BLISSEY);
await game.classicMode.startBattle([SpeciesId.RATTATA]); await game.classicMode.startBattle([SpeciesId.RATTATA]);
const blissey = game.scene.getEnemyPokemon()!; const blissey = game.scene.getEnemyPokemon()!;
@ -153,5 +158,5 @@ describe("Abilities - Flash Fire", () => {
const flashFireDmg = initialHP - blissey.hp; const flashFireDmg = initialHP - blissey.hp;
expect(flashFireDmg).toBeGreaterThan(originalDmg); expect(flashFireDmg).toBeGreaterThan(originalDmg);
}, 20000); });
}); });

View File

@ -47,9 +47,10 @@ describe("Abilities - Flower Gift", () => {
allyAbility = AbilityId.BALL_FETCH, allyAbility = AbilityId.BALL_FETCH,
enemyAbility = AbilityId.BALL_FETCH, enemyAbility = AbilityId.BALL_FETCH,
): Promise<[number, number]> => { ): Promise<[number, number]> => {
game.override.battleStyle("double"); game.override
game.override.moveset([MoveId.SPLASH, MoveId.SUNNY_DAY, move, MoveId.HEAL_PULSE]); .battleStyle("double")
game.override.enemyMoveset([MoveId.SPLASH, MoveId.HEAL_PULSE]); .moveset([MoveId.SPLASH, MoveId.SUNNY_DAY, move, MoveId.HEAL_PULSE])
.enemyMoveset([MoveId.SPLASH, MoveId.HEAL_PULSE]);
const target_index = allyAttacker ? BattlerIndex.ENEMY : BattlerIndex.PLAYER_2; const target_index = allyAttacker ? BattlerIndex.ENEMY : BattlerIndex.PLAYER_2;
const attacker_index = allyAttacker ? BattlerIndex.PLAYER_2 : BattlerIndex.ENEMY; const attacker_index = allyAttacker ? BattlerIndex.PLAYER_2 : BattlerIndex.ENEMY;
const ally_move = allyAttacker ? move : MoveId.SPLASH; const ally_move = allyAttacker ? move : MoveId.SPLASH;

View File

@ -89,7 +89,6 @@ describe("Abilities - Flower Veil", () => {
await game.move.selectEnemyMove(MoveId.THUNDER_WAVE); await game.move.selectEnemyMove(MoveId.THUNDER_WAVE);
await game.toNextTurn(); await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.status).toBeUndefined(); expect(game.scene.getPlayerPokemon()!.status).toBeUndefined();
vi.spyOn(allMoves[MoveId.THUNDER_WAVE], "accuracy", "get").mockClear();
}); });
it("should not prevent status conditions for a non-grass user and its non-grass allies", async () => { it("should not prevent status conditions for a non-grass user and its non-grass allies", async () => {

View File

@ -63,9 +63,10 @@ describe("Abilities - Good As Gold", () => {
}); });
it("should not block any status moves that target the field, one side, or all pokemon", async () => { it("should not block any status moves that target the field, one side, or all pokemon", async () => {
game.override.battleStyle("double"); game.override
game.override.enemyMoveset([MoveId.STEALTH_ROCK, MoveId.HAZE]); .battleStyle("double")
game.override.moveset([MoveId.SWORDS_DANCE, MoveId.SAFEGUARD]); .enemyMoveset([MoveId.STEALTH_ROCK, MoveId.HAZE])
.moveset([MoveId.SWORDS_DANCE, MoveId.SAFEGUARD]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]);
const [good_as_gold, ball_fetch] = game.scene.getPlayerField(); const [good_as_gold, ball_fetch] = game.scene.getPlayerField();
@ -85,8 +86,7 @@ describe("Abilities - Good As Gold", () => {
}); });
it("should not block field targeted effects in singles", async () => { it("should not block field targeted effects in singles", async () => {
game.override.battleStyle("single"); game.override.battleStyle("single").enemyMoveset([MoveId.SPIKES]);
game.override.enemyMoveset([MoveId.SPIKES]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.move.select(MoveId.SPLASH, 0); game.move.select(MoveId.SPLASH, 0);
@ -96,8 +96,7 @@ describe("Abilities - Good As Gold", () => {
}); });
it("should block the ally's helping hand", async () => { it("should block the ally's helping hand", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").moveset([MoveId.HELPING_HAND, MoveId.TACKLE]);
game.override.moveset([MoveId.HELPING_HAND, MoveId.TACKLE]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]);
game.move.select(MoveId.HELPING_HAND, 0); game.move.select(MoveId.HELPING_HAND, 0);
@ -129,8 +128,7 @@ describe("Abilities - Good As Gold", () => {
}); });
it("should not block field targeted effects like rain dance", async () => { it("should not block field targeted effects like rain dance", async () => {
game.override.battleStyle("single"); game.override.battleStyle("single").enemyMoveset([MoveId.RAIN_DANCE]);
game.override.enemyMoveset([MoveId.RAIN_DANCE]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.move.use(MoveId.SPLASH, 0); game.move.use(MoveId.SPLASH, 0);

View File

@ -4,17 +4,15 @@ import { SpeciesId } from "#enums/species-id";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import GameManager from "#test/testUtils/gameManager"; import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { PostTurnResetStatusAbAttr } from "#app/@types/ability-types"; import { PostTurnResetStatusAbAttr } from "#app/data/abilities/ability";
describe("Abilities - Healer", () => { describe("Abilities - Healer", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
let healerAttrSpy: MockInstance;
let healerAttr: PostTurnResetStatusAbAttr;
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ phaserGame = new Phaser.Game({
@ -24,7 +22,6 @@ describe("Abilities - Healer", () => {
afterEach(() => { afterEach(() => {
game.phaseInterceptor.restoreOg(); game.phaseInterceptor.restoreOg();
healerAttrSpy.mockRestore();
}); });
beforeEach(() => { beforeEach(() => {
@ -38,30 +35,28 @@ describe("Abilities - Healer", () => {
.enemyAbility(AbilityId.BALL_FETCH) .enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH);
healerAttr = allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0]; // Mock healer to have a 100% chance of healing its ally
healerAttrSpy = vi vi.spyOn(allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0], "getCondition").mockReturnValue(
.spyOn(healerAttr, "getCondition") (pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly()),
.mockReturnValue((pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly())); );
}); });
it("should not queue a message phase for healing if the ally has fainted", async () => { it("should not queue a message phase for healing if the ally has fainted", async () => {
const abSpy = vi.spyOn(PostTurnResetStatusAbAttr.prototype, "canApplyPostTurn");
game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]); game.override.moveset([MoveId.SPLASH, MoveId.LUNAR_DANCE]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
const user = game.scene.getPlayerPokemon()!; const user = game.scene.getPlayerPokemon()!;
// Only want one magikarp to have the ability. // Only want one magikarp to have the ability
vi.spyOn(user, "getAbility").mockReturnValue(allAbilities[AbilityId.HEALER]); vi.spyOn(user, "getAbility").mockReturnValue(allAbilities[AbilityId.HEALER]);
game.move.select(MoveId.SPLASH); game.move.select(MoveId.SPLASH);
// faint the ally // faint the ally
game.move.select(MoveId.LUNAR_DANCE, 1); game.move.select(MoveId.LUNAR_DANCE, 1);
const abSpy = vi.spyOn(healerAttr, "canApplyPostTurn");
await game.phaseInterceptor.to("TurnEndPhase"); await game.phaseInterceptor.to("TurnEndPhase");
// It's not enough to just test that the ally still has its status. // It's not enough to just test that the ally still has its status.
// We need to ensure that the ability failed to meet its condition // We need to ensure that the ability failed to meet its condition
expect(abSpy).toHaveReturnedWith(false); expect(abSpy).toHaveReturnedWith(false);
// Explicitly restore the mock to ensure pollution doesn't happen
abSpy.mockRestore();
}); });
it("should heal the status of an ally if the ally has a status", async () => { it("should heal the status of an ally if the ally has a status", async () => {

View File

@ -75,8 +75,7 @@ describe("Abilities - Hustle", () => {
}); });
it("does not affect OHKO moves", async () => { it("does not affect OHKO moves", async () => {
game.override.startingLevel(100); game.override.startingLevel(100).enemyLevel(30);
game.override.enemyLevel(30);
await game.classicMode.startBattle([SpeciesId.PIKACHU]); await game.classicMode.startBattle([SpeciesId.PIKACHU]);
const pikachu = game.scene.getPlayerPokemon()!; const pikachu = game.scene.getPlayerPokemon()!;

View File

@ -30,10 +30,11 @@ describe("Abilities - Ice Face", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
game.override.enemySpecies(SpeciesId.EISCUE); .battleStyle("single")
game.override.enemyAbility(AbilityId.ICE_FACE); .enemySpecies(SpeciesId.EISCUE)
game.override.moveset([MoveId.TACKLE, MoveId.ICE_BEAM, MoveId.TOXIC_THREAD, MoveId.HAIL]); .enemyAbility(AbilityId.ICE_FACE)
.moveset([MoveId.TACKLE, MoveId.ICE_BEAM, MoveId.TOXIC_THREAD, MoveId.HAIL]);
}); });
it("takes no damage from physical move and transforms to Noice", async () => { it("takes no damage from physical move and transforms to Noice", async () => {
@ -51,8 +52,7 @@ describe("Abilities - Ice Face", () => {
}); });
it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => { it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => {
game.override.moveset([MoveId.SURGING_STRIKES]); game.override.moveset([MoveId.SURGING_STRIKES]).enemyLevel(1);
game.override.enemyLevel(1);
await game.classicMode.startBattle([SpeciesId.HITMONLEE]); await game.classicMode.startBattle([SpeciesId.HITMONLEE]);
game.move.select(MoveId.SURGING_STRIKES); game.move.select(MoveId.SURGING_STRIKES);
@ -196,12 +196,13 @@ describe("Abilities - Ice Face", () => {
}); });
it("reverts to Ice Face on arena reset", async () => { it("reverts to Ice Face on arena reset", async () => {
game.override.startingWave(4); game.override
game.override.startingLevel(4); .startingWave(4)
game.override.enemySpecies(SpeciesId.MAGIKARP); .startingLevel(4)
game.override.starterForms({ .enemySpecies(SpeciesId.MAGIKARP)
[SpeciesId.EISCUE]: noiceForm, .starterForms({
}); [SpeciesId.EISCUE]: noiceForm,
});
await game.classicMode.startBattle([SpeciesId.EISCUE]); await game.classicMode.startBattle([SpeciesId.EISCUE]);

View File

@ -116,26 +116,23 @@ describe("Abilities - Illusion", () => {
expect(psychicEffectiveness).above(flameThrowerEffectiveness); expect(psychicEffectiveness).above(flameThrowerEffectiveness);
}); });
it("does not break from indirect damage", async () => { it("should not break from indirect damage from status, weather or recoil", async () => {
game.override.enemySpecies(SpeciesId.GIGALITH); game.override.enemySpecies(SpeciesId.GIGALITH).enemyAbility(AbilityId.SAND_STREAM);
game.override.enemyAbility(AbilityId.SAND_STREAM);
game.override.enemyMoveset(MoveId.WILL_O_WISP);
game.override.moveset([MoveId.FLARE_BLITZ]);
await game.classicMode.startBattle([SpeciesId.ZOROARK, SpeciesId.AZUMARILL]); await game.classicMode.startBattle([SpeciesId.ZOROARK, SpeciesId.AZUMARILL]);
game.move.select(MoveId.FLARE_BLITZ); game.move.use(MoveId.FLARE_BLITZ);
await game.move.forceEnemyMove(MoveId.WILL_O_WISP);
await game.phaseInterceptor.to("TurnEndPhase"); await game.toEndOfTurn();
const zoroark = game.scene.getPlayerPokemon()!; const zoroark = game.scene.getPlayerPokemon()!;
expect(!!zoroark.summonData.illusion).equals(true); expect(!!zoroark.summonData.illusion).equals(true);
}); });
it("copies the the name, nickname, gender, shininess, and pokeball from the illusion source", async () => { it("copies the the name, nickname, gender, shininess, and pokeball from the illusion source", async () => {
game.override.enemyMoveset(MoveId.SPLASH); game.override.enemyMoveset(MoveId.SPLASH);
await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.ZOROARK, SpeciesId.AXEW]); await game.classicMode.startBattle([SpeciesId.ABRA, SpeciesId.ZOROARK, SpeciesId.AXEW]);
const axew = game.scene.getPlayerParty().at(2)!; const axew = game.scene.getPlayerParty().at(2)!;
axew.shiny = true; axew.shiny = true;
axew.nickname = btoa(unescape(encodeURIComponent("axew nickname"))); axew.nickname = btoa(unescape(encodeURIComponent("axew nickname")));

View File

@ -162,8 +162,7 @@ describe("Abilities - Imposter", () => {
}); });
it("should stay transformed with the correct form after reload", async () => { it("should stay transformed with the correct form after reload", async () => {
game.override.moveset([MoveId.ABSORB]); game.override.moveset([MoveId.ABSORB]).enemySpecies(SpeciesId.UNOWN);
game.override.enemySpecies(SpeciesId.UNOWN);
await game.classicMode.startBattle([SpeciesId.DITTO]); await game.classicMode.startBattle([SpeciesId.DITTO]);
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;

View File

@ -62,7 +62,7 @@ describe("Abilities - Intimidate", () => {
expect(playerPokemon.species.speciesId).toBe(SpeciesId.POOCHYENA); expect(playerPokemon.species.speciesId).toBe(SpeciesId.POOCHYENA);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2);
}, 20000); });
it("should lower ATK stat stage by 1 for every enemy Pokemon in a double battle on entry", async () => { it("should lower ATK stat stage by 1 for every enemy Pokemon in a double battle on entry", async () => {
game.override.battleStyle("double").startingWave(3); game.override.battleStyle("double").startingWave(3);
@ -85,11 +85,10 @@ describe("Abilities - Intimidate", () => {
expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2); expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2);
expect(playerField[0].getStatStage(Stat.ATK)).toBe(-2); expect(playerField[0].getStatStage(Stat.ATK)).toBe(-2);
expect(playerField[1].getStatStage(Stat.ATK)).toBe(-2); expect(playerField[1].getStatStage(Stat.ATK)).toBe(-2);
}, 20000); });
it("should not activate again if there is no switch or new entry", async () => { it("should not activate again if there is no switch or new entry", async () => {
game.override.startingWave(2); game.override.startingWave(2).moveset([MoveId.SPLASH]);
game.override.moveset([MoveId.SPLASH]);
await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]); await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
@ -103,7 +102,7 @@ describe("Abilities - Intimidate", () => {
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, 20000); });
it("should lower ATK stat stage by 1 for every switch", async () => { it("should lower ATK stat stage by 1 for every switch", async () => {
game.override.moveset([MoveId.SPLASH]).enemyMoveset([MoveId.VOLT_SWITCH]).startingWave(5); game.override.moveset([MoveId.SPLASH]).enemyMoveset([MoveId.VOLT_SWITCH]).startingWave(5);
@ -130,5 +129,5 @@ describe("Abilities - Intimidate", () => {
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-3); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-3);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
}, 200000); });
}); });

View File

@ -22,10 +22,11 @@ describe("Abilities - Intrepid Sword", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
game.override.enemySpecies(SpeciesId.ZACIAN); .battleStyle("single")
game.override.enemyAbility(AbilityId.INTREPID_SWORD); .enemySpecies(SpeciesId.ZACIAN)
game.override.ability(AbilityId.INTREPID_SWORD); .enemyAbility(AbilityId.INTREPID_SWORD)
.ability(AbilityId.INTREPID_SWORD);
}); });
it("should raise ATK stat stage by 1 on entry", async () => { it("should raise ATK stat stage by 1 on entry", async () => {
@ -38,5 +39,5 @@ describe("Abilities - Intrepid Sword", () => {
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000); });
}); });

View File

@ -108,8 +108,7 @@ describe("Abilities - Libero", () => {
}); });
test("ability applies correctly even if the type has changed by another ability", async () => { test("ability applies correctly even if the type has changed by another ability", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).passiveAbility(AbilityId.REFRIGERATE);
game.override.passiveAbility(AbilityId.REFRIGERATE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -156,8 +155,7 @@ describe("Abilities - Libero", () => {
}); });
test("ability applies correctly even if the pokemon's move misses", async () => { test("ability applies correctly even if the pokemon's move misses", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).enemyMoveset(MoveId.SPLASH);
game.override.enemyMoveset(MoveId.SPLASH);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -188,8 +186,7 @@ describe("Abilities - Libero", () => {
}); });
test("ability applies correctly even if the pokemon's move fails because of type immunity", async () => { test("ability applies correctly even if the pokemon's move fails because of type immunity", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).enemySpecies(SpeciesId.GASTLY);
game.override.enemySpecies(SpeciesId.GASTLY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -262,8 +259,7 @@ describe("Abilities - Libero", () => {
}); });
test("ability applies correctly even if the pokemon's Trick-or-Treat fails", async () => { test("ability applies correctly even if the pokemon's Trick-or-Treat fails", async () => {
game.override.moveset([MoveId.TRICK_OR_TREAT]); game.override.moveset([MoveId.TRICK_OR_TREAT]).enemySpecies(SpeciesId.GASTLY);
game.override.enemySpecies(SpeciesId.GASTLY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -41,18 +41,16 @@ describe("Abilities - Magic Bounce", () => {
it("should reflect basic status moves", async () => { it("should reflect basic status moves", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.move.select(MoveId.GROWL); game.move.use(MoveId.GROWL);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(-1); expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(-1);
}); });
it("should not bounce moves while the target is in the semi-invulnerable state", async () => { it("should not bounce moves while the target is in the semi-invulnerable state", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.override.moveset([MoveId.GROWL]);
game.override.enemyMoveset([MoveId.FLY]);
game.move.select(MoveId.GROWL); game.move.use(MoveId.GROWL);
await game.move.selectEnemyMove(MoveId.FLY); await game.move.forceEnemyMove(MoveId.FLY);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
@ -61,11 +59,10 @@ describe("Abilities - Magic Bounce", () => {
it("should individually bounce back multi-target moves", async () => { it("should individually bounce back multi-target moves", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double");
game.override.moveset([MoveId.GROWL, MoveId.SPLASH]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
game.move.select(MoveId.GROWL, 0); game.move.use(MoveId.GROWL, 0);
game.move.select(MoveId.SPLASH, 1); game.move.use(MoveId.SPLASH, 1);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
const user = game.scene.getPlayerField()[0]; const user = game.scene.getPlayerField()[0];
@ -75,9 +72,8 @@ describe("Abilities - Magic Bounce", () => {
it("should still bounce back a move that would otherwise fail", async () => { it("should still bounce back a move that would otherwise fail", async () => {
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.scene.getEnemyPokemon()?.setStatStage(Stat.ATK, -6); game.scene.getEnemyPokemon()?.setStatStage(Stat.ATK, -6);
game.override.moveset([MoveId.GROWL]);
game.move.select(MoveId.GROWL); game.move.use(MoveId.GROWL);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(-1); expect(game.scene.getPlayerPokemon()!.getStatStage(Stat.ATK)).toBe(-1);
@ -107,29 +103,26 @@ describe("Abilities - Magic Bounce", () => {
game.override.ability(AbilityId.MOLD_BREAKER); game.override.ability(AbilityId.MOLD_BREAKER);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.move.select(MoveId.GROWL); game.move.use(MoveId.GROWL);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1); expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(-1);
}); });
it("should bounce back a spread status move against both pokemon", async () => { it("should bounce back a spread status move against both pokemon", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").enemyMoveset([MoveId.SPLASH]);
game.override.moveset([MoveId.GROWL, MoveId.SPLASH]);
game.override.enemyMoveset([MoveId.SPLASH]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
game.move.select(MoveId.GROWL, 0); game.move.use(MoveId.GROWL, 0);
game.move.select(MoveId.SPLASH, 1); game.move.use(MoveId.SPLASH, 1);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getPlayerField().every(p => p.getStatStage(Stat.ATK) === -2)).toBeTruthy(); expect(game.scene.getPlayerField().every(p => p.getStatStage(Stat.ATK) === -2)).toBeTruthy();
}); });
it("should only bounce spikes back once in doubles when both targets have magic bounce", async () => { it("should only bounce spikes back once in doubles when both targets have magic bounce", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").moveset([MoveId.SPIKES]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.override.moveset([MoveId.SPIKES]);
game.move.select(MoveId.SPIKES); game.move.select(MoveId.SPIKES);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
@ -139,8 +132,7 @@ describe("Abilities - Magic Bounce", () => {
}); });
it("should bounce spikes even when the target is protected", async () => { it("should bounce spikes even when the target is protected", async () => {
game.override.moveset([MoveId.SPIKES]); game.override.moveset([MoveId.SPIKES]).enemyMoveset([MoveId.PROTECT]);
game.override.enemyMoveset([MoveId.PROTECT]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.move.select(MoveId.SPIKES); game.move.select(MoveId.SPIKES);
@ -149,8 +141,7 @@ describe("Abilities - Magic Bounce", () => {
}); });
it("should not bounce spikes when the target is in the semi-invulnerable state", async () => { it("should not bounce spikes when the target is in the semi-invulnerable state", async () => {
game.override.moveset([MoveId.SPIKES]); game.override.moveset([MoveId.SPIKES]).enemyMoveset([MoveId.FLY]);
game.override.enemyMoveset([MoveId.FLY]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.move.select(MoveId.SPIKES); game.move.select(MoveId.SPIKES);
@ -160,9 +151,8 @@ describe("Abilities - Magic Bounce", () => {
}); });
it("should not bounce back curse", async () => { it("should not bounce back curse", async () => {
game.override.starterSpecies(SpeciesId.GASTLY);
await game.classicMode.startBattle([SpeciesId.GASTLY]);
game.override.moveset([MoveId.CURSE]); game.override.moveset([MoveId.CURSE]);
await game.classicMode.startBattle([SpeciesId.GASTLY]);
game.move.select(MoveId.CURSE); game.move.select(MoveId.CURSE);
await game.phaseInterceptor.to("BerryPhase"); await game.phaseInterceptor.to("BerryPhase");
@ -171,8 +161,7 @@ describe("Abilities - Magic Bounce", () => {
}); });
it("should not cause encore to be interrupted after bouncing", async () => { it("should not cause encore to be interrupted after bouncing", async () => {
game.override.moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]); game.override.moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]).enemyMoveset([MoveId.TACKLE, MoveId.GROWL]);
game.override.enemyMoveset([MoveId.TACKLE, MoveId.GROWL]);
// game.override.ability(AbilityId.MOLD_BREAKER); // game.override.ability(AbilityId.MOLD_BREAKER);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
@ -199,9 +188,10 @@ describe("Abilities - Magic Bounce", () => {
// TODO: encore is failing if the last move was virtual. // TODO: encore is failing if the last move was virtual.
it.todo("should not cause the bounced move to count for encore", async () => { it.todo("should not cause the bounced move to count for encore", async () => {
game.override.moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE]); game.override
game.override.enemyMoveset([MoveId.GROWL, MoveId.TACKLE]); .moveset([MoveId.SPLASH, MoveId.GROWL, MoveId.ENCORE])
game.override.enemyAbility(AbilityId.MAGIC_BOUNCE); .enemyMoveset([MoveId.GROWL, MoveId.TACKLE])
.enemyAbility(AbilityId.MAGIC_BOUNCE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const playerPokemon = game.scene.getPlayerPokemon()!; const playerPokemon = game.scene.getPlayerPokemon()!;
@ -227,9 +217,8 @@ describe("Abilities - Magic Bounce", () => {
// TODO: stomping tantrum should consider moves that were bounced. // TODO: stomping tantrum should consider moves that were bounced.
it.todo("should cause stomping tantrum to double in power when the last move was bounced", async () => { it.todo("should cause stomping tantrum to double in power when the last move was bounced", async () => {
game.override.battleStyle("single"); game.override.battleStyle("single").moveset([MoveId.STOMPING_TANTRUM, MoveId.CHARM]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.override.moveset([MoveId.STOMPING_TANTRUM, MoveId.CHARM]);
const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM];
vi.spyOn(stomping_tantrum, "calculateBattlePower"); vi.spyOn(stomping_tantrum, "calculateBattlePower");
@ -242,36 +231,30 @@ describe("Abilities - Magic Bounce", () => {
expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(150); expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(150);
}); });
// TODO: stomping tantrum should consider moves that were bounced. // TODO: stomping tantrum should consider moves that were bounced
it.todo( it.todo("should boost enemy's stomping tantrum after failed bounce", async () => {
"should properly cause the enemy's stomping tantrum to be doubled in power after bouncing and failing", game.override.enemyMoveset([MoveId.STOMPING_TANTRUM, MoveId.SPLASH, MoveId.CHARM]);
async () => { await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
game.override.enemyMoveset([MoveId.STOMPING_TANTRUM, MoveId.SPLASH, MoveId.CHARM]);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM]; const stomping_tantrum = allMoves[MoveId.STOMPING_TANTRUM];
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(stomping_tantrum, "calculateBattlePower"); vi.spyOn(stomping_tantrum, "calculateBattlePower");
game.move.select(MoveId.SPORE); // Spore gets reflected back onto us
await game.move.selectEnemyMove(MoveId.CHARM); game.move.select(MoveId.SPORE);
await game.phaseInterceptor.to("TurnEndPhase"); await game.move.selectEnemyMove(MoveId.CHARM);
expect(enemy.getLastXMoves(1)[0].result).toBe("success"); await game.toNextTurn();
expect(enemy.getLastXMoves(1)[0].result).toBe("success");
await game.phaseInterceptor.to("BerryPhase"); game.move.select(MoveId.SPORE);
expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75); await game.move.selectEnemyMove(MoveId.STOMPING_TANTRUM);
await game.toNextTurn();
await game.toNextTurn(); expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(150);
game.move.select(MoveId.GROWL); });
await game.phaseInterceptor.to("BerryPhase");
expect(stomping_tantrum.calculateBattlePower).toHaveReturnedWith(75);
},
);
it("should respect immunities when bouncing a move", async () => { it("should respect immunities when bouncing a move", async () => {
vi.spyOn(allMoves[MoveId.THUNDER_WAVE], "accuracy", "get").mockReturnValue(100); vi.spyOn(allMoves[MoveId.THUNDER_WAVE], "accuracy", "get").mockReturnValue(100);
game.override.moveset([MoveId.THUNDER_WAVE, MoveId.GROWL]); game.override.moveset([MoveId.THUNDER_WAVE, MoveId.GROWL]).ability(AbilityId.SOUNDPROOF);
game.override.ability(AbilityId.SOUNDPROOF);
await game.classicMode.startBattle([SpeciesId.PHANPY]); await game.classicMode.startBattle([SpeciesId.PHANPY]);
// Turn 1 - thunder wave immunity test // Turn 1 - thunder wave immunity test
@ -309,8 +292,7 @@ describe("Abilities - Magic Bounce", () => {
}); });
it("should always apply the leftmost available target's magic bounce when bouncing moves like sticky webs in doubles", async () => { it("should always apply the leftmost available target's magic bounce when bouncing moves like sticky webs in doubles", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").moveset([MoveId.STICKY_WEB, MoveId.SPLASH, MoveId.TRICK_ROOM]);
game.override.moveset([MoveId.STICKY_WEB, MoveId.SPLASH, MoveId.TRICK_ROOM]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
const [enemy_1, enemy_2] = game.scene.getEnemyField(); const [enemy_1, enemy_2] = game.scene.getEnemyField();
@ -345,6 +327,7 @@ describe("Abilities - Magic Bounce", () => {
it("should not bounce back status moves that hit through semi-invulnerable states", async () => { it("should not bounce back status moves that hit through semi-invulnerable states", async () => {
game.override.moveset([MoveId.TOXIC, MoveId.CHARM]); game.override.moveset([MoveId.TOXIC, MoveId.CHARM]);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
game.move.select(MoveId.TOXIC); game.move.select(MoveId.TOXIC);
await game.move.selectEnemyMove(MoveId.FLY); await game.move.selectEnemyMove(MoveId.FLY);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);

View File

@ -30,16 +30,16 @@ describe("Abilities - Magic Guard", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
/** Player Pokemon overrides */ game.override
game.override.ability(AbilityId.MAGIC_GUARD); /** Player Pokemon overrides */
game.override.moveset([MoveId.SPLASH]); .ability(AbilityId.MAGIC_GUARD)
game.override.startingLevel(100); .moveset([MoveId.SPLASH])
.startingLevel(100)
/** Enemy Pokemon overrides */ /** Enemy Pokemon overrides */
game.override.enemySpecies(SpeciesId.SNORLAX); .enemySpecies(SpeciesId.SNORLAX)
game.override.enemyAbility(AbilityId.INSOMNIA); .enemyAbility(AbilityId.INSOMNIA)
game.override.enemyMoveset(MoveId.SPLASH); .enemyMoveset(MoveId.SPLASH)
game.override.enemyLevel(100); .enemyLevel(100);
}); });
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(Ability) //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(Ability)
@ -89,8 +89,9 @@ describe("Abilities - Magic Guard", () => {
}); });
it("ability effect should not persist when the ability is replaced", async () => { it("ability effect should not persist when the ability is replaced", async () => {
game.override.enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED]); game.override
game.override.statusEffect(StatusEffect.POISON); .enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED])
.statusEffect(StatusEffect.POISON);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -108,8 +109,7 @@ describe("Abilities - Magic Guard", () => {
}); });
it("Magic Guard prevents damage caused by burn but other non-damaging effects are still applied", async () => { it("Magic Guard prevents damage caused by burn but other non-damaging effects are still applied", async () => {
game.override.enemyStatusEffect(StatusEffect.BURN); game.override.enemyStatusEffect(StatusEffect.BURN).enemyAbility(AbilityId.MAGIC_GUARD);
game.override.enemyAbility(AbilityId.MAGIC_GUARD);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -130,8 +130,7 @@ describe("Abilities - Magic Guard", () => {
}); });
it("Magic Guard prevents damage caused by toxic but other non-damaging effects are still applied", async () => { it("Magic Guard prevents damage caused by toxic but other non-damaging effects are still applied", async () => {
game.override.enemyStatusEffect(StatusEffect.TOXIC); game.override.enemyStatusEffect(StatusEffect.TOXIC).enemyAbility(AbilityId.MAGIC_GUARD);
game.override.enemyAbility(AbilityId.MAGIC_GUARD);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -208,8 +207,7 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard prevents against damage from volatile status effects", async () => { it("Magic Guard prevents against damage from volatile status effects", async () => {
await game.classicMode.startBattle([SpeciesId.DUSKULL]); await game.classicMode.startBattle([SpeciesId.DUSKULL]);
game.override.moveset([MoveId.CURSE]); game.override.moveset([MoveId.CURSE]).enemyAbility(AbilityId.MAGIC_GUARD);
game.override.enemyAbility(AbilityId.MAGIC_GUARD);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
@ -331,8 +329,9 @@ describe("Abilities - Magic Guard", () => {
//Tests the ability Bad Dreams //Tests the ability Bad Dreams
game.override.statusEffect(StatusEffect.SLEEP); game.override.statusEffect(StatusEffect.SLEEP);
//enemy pokemon is given Spore just in case player pokemon somehow awakens during test //enemy pokemon is given Spore just in case player pokemon somehow awakens during test
game.override.enemyMoveset([MoveId.SPORE, MoveId.SPORE, MoveId.SPORE, MoveId.SPORE]); game.override
game.override.enemyAbility(AbilityId.BAD_DREAMS); .enemyMoveset([MoveId.SPORE, MoveId.SPORE, MoveId.SPORE, MoveId.SPORE])
.enemyAbility(AbilityId.BAD_DREAMS);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -353,8 +352,7 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard prevents damage from abilities with PostFaintContactDamageAbAttr", async () => { it("Magic Guard prevents damage from abilities with PostFaintContactDamageAbAttr", async () => {
//Tests the abilities Innards Out/Aftermath //Tests the abilities Innards Out/Aftermath
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).enemyAbility(AbilityId.AFTERMATH);
game.override.enemyAbility(AbilityId.AFTERMATH);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -377,8 +375,7 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard prevents damage from abilities with PostDefendContactDamageAbAttr", async () => { it("Magic Guard prevents damage from abilities with PostDefendContactDamageAbAttr", async () => {
//Tests the abilities Iron Barbs/Rough Skin //Tests the abilities Iron Barbs/Rough Skin
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).enemyAbility(AbilityId.IRON_BARBS);
game.override.enemyAbility(AbilityId.IRON_BARBS);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -400,8 +397,7 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard prevents damage from abilities with ReverseDrainAbAttr", async () => { it("Magic Guard prevents damage from abilities with ReverseDrainAbAttr", async () => {
//Tests the ability Liquid Ooze //Tests the ability Liquid Ooze
game.override.moveset([MoveId.ABSORB]); game.override.moveset([MoveId.ABSORB]).enemyAbility(AbilityId.LIQUID_OOZE);
game.override.enemyAbility(AbilityId.LIQUID_OOZE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -422,9 +418,7 @@ describe("Abilities - Magic Guard", () => {
}); });
it("Magic Guard prevents HP loss from abilities with PostWeatherLapseDamageAbAttr", async () => { it("Magic Guard prevents HP loss from abilities with PostWeatherLapseDamageAbAttr", async () => {
//Tests the abilities Solar Power/Dry Skin game.override.passiveAbility(AbilityId.SOLAR_POWER).weather(WeatherType.SUNNY);
game.override.passiveAbility(AbilityId.SOLAR_POWER);
game.override.weather(WeatherType.SUNNY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;

View File

@ -37,8 +37,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Player side + single battle Intimidate - opponent loses stats", async () => { it("Player side + single battle Intimidate - opponent loses stats", async () => {
game.override.ability(AbilityId.MIRROR_ARMOR); game.override.ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
game.override.enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -54,8 +53,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Enemy side + single battle Intimidate - player loses stats", async () => { it("Enemy side + single battle Intimidate - player loses stats", async () => {
game.override.enemyAbility(AbilityId.MIRROR_ARMOR); game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
game.override.ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -71,9 +69,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Player side + double battle Intimidate - opponents each lose -2 atk", async () => { it("Player side + double battle Intimidate - opponents each lose -2 atk", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]);
const [enemy1, enemy2] = game.scene.getEnemyField(); const [enemy1, enemy2] = game.scene.getEnemyField();
@ -93,9 +89,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Enemy side + double battle Intimidate - players each lose -2 atk", async () => { it("Enemy side + double battle Intimidate - players each lose -2 atk", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
game.override.enemyAbility(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]);
const [enemy1, enemy2] = game.scene.getEnemyField(); const [enemy1, enemy2] = game.scene.getEnemyField();
@ -115,8 +109,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Player side + single battle Intimidate + Tickle - opponent loses stats", async () => { it("Player side + single battle Intimidate + Tickle - opponent loses stats", async () => {
game.override.ability(AbilityId.MIRROR_ARMOR); game.override.ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
game.override.enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -134,9 +127,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Player side + double battle Intimidate + Tickle - opponents each lose -3 atk, -1 def", async () => { it("Player side + double battle Intimidate + Tickle - opponents each lose -3 atk, -1 def", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]);
const [enemy1, enemy2] = game.scene.getEnemyField(); const [enemy1, enemy2] = game.scene.getEnemyField();
@ -159,8 +150,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Enemy side + single battle Intimidate + Tickle - player loses stats", async () => { it("Enemy side + single battle Intimidate + Tickle - player loses stats", async () => {
game.override.enemyAbility(AbilityId.MIRROR_ARMOR); game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
game.override.ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -178,8 +168,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Player side + single battle Intimidate + oppoenent has white smoke - no one loses stats", async () => { it("Player side + single battle Intimidate + oppoenent has white smoke - no one loses stats", async () => {
game.override.enemyAbility(AbilityId.WHITE_SMOKE); game.override.enemyAbility(AbilityId.WHITE_SMOKE).ability(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.MIRROR_ARMOR);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -197,8 +186,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Enemy side + single battle Intimidate + player has white smoke - no one loses stats", async () => { it("Enemy side + single battle Intimidate + player has white smoke - no one loses stats", async () => {
game.override.ability(AbilityId.WHITE_SMOKE); game.override.ability(AbilityId.WHITE_SMOKE).enemyAbility(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.MIRROR_ARMOR);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -252,9 +240,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Both sides have mirror armor - does not loop, player loses attack", async () => { it("Both sides have mirror armor - does not loop, player loses attack", async () => {
game.override.enemyAbility(AbilityId.MIRROR_ARMOR); game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]); await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -288,8 +274,7 @@ describe("Ability - Mirror Armor", () => {
}); });
it("Double battle + sticky web applied player side - player switches out and enemy 1 should lose -1 speed", async () => { it("Double battle + sticky web applied player side - player switches out and enemy 1 should lose -1 speed", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.MIRROR_ARMOR);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER, SpeciesId.SQUIRTLE]); await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER, SpeciesId.SQUIRTLE]);
const [enemy1, enemy2] = game.scene.getEnemyField(); const [enemy1, enemy2] = game.scene.getEnemyField();

View File

@ -27,13 +27,14 @@ describe("Abilities - Moxie", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
const moveToUse = MoveId.AERIAL_ACE; const moveToUse = MoveId.AERIAL_ACE;
game.override.battleStyle("single"); game.override
game.override.enemySpecies(SpeciesId.RATTATA); .battleStyle("single")
game.override.enemyAbility(AbilityId.MOXIE); .enemySpecies(SpeciesId.RATTATA)
game.override.ability(AbilityId.MOXIE); .enemyAbility(AbilityId.MOXIE)
game.override.startingLevel(2000); .ability(AbilityId.MOXIE)
game.override.moveset([moveToUse]); .startingLevel(2000)
game.override.enemyMoveset(MoveId.SPLASH); .moveset([moveToUse])
.enemyMoveset(MoveId.SPLASH);
}); });
it("should raise ATK stat stage by 1 when winning a battle", async () => { it("should raise ATK stat stage by 1 when winning a battle", async () => {
@ -48,7 +49,7 @@ describe("Abilities - Moxie", () => {
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase); await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000); });
// TODO: Activate this test when MOXIE is corrected to work on faint and not on battle victory // TODO: Activate this test when MOXIE is corrected to work on faint and not on battle victory
it.todo( it.todo(

View File

@ -63,7 +63,7 @@ describe("Abilities - Mycelium Might", () => {
// Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced. // Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced.
expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1);
}, 20000); });
it("will still go first if a status move that is in a higher priority bracket than the opponent's move is used", async () => { it("will still go first if a status move that is in a higher priority bracket than the opponent's move is used", async () => {
game.override.enemyMoveset(MoveId.TACKLE); game.override.enemyMoveset(MoveId.TACKLE);
@ -86,7 +86,7 @@ describe("Abilities - Mycelium Might", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
// Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced. // Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced.
expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1);
}, 20000); });
it("will not affect non-status moves", async () => { it("will not affect non-status moves", async () => {
await game.classicMode.startBattle([SpeciesId.REGIELEKI]); await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
@ -105,5 +105,5 @@ describe("Abilities - Mycelium Might", () => {
// This means that the commandOrder should be identical to the speedOrder // This means that the commandOrder should be identical to the speedOrder
expect(speedOrder).toEqual([playerIndex, enemyIndex]); expect(speedOrder).toEqual([playerIndex, enemyIndex]);
expect(commandOrder).toEqual([playerIndex, enemyIndex]); expect(commandOrder).toEqual([playerIndex, enemyIndex]);
}, 20000); });
}); });

View File

@ -45,8 +45,9 @@ describe("Abilities - Normalize", () => {
}); });
it("should not apply the old type boost item after changing a move's type", async () => { it("should not apply the old type boost item after changing a move's type", async () => {
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }]); game.override
game.override.moveset([MoveId.LEAFAGE]); .startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }])
.moveset([MoveId.LEAFAGE]);
const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower"); const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower");
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -58,8 +59,9 @@ describe("Abilities - Normalize", () => {
}); });
it("should apply silk scarf's power boost after changing a move's type", async () => { it("should apply silk scarf's power boost after changing a move's type", async () => {
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]); game.override
game.override.moveset([MoveId.LEAFAGE]); .startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }])
.moveset([MoveId.LEAFAGE]);
const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower"); const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower");
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -26,14 +26,15 @@ describe("Abilities - Parental Bond", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
game.override.disableCrits(); .battleStyle("single")
game.override.ability(AbilityId.PARENTAL_BOND); .disableCrits()
game.override.enemySpecies(SpeciesId.SNORLAX); .ability(AbilityId.PARENTAL_BOND)
game.override.enemyAbility(AbilityId.FUR_COAT); .enemySpecies(SpeciesId.SNORLAX)
game.override.enemyMoveset(MoveId.SPLASH); .enemyAbility(AbilityId.FUR_COAT)
game.override.startingLevel(100); .enemyMoveset(MoveId.SPLASH)
game.override.enemyLevel(100); .startingLevel(100)
.enemyLevel(100);
}); });
it("should add second strike to attack move", async () => { it("should add second strike to attack move", async () => {
@ -61,8 +62,7 @@ describe("Abilities - Parental Bond", () => {
}); });
it("should apply secondary effects to both strikes", async () => { it("should apply secondary effects to both strikes", async () => {
game.override.moveset([MoveId.POWER_UP_PUNCH]); game.override.moveset([MoveId.POWER_UP_PUNCH]).enemySpecies(SpeciesId.AMOONGUSS);
game.override.enemySpecies(SpeciesId.AMOONGUSS);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -148,8 +148,7 @@ describe("Abilities - Parental Bond", () => {
}); });
it("should not apply multiplier to counter moves", async () => { it("should not apply multiplier to counter moves", async () => {
game.override.moveset([MoveId.COUNTER]); game.override.moveset([MoveId.COUNTER]).enemyMoveset([MoveId.TACKLE]);
game.override.enemyMoveset([MoveId.TACKLE]);
await game.classicMode.startBattle([SpeciesId.SHUCKLE]); await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
@ -167,9 +166,7 @@ describe("Abilities - Parental Bond", () => {
}); });
it("should not apply to multi-target moves", async () => { it("should not apply to multi-target moves", async () => {
game.override.battleStyle("double"); game.override.battleStyle("double").moveset([MoveId.EARTHQUAKE]).passiveAbility(AbilityId.LEVITATE);
game.override.moveset([MoveId.EARTHQUAKE]);
game.override.passiveAbility(AbilityId.LEVITATE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]);
@ -237,8 +234,7 @@ describe("Abilities - Parental Bond", () => {
}); });
it("Moves boosted by this ability and Multi-Lens should strike 3 times", async () => { it("Moves boosted by this ability and Multi-Lens should strike 3 times", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -252,8 +248,7 @@ describe("Abilities - Parental Bond", () => {
}); });
it("Seismic Toss boosted by this ability and Multi-Lens should strike 3 times", async () => { it("Seismic Toss boosted by this ability and Multi-Lens should strike 3 times", async () => {
game.override.moveset([MoveId.SEISMIC_TOSS]); game.override.moveset([MoveId.SEISMIC_TOSS]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -378,8 +373,7 @@ describe("Abilities - Parental Bond", () => {
}); });
it("should not cause user to hit into King's Shield more than once", async () => { it("should not cause user to hit into King's Shield more than once", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).enemyMoveset([MoveId.KINGS_SHIELD]);
game.override.enemyMoveset([MoveId.KINGS_SHIELD]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -393,8 +387,7 @@ describe("Abilities - Parental Bond", () => {
}); });
it("should not cause user to hit into Storm Drain more than once", async () => { it("should not cause user to hit into Storm Drain more than once", async () => {
game.override.moveset([MoveId.WATER_GUN]); game.override.moveset([MoveId.WATER_GUN]).enemyAbility(AbilityId.STORM_DRAIN);
game.override.enemyAbility(AbilityId.STORM_DRAIN);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -21,19 +21,18 @@ describe("Abilities - Perish Song", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
game.override.disableCrits(); .battleStyle("single")
.disableCrits()
game.override.enemySpecies(SpeciesId.MAGIKARP); .enemySpecies(SpeciesId.MAGIKARP)
game.override.enemyAbility(AbilityId.BALL_FETCH); .enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.CURSOLA)
game.override.starterSpecies(SpeciesId.CURSOLA); .ability(AbilityId.PERISH_BODY)
game.override.ability(AbilityId.PERISH_BODY); .moveset([MoveId.SPLASH])
game.override.moveset([MoveId.SPLASH]); .enemyMoveset([MoveId.AQUA_JET]);
}); });
it("should trigger when hit with damaging move", async () => { it("should trigger when hit with damaging move", async () => {
game.override.enemyMoveset([MoveId.AQUA_JET]);
await game.classicMode.startBattle(); await game.classicMode.startBattle();
const cursola = game.scene.getPlayerPokemon(); const cursola = game.scene.getPlayerPokemon();
const magikarp = game.scene.getEnemyPokemon(); const magikarp = game.scene.getEnemyPokemon();
@ -46,7 +45,7 @@ describe("Abilities - Perish Song", () => {
}); });
it("should trigger even when fainting", async () => { it("should trigger even when fainting", async () => {
game.override.enemyMoveset([MoveId.AQUA_JET]).enemyLevel(100).startingLevel(1); game.override.enemyLevel(100).startingLevel(1);
await game.classicMode.startBattle([SpeciesId.CURSOLA, SpeciesId.FEEBAS]); await game.classicMode.startBattle([SpeciesId.CURSOLA, SpeciesId.FEEBAS]);
const magikarp = game.scene.getEnemyPokemon(); const magikarp = game.scene.getEnemyPokemon();
@ -87,9 +86,10 @@ describe("Abilities - Perish Song", () => {
}); });
it("should activate if cursola already has perish song, but not reset its counter", async () => { it("should activate if cursola already has perish song, but not reset its counter", async () => {
game.override.enemyMoveset([MoveId.PERISH_SONG, MoveId.AQUA_JET, MoveId.SPLASH]); game.override
game.override.moveset([MoveId.WHIRLWIND, MoveId.SPLASH]); .enemyMoveset([MoveId.PERISH_SONG, MoveId.AQUA_JET, MoveId.SPLASH])
game.override.startingWave(5); .moveset([MoveId.WHIRLWIND, MoveId.SPLASH])
.startingWave(5);
await game.classicMode.startBattle([SpeciesId.CURSOLA]); await game.classicMode.startBattle([SpeciesId.CURSOLA]);
const cursola = game.scene.getPlayerPokemon(); const cursola = game.scene.getPlayerPokemon();

View File

@ -35,8 +35,7 @@ describe("Abilities - POWER CONSTRUCT", () => {
test("check if fainted 50% Power Construct Pokemon switches to base form on arena reset", async () => { test("check if fainted 50% Power Construct Pokemon switches to base form on arena reset", async () => {
const baseForm = 2, const baseForm = 2,
completeForm = 4; completeForm = 4;
game.override.startingWave(4); game.override.startingWave(4).starterForms({
game.override.starterForms({
[SpeciesId.ZYGARDE]: completeForm, [SpeciesId.ZYGARDE]: completeForm,
}); });
@ -62,8 +61,7 @@ describe("Abilities - POWER CONSTRUCT", () => {
test("check if fainted 10% Power Construct Pokemon switches to base form on arena reset", async () => { test("check if fainted 10% Power Construct Pokemon switches to base form on arena reset", async () => {
const baseForm = 3, const baseForm = 3,
completeForm = 5; completeForm = 5;
game.override.startingWave(4); game.override.startingWave(4).starterForms({
game.override.starterForms({
[SpeciesId.ZYGARDE]: completeForm, [SpeciesId.ZYGARDE]: completeForm,
}); });

View File

@ -26,11 +26,12 @@ describe("Abilities - Power Spot", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("double"); game.override
game.override.moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]); .battleStyle("double")
game.override.enemyMoveset(MoveId.SPLASH); .moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM])
game.override.enemySpecies(SpeciesId.SHUCKLE); .enemyMoveset(MoveId.SPLASH)
game.override.enemyAbility(AbilityId.BALL_FETCH); .enemySpecies(SpeciesId.SHUCKLE)
.enemyAbility(AbilityId.BALL_FETCH);
}); });
it("raises the power of allies' special moves by 30%", async () => { it("raises the power of allies' special moves by 30%", async () => {

View File

@ -108,8 +108,7 @@ describe("Abilities - Protean", () => {
}); });
test("ability applies correctly even if the type has changed by another ability", async () => { test("ability applies correctly even if the type has changed by another ability", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).passiveAbility(AbilityId.REFRIGERATE);
game.override.passiveAbility(AbilityId.REFRIGERATE);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -156,8 +155,7 @@ describe("Abilities - Protean", () => {
}); });
test("ability applies correctly even if the pokemon's move misses", async () => { test("ability applies correctly even if the pokemon's move misses", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).enemyMoveset(MoveId.SPLASH);
game.override.enemyMoveset(MoveId.SPLASH);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -188,8 +186,7 @@ describe("Abilities - Protean", () => {
}); });
test("ability applies correctly even if the pokemon's move fails because of type immunity", async () => { test("ability applies correctly even if the pokemon's move fails because of type immunity", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]).enemySpecies(SpeciesId.GASTLY);
game.override.enemySpecies(SpeciesId.GASTLY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -262,8 +259,7 @@ describe("Abilities - Protean", () => {
}); });
test("ability applies correctly even if the pokemon's Trick-or-Treat fails", async () => { test("ability applies correctly even if the pokemon's Trick-or-Treat fails", async () => {
game.override.moveset([MoveId.TRICK_OR_TREAT]); game.override.moveset([MoveId.TRICK_OR_TREAT]).enemySpecies(SpeciesId.GASTLY);
game.override.enemySpecies(SpeciesId.GASTLY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]); await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -23,16 +23,15 @@ describe("Abilities - Quick Draw", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
.battleStyle("single")
game.override.starterSpecies(SpeciesId.MAGIKARP); .starterSpecies(SpeciesId.MAGIKARP)
game.override.ability(AbilityId.QUICK_DRAW); .ability(AbilityId.QUICK_DRAW)
game.override.moveset([MoveId.TACKLE, MoveId.TAIL_WHIP]); .moveset([MoveId.TACKLE, MoveId.TAIL_WHIP])
.enemyLevel(100)
game.override.enemyLevel(100); .enemySpecies(SpeciesId.MAGIKARP)
game.override.enemySpecies(SpeciesId.MAGIKARP); .enemyAbility(AbilityId.BALL_FETCH)
game.override.enemyAbility(AbilityId.BALL_FETCH); .enemyMoveset([MoveId.TACKLE]);
game.override.enemyMoveset([MoveId.TACKLE]);
vi.spyOn( vi.spyOn(
allAbilities[AbilityId.QUICK_DRAW].getAttrs("BypassSpeedChanceAbAttr")[0], allAbilities[AbilityId.QUICK_DRAW].getAttrs("BypassSpeedChanceAbAttr")[0],
@ -55,8 +54,8 @@ describe("Abilities - Quick Draw", () => {
expect(pokemon.isFainted()).toBe(false); expect(pokemon.isFainted()).toBe(false);
expect(enemy.isFainted()).toBe(true); expect(enemy.isFainted()).toBe(true);
expect(pokemon.waveData.abilitiesApplied).contain(AbilityId.QUICK_DRAW); expect(pokemon.waveData.abilitiesApplied).toContain(AbilityId.QUICK_DRAW);
}, 20000); });
test( test(
"does not triggered by non damage moves", "does not triggered by non damage moves",
@ -97,6 +96,6 @@ describe("Abilities - Quick Draw", () => {
expect(pokemon.isFainted()).toBe(true); expect(pokemon.isFainted()).toBe(true);
expect(enemy.isFainted()).toBe(false); expect(enemy.isFainted()).toBe(false);
expect(pokemon.waveData.abilitiesApplied).contain(AbilityId.QUICK_DRAW); expect(pokemon.waveData.abilitiesApplied).toContain(AbilityId.QUICK_DRAW);
}, 20000); });
}); });

View File

@ -22,15 +22,14 @@ describe("Abilities - Sand Spit", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
game.override.disableCrits(); .battleStyle("single")
.disableCrits()
game.override.enemySpecies(SpeciesId.MAGIKARP); .enemySpecies(SpeciesId.MAGIKARP)
game.override.enemyAbility(AbilityId.BALL_FETCH); .enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.SILICOBRA)
game.override.starterSpecies(SpeciesId.SILICOBRA); .ability(AbilityId.SAND_SPIT)
game.override.ability(AbilityId.SAND_SPIT); .moveset([MoveId.SPLASH, MoveId.COIL]);
game.override.moveset([MoveId.SPLASH, MoveId.COIL]);
}); });
it("should trigger when hit with damaging move", async () => { it("should trigger when hit with damaging move", async () => {
@ -41,7 +40,7 @@ describe("Abilities - Sand Spit", () => {
await game.toNextTurn(); await game.toNextTurn();
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SANDSTORM); expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SANDSTORM);
}, 20000); });
it("should trigger even when fainting", async () => { it("should trigger even when fainting", async () => {
game.override.enemyMoveset([MoveId.TACKLE]).enemyLevel(100).startingLevel(1); game.override.enemyMoveset([MoveId.TACKLE]).enemyLevel(100).startingLevel(1);
@ -62,5 +61,5 @@ describe("Abilities - Sand Spit", () => {
await game.toNextTurn(); await game.toNextTurn();
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.SANDSTORM); expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.SANDSTORM);
}, 20000); });
}); });

View File

@ -31,8 +31,7 @@ describe("Abilities - SCHOOLING", () => {
test("check if fainted pokemon switches to base form on arena reset", async () => { test("check if fainted pokemon switches to base form on arena reset", async () => {
const soloForm = 0, const soloForm = 0,
schoolForm = 1; schoolForm = 1;
game.override.startingWave(4); game.override.startingWave(4).starterForms({
game.override.starterForms({
[SpeciesId.WISHIWASHI]: schoolForm, [SpeciesId.WISHIWASHI]: schoolForm,
}); });

View File

@ -24,9 +24,7 @@ describe("Abilities - Screen Cleaner", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override.battleStyle("single").ability(AbilityId.SCREEN_CLEANER).enemySpecies(SpeciesId.SHUCKLE);
game.override.ability(AbilityId.SCREEN_CLEANER);
game.override.enemySpecies(SpeciesId.SHUCKLE);
}); });
it("removes Aurora Veil", async () => { it("removes Aurora Veil", async () => {

View File

@ -22,15 +22,14 @@ describe("Abilities - Seed Sower", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
game.override.disableCrits(); .battleStyle("single")
.disableCrits()
game.override.enemySpecies(SpeciesId.MAGIKARP); .enemySpecies(SpeciesId.MAGIKARP)
game.override.enemyAbility(AbilityId.BALL_FETCH); .enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.ARBOLIVA)
game.override.starterSpecies(SpeciesId.ARBOLIVA); .ability(AbilityId.SEED_SOWER)
game.override.ability(AbilityId.SEED_SOWER); .moveset([MoveId.SPLASH]);
game.override.moveset([MoveId.SPLASH]);
}); });
it("should trigger when hit with damaging move", async () => { it("should trigger when hit with damaging move", async () => {

View File

@ -69,7 +69,7 @@ describe("Abilities - Sheer Force", () => {
await game.phaseInterceptor.to("BerryPhase", false); await game.phaseInterceptor.to("BerryPhase", false);
expect(bindMove.calculateBattlePower).toHaveLastReturnedWith(bindMove.power); expect(bindMove.calculateBattlePower).toHaveLastReturnedWith(bindMove.power);
}, 20000); });
it("Sheer Force does not boost the base damage of moves with no secondary effect", async () => { it("Sheer Force does not boost the base damage of moves with no secondary effect", async () => {
game.override.moveset([MoveId.TACKLE]); game.override.moveset([MoveId.TACKLE]);

View File

@ -26,12 +26,13 @@ describe("Abilities - Shield Dust", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("single"); game.override
game.override.enemySpecies(SpeciesId.ONIX); .battleStyle("single")
game.override.enemyAbility(AbilityId.SHIELD_DUST); .enemySpecies(SpeciesId.ONIX)
game.override.startingLevel(100); .enemyAbility(AbilityId.SHIELD_DUST)
game.override.moveset(MoveId.AIR_SLASH); .startingLevel(100)
game.override.enemyMoveset(MoveId.TACKLE); .moveset(MoveId.AIR_SLASH)
.enemyMoveset(MoveId.TACKLE);
}); });
it("Shield Dust", async () => { it("Shield Dust", async () => {

View File

@ -26,17 +26,17 @@ describe("Abilities - SHIELDS DOWN", () => {
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
const moveToUse = MoveId.SPLASH; const moveToUse = MoveId.SPLASH;
game.override.battleStyle("single"); game.override
game.override.ability(AbilityId.SHIELDS_DOWN); .battleStyle("single")
game.override.moveset([moveToUse]); .ability(AbilityId.SHIELDS_DOWN)
game.override.enemyMoveset([MoveId.TACKLE]); .moveset([moveToUse])
.enemyMoveset([MoveId.TACKLE]);
}); });
test("check if fainted pokemon switched to base form on arena reset", async () => { test("check if fainted pokemon switched to base form on arena reset", async () => {
const meteorForm = 0, const meteorForm = 0,
coreForm = 7; coreForm = 7;
game.override.startingWave(4); game.override.startingWave(4).starterForms({
game.override.starterForms({
[SpeciesId.MINIOR]: coreForm, [SpeciesId.MINIOR]: coreForm,
}); });
@ -70,8 +70,7 @@ describe("Abilities - SHIELDS DOWN", () => {
}); });
test("should still ignore non-volatile status moves used by a pokemon with mold breaker", async () => { test("should still ignore non-volatile status moves used by a pokemon with mold breaker", async () => {
game.override.enemyAbility(AbilityId.MOLD_BREAKER); game.override.enemyAbility(AbilityId.MOLD_BREAKER).enemyMoveset([MoveId.SPORE]);
game.override.enemyMoveset([MoveId.SPORE]);
await game.classicMode.startBattle([SpeciesId.MINIOR]); await game.classicMode.startBattle([SpeciesId.MINIOR]);
@ -94,8 +93,7 @@ describe("Abilities - SHIELDS DOWN", () => {
}); });
test("should ignore status moves even through mold breaker", async () => { test("should ignore status moves even through mold breaker", async () => {
game.override.enemyMoveset([MoveId.SPORE]); game.override.enemyMoveset([MoveId.SPORE]).enemyAbility(AbilityId.MOLD_BREAKER);
game.override.enemyAbility(AbilityId.MOLD_BREAKER);
await game.classicMode.startBattle([SpeciesId.MINIOR]); await game.classicMode.startBattle([SpeciesId.MINIOR]);
@ -108,8 +106,9 @@ describe("Abilities - SHIELDS DOWN", () => {
// toxic spikes currently does not poison flying types when gravity is in effect // toxic spikes currently does not poison flying types when gravity is in effect
test.todo("should become poisoned by toxic spikes when grounded", async () => { test.todo("should become poisoned by toxic spikes when grounded", async () => {
game.override.enemyMoveset([MoveId.GRAVITY, MoveId.TOXIC_SPIKES, MoveId.SPLASH]); game.override
game.override.moveset([MoveId.GRAVITY, MoveId.SPLASH]); .enemyMoveset([MoveId.GRAVITY, MoveId.TOXIC_SPIKES, MoveId.SPLASH])
.moveset([MoveId.GRAVITY, MoveId.SPLASH]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MINIOR]); await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MINIOR]);
@ -155,9 +154,7 @@ describe("Abilities - SHIELDS DOWN", () => {
// the `NoTransformAbilityAbAttr` attribute is not checked anywhere, so this test cannot pass. // the `NoTransformAbilityAbAttr` attribute is not checked anywhere, so this test cannot pass.
test.todo("ditto should not be immune to status after transforming", async () => { test.todo("ditto should not be immune to status after transforming", async () => {
game.override.enemySpecies(SpeciesId.DITTO); game.override.enemySpecies(SpeciesId.DITTO).enemyAbility(AbilityId.IMPOSTER).moveset([MoveId.SPLASH, MoveId.SPORE]);
game.override.enemyAbility(AbilityId.IMPOSTER);
game.override.moveset([MoveId.SPLASH, MoveId.SPORE]);
await game.classicMode.startBattle([SpeciesId.MINIOR]); await game.classicMode.startBattle([SpeciesId.MINIOR]);
@ -169,11 +166,12 @@ describe("Abilities - SHIELDS DOWN", () => {
}); });
test("should not prevent minior from receiving the fainted status effect in trainer battles", async () => { test("should not prevent minior from receiving the fainted status effect in trainer battles", async () => {
game.override.enemyMoveset([MoveId.TACKLE]); game.override
game.override.moveset([MoveId.THUNDERBOLT]); .enemyMoveset([MoveId.TACKLE])
game.override.startingLevel(100); .moveset([MoveId.THUNDERBOLT])
game.override.startingWave(5); .startingLevel(100)
game.override.enemySpecies(SpeciesId.MINIOR); .startingWave(5)
.enemySpecies(SpeciesId.MINIOR);
await game.classicMode.startBattle([SpeciesId.REGIELEKI]); await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
const minior = game.scene.getEnemyPokemon()!; const minior = game.scene.getEnemyPokemon()!;

View File

@ -36,5 +36,5 @@ describe("Abilities - Simple", () => {
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-2);
}, 20000); });
}); });

View File

@ -53,7 +53,7 @@ describe("Abilities - Stall", () => {
// The opponent Pokemon (with Stall) goes last despite having higher speed than the player Pokemon. // The opponent Pokemon (with Stall) goes last despite having higher speed than the player Pokemon.
expect(speedOrder).toEqual([enemyIndex, playerIndex]); expect(speedOrder).toEqual([enemyIndex, playerIndex]);
expect(commandOrder).toEqual([playerIndex, enemyIndex]); expect(commandOrder).toEqual([playerIndex, enemyIndex]);
}, 20000); });
it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async () => { it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async () => {
await game.classicMode.startBattle([SpeciesId.SHUCKLE]); await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
@ -71,7 +71,7 @@ describe("Abilities - Stall", () => {
// The player Pokemon goes second because its move is in a lower priority bracket. // The player Pokemon goes second because its move is in a lower priority bracket.
expect(speedOrder).toEqual([enemyIndex, playerIndex]); expect(speedOrder).toEqual([enemyIndex, playerIndex]);
expect(commandOrder).toEqual([enemyIndex, playerIndex]); expect(commandOrder).toEqual([enemyIndex, playerIndex]);
}, 20000); });
it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async () => { it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async () => {
game.override.ability(AbilityId.STALL); game.override.ability(AbilityId.STALL);
@ -91,5 +91,5 @@ describe("Abilities - Stall", () => {
// The player Pokemon (with Stall) goes second because its speed is lower. // The player Pokemon (with Stall) goes second because its speed is lower.
expect(speedOrder).toEqual([enemyIndex, playerIndex]); expect(speedOrder).toEqual([enemyIndex, playerIndex]);
expect(commandOrder).toEqual([enemyIndex, playerIndex]); expect(commandOrder).toEqual([enemyIndex, playerIndex]);
}, 20000); });
}); });

View File

@ -28,11 +28,12 @@ describe("Abilities - Steely Spirit", () => {
beforeEach(() => { beforeEach(() => {
ironHeadPower = allMoves[moveToCheck].power; ironHeadPower = allMoves[moveToCheck].power;
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleStyle("double"); game.override
game.override.enemySpecies(SpeciesId.SHUCKLE); .battleStyle("double")
game.override.enemyAbility(AbilityId.BALL_FETCH); .enemySpecies(SpeciesId.SHUCKLE)
game.override.moveset([MoveId.IRON_HEAD, MoveId.SPLASH]); .enemyAbility(AbilityId.BALL_FETCH)
game.override.enemyMoveset(MoveId.SPLASH); .moveset([MoveId.IRON_HEAD, MoveId.SPLASH])
.enemyMoveset(MoveId.SPLASH);
vi.spyOn(allMoves[moveToCheck], "calculateBattlePower"); vi.spyOn(allMoves[moveToCheck], "calculateBattlePower");
}); });

Some files were not shown because too many files have changed in this diff Show More