Merge branch 'beta' into tsconfig

This commit is contained in:
NightKev 2025-06-15 00:52:47 -07:00 committed by GitHub
commit b5f40a3e0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
231 changed files with 1399 additions and 1632 deletions

View File

@ -4,7 +4,7 @@ module.exports = {
{
name: "only-type-imports",
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: {
path: ["(^|/)src/@types", "(^|/)src/enums"],
},
@ -14,7 +14,7 @@ module.exports = {
},
{
name: "no-circular-at-runtime",
severity: "warn",
severity: "error",
comment:
"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) ",
@ -34,7 +34,7 @@ module.exports = {
"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 " +
"files (.d.ts), tsconfig.json and some of the babel and webpack configs.",
severity: "warn",
severity: "error",
from: {
orphan: true,
pathNot: [
@ -42,8 +42,7 @@ module.exports = {
"[.]d[.]ts$", // TypeScript declaration files
"(^|/)tsconfig[.]json$", // TypeScript config
"(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs
// anything in src/@types
"(^|/)src/@types/",
"(^|/)test/.+[.]setup[.]ts", // Vitest setup files
],
},
to: {},
@ -53,7 +52,7 @@ module.exports = {
comment:
"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.",
severity: "warn",
severity: "error",
from: {},
to: {
dependencyTypes: ["core"],
@ -86,7 +85,7 @@ module.exports = {
comment:
"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.",
severity: "warn",
severity: "error",
from: {},
to: {
dependencyTypes: ["deprecated"],
@ -122,7 +121,7 @@ module.exports = {
"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 " +
"maintenance problems later on.",
severity: "warn",
severity: "error",
from: {},
to: {
moreThanOneDependencyType: true,
@ -133,7 +132,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",
@ -188,7 +187,7 @@ module.exports = {
"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 " +
"add an exception to your dependency-cruiser configuration.",
severity: "warn",
severity: "error",
from: {},
to: {
dependencyTypes: ["npm-peer"],
@ -196,6 +195,7 @@ module.exports = {
},
],
options: {
exclude: ["src/plugins/vite/*", "src/vite.env.d.ts"],
/* Which modules not to follow further when encountered */
doNotFollow: {
/* path: an array of regular expressions in strings to match against */
@ -218,7 +218,7 @@ module.exports = {
module systems it knows of. It's the default because it's the safe option
It might come at a performance penalty, though.
moduleSystems: ['amd', 'cjs', 'es6', 'tsd']
As in practice only commonjs ('cjs') and ecmascript modules ('es6')
are widely used, you can limit the moduleSystems to those.
*/
@ -226,7 +226,7 @@ module.exports = {
// moduleSystems: ['cjs', 'es6'],
/* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/'
to open it on your online repo or `vscode://file/${process.cwd()}/` to
to open it on your online repo or `vscode://file/${process.cwd()}/` to
open it in visual studio code),
*/
// prefix: `vscode://file/${process.cwd()}/`,
@ -235,7 +235,7 @@ module.exports = {
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
*/
// tsPreCompilationDeps: false,
tsPreCompilationDeps: true,
/* 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
@ -271,7 +271,7 @@ module.exports = {
to './webpack.conf.js'.
The (optional) `env` and `arguments` attributes contain the parameters
to be passed if your webpack config is a function and takes them (see
to be passed if your webpack config is a function and takes them (see
webpack documentation for details)
*/
// webpackConfig: {
@ -322,8 +322,8 @@ module.exports = {
A list of alias fields in package.jsons
See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and
the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields)
documentation
documentation
Defaults to an empty array (= don't use alias fields).
*/
// aliasFields: ["browser"],

2
.github/CODEOWNERS vendored
View File

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

View File

@ -2,25 +2,28 @@
<!-- Feel free to look at other PRs for examples -->
<!--
Make sure the title includes categorization (choose the one that best fits):
- [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges
- [Bug]: If the PR is primarily a bug fix
- [Move]: If a move has new or changed functionality
- [Ability]: If an ability has new or changed functionality
- [Item]: For new or modified items
- [Mystery]: For new or modified Mystery Encounters
- [Test]: If the PR is primarily adding or modifying tests
- [UI/UX]: If the PR is changing UI/UX elements
- [Audio]: If the PR is adding or changing music/sfx
- [Sprite]: If the PR is adding or changing sprites
- [Balance]: If the PR is related to game balance
- [Challenge]: If the PR is adding or modifying challenges
- [Refactor]: If the PR is primarily rewriting existing code
- [Docs]: If the PR is just 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
- [Dev]: If the PR is primarily changing something pertaining to development (lefthook hooks, linter rules, etc.)
- [i18n]: If the PR is primarily adding/changing locale keys or key usage (may come with an associated locales 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
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?
@ -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 tested the changes manually?
- [ ] 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 made sure that any UI change works for both UI themes (default and legacy)?
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?
- [ ] 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
- name: Deploy build on server
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 }}
ssh -t ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "~/prmanifest --inpath ${{ secrets.DESTINATION_DIR }} --outpath ${{ secrets.DESTINATION_DIR }}/manifest.json"
- 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": {
"noUndeclaredVariables": "off",
"noUnusedVariables": "error",
"noSwitchDeclarations": "warn", // TODO: refactor and make this an error
"noVoidTypeReturn": "warn", // TODO: Refactor and make this an error
"noSwitchDeclarations": "error",
"noVoidTypeReturn": "error",
"noUnusedImports": "error"
},
"style": {
@ -85,7 +85,7 @@
"useLiteralKeys": "off",
"noForEach": "off", // Foreach vs for of is not that simple.
"noUselessSwitchCase": "off", // Explicit > Implicit
"noUselessConstructor": "warn", // TODO: Refactor and make this an error
"noUselessConstructor": "error",
"noBannedTypes": "warn" // TODO: Refactor and make this an error
},
"nursery": {

2
global.d.ts vendored
View File

@ -7,7 +7,7 @@ declare global {
* Only used in testing.
* 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.
*
*
* To set up your own server in a test see `game_data.test.ts`
*/
var server: SetupServerApi;

View File

@ -18,9 +18,9 @@
"eslint": "eslint --fix .",
"eslint-ci": "eslint .",
"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",
"depcruise": "depcruise src",
"depcruise": "depcruise src test",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"postinstall": "npx lefthook install && npx lefthook run post-merge",
"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 = {
[tier: string]: WeightedModifierType[];
}
};

View File

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

View File

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

View File

@ -1,12 +1,18 @@
import { globalScene } from "#app/global-scene";
import { allMoves } from "./data-lists";
import { allMoves } from "#app/data/data-lists";
import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
import type Pokemon from "#app/field/pokemon";
import {
type nil,
getFrameMs,
getEnumKeys,
getEnumValues,
animationFileName,
coerceArray,
isNullOrUndefined,
} from "#app/utils/common";
import type { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id";
import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils/common";
import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims";
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
*/
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 encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) {
@ -845,7 +851,7 @@ export abstract class BattleAnim {
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 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 { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
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 { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id";
@ -49,7 +49,7 @@ export class BattlerTag {
isBatonPassable = false,
) {
this.tagType = tagType;
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [lapseType];
this.lapseTypes = coerceArray(lapseType);
this.turnCount = turnCount;
this.sourceMove = sourceMove!; // TODO: is this bang correct?
this.isBatonPassable = isBatonPassable;
@ -123,16 +123,6 @@ export interface TerrainBattlerTag {
* Players and enemies should not be allowed to select restricted moves.
*/
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 lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
@ -1468,16 +1458,6 @@ export class WrapTag 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 {
return i18next.t("battlerTags:vortexOnTrap", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),

View File

@ -2155,6 +2155,7 @@ export class PlantHealAttr extends WeatherHealAttr {
case WeatherType.SANDSTORM:
case WeatherType.HAIL:
case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN:
return 0.25;
default:
@ -4156,6 +4157,7 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr {
case WeatherType.SANDSTORM:
case WeatherType.HAIL:
case WeatherType.SNOW:
case WeatherType.FOG:
case WeatherType.HEAVY_RAIN:
power.value *= 0.5;
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 { ModifierTier } from "#enums/modifier-tier";
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import {
getPlayerModifierTypeOptions,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
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 { MusicPreference } from "#app/system/settings/settings";
import type { ModifierTypeOption } from "#app/modifier/modifier-type";
import {
getPlayerModifierTypeOptions,
regenerateModifierPoolThresholds,
} from "#app/modifier/modifier-type";
import { getPlayerModifierTypeOptions, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
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 { AttackTypeBoosterModifier } from "#app/modifier/modifier";
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 { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -272,7 +272,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
constructor(timeOfDay: TimeOfDay | TimeOfDay[]) {
super();
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay];
this.requiredTimeOfDay = coerceArray(timeOfDay);
}
override meetsRequirement(): boolean {
@ -294,7 +294,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
constructor(weather: WeatherType | WeatherType[]) {
super();
this.requiredWeather = Array.isArray(weather) ? weather : [weather];
this.requiredWeather = coerceArray(weather);
}
override meetsRequirement(): boolean {
@ -360,7 +360,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
constructor(heldItem: string | string[], minNumberOfItems = 1) {
super();
this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
this.requiredHeldItemModifiers = coerceArray(heldItem);
}
override meetsRequirement(): boolean {
@ -426,7 +426,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredSpecies = Array.isArray(species) ? species : [species];
this.requiredSpecies = coerceArray(species);
}
override meetsRequirement(): boolean {
@ -466,7 +466,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredNature = Array.isArray(nature) ? nature : [nature];
this.requiredNature = coerceArray(nature);
}
override meetsRequirement(): boolean {
@ -504,7 +504,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.excludeFainted = excludeFainted;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredType = Array.isArray(type) ? type : [type];
this.requiredType = coerceArray(type);
}
override meetsRequirement(): boolean {
@ -558,7 +558,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(moves) ? moves : [moves];
this.requiredMoves = coerceArray(moves);
}
override meetsRequirement(): boolean {
@ -609,7 +609,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove];
this.requiredMoves = coerceArray(learnableMove);
}
override meetsRequirement(): boolean {
@ -665,7 +665,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities];
this.requiredAbilities = coerceArray(abilities);
}
override meetsRequirement(): boolean {
@ -710,7 +710,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect];
this.requiredStatusEffect = coerceArray(statusEffect);
}
override meetsRequirement(): boolean {
@ -785,7 +785,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem];
this.requiredFormChangeItem = coerceArray(formChangeItem);
}
override meetsRequirement(): boolean {
@ -843,7 +843,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
this.requiredEvolutionItem = coerceArray(evolutionItems);
}
override meetsRequirement(): boolean {
@ -908,7 +908,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
this.requiredHeldItemModifiers = coerceArray(heldItem);
this.requireTransferable = requireTransferable;
}
@ -972,7 +972,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
this.requiredHeldItemTypes = coerceArray(heldItemTypes);
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 { PokemonMove } from "../moves/pokemon-move";
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 { MysteryEncounterSpriteConfig } 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(
...encounterAnimations: EncounterAnim[]
): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
const animations = coerceArray(encounterAnimations);
return Object.assign(this, { encounterAnimations: animations });
}
@ -729,7 +729,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedGameModes(
...disallowedGameModes: GameModes[]
): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
const gameModes = coerceArray(disallowedGameModes);
return Object.assign(this, { disallowedGameModes: gameModes });
}
@ -741,7 +741,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
withDisallowedChallenges(
...disallowedChallenges: Challenges[]
): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
const challenges = coerceArray(disallowedChallenges);
return Object.assign(this, { disallowedChallenges: challenges });
}

View File

@ -1,7 +1,7 @@
import type { MoveId } from "#enums/move-id";
import type { PlayerPokemon } from "#app/field/pokemon";
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 { globalScene } from "#app/global-scene";
@ -29,7 +29,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
constructor(requiredMoves: MoveId | MoveId[], options: CanLearnMoveRequirementOptions = {}) {
super();
this.requiredMoves = Array.isArray(requiredMoves) ? requiredMoves : [requiredMoves];
this.requiredMoves = coerceArray(requiredMoves);
this.excludeLevelMoves = options.excludeLevelMoves ?? 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 { PartyUiMode } from "#app/ui/party-ui-handler";
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 { BiomeId } from "#enums/biome-id";
import type { TrainerType } from "#enums/trainer-type";
@ -449,7 +449,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
* @param moves
*/
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));
}
@ -792,7 +792,7 @@ export function setEncounterRewards(
* @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) {
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
const participantIds = coerceArray(participantId);
globalScene.currentBattle.mysteryEncounter!.doEncounterExp = () => {
globalScene.phaseManager.unshiftNew("PartyExpPhase", baseExpValue, useWaveIndex, new Set(participantIds));

View File

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

View File

@ -1,7 +1,14 @@
import { globalScene } from "#app/global-scene";
import { modifierTypes } from "../data-lists";
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 { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms";
@ -554,10 +561,7 @@ export class TrainerConfig {
this.speciesPools = evilAdminTrainerPools[poolName];
signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
});
const nameForCall = this.name.toLowerCase().replace(/\s/g, "_");
@ -620,10 +624,7 @@ export class TrainerConfig {
this.setPartyTemplates(trainerPartyTemplates.RIVAL_5);
}
signatureSpecies.forEach((speciesPool, s) => {
if (!Array.isArray(speciesPool)) {
speciesPool = [speciesPool];
}
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
});
if (!isNullOrUndefined(specialtyType)) {
this.setSpeciesFilter(p => p.isOfType(specialtyType));
@ -668,12 +669,8 @@ export class TrainerConfig {
// Set up party members with their corresponding species.
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.
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(speciesPool));
this.setPartyMemberFunc(-(s + 1), getRandomPartyMemberFunc(coerceArray(speciesPool)));
});
// 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.
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.
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.

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

View File

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

View File

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

View File

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

View File

@ -272,7 +272,7 @@ class DefaultOverrides {
/**
* 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`.
*/
readonly BATTLE_TYPE_OVERRIDE: Exclude<BattleType, BattleType.CLEAR> | null = null;
@ -298,4 +298,4 @@ export type RandomTrainerOverride = {
}
/** 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 { CommandPhase } from "#app/phases/command-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 type { DynamicPhaseType } from "#enums/dynamic-phase-type";
import { EggHatchPhase } from "#app/phases/egg-hatch-phase";
@ -438,9 +438,7 @@ export class PhaseManager {
* @returns boolean if a targetPhase was found and added
*/
prependToPhase(phase: Phase | Phase[], targetPhase: PhaseString): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
phase = coerceArray(phase);
const target = PHASES[targetPhase];
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
*/
appendToPhase(phase: Phase | Phase[], targetPhase: PhaseString, condition?: (p: Phase) => boolean): boolean {
if (!Array.isArray(phase)) {
phase = [phase];
}
phase = coerceArray(phase);
const target = PHASES[targetPhase];
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 {
regenerateModifierPoolThresholds,
getEnemyBuffModifierForWave,
} from "#app/modifier/modifier-type";
import { regenerateModifierPoolThresholds, getEnemyBuffModifierForWave } from "#app/modifier/modifier-type";
import { ModifierPoolType } from "#enums/modifier-pool-type";
import { EnemyPersistentModifier } from "#app/modifier/modifier";
import { Phase } from "#app/phase";

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,8 @@ export class QuietFormChangePhase extends BattlePhase {
super.start();
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);

View File

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

View File

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

View File

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

View File

@ -174,7 +174,24 @@ export async function initI18n(): Promise<void> {
"es-MX": ["es-ES", "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: {
loadPath(lng: string, [ns]: string[]) {
let fileName: string;

View File

@ -41,9 +41,9 @@ export function minifyJsonPlugin(basePath: string | string[], recursive?: boolea
},
async closeBundle() {
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);
if (fs.existsSync(baseDir)) {
applyToDir(baseDir, recursive);

View File

@ -1,3 +1,5 @@
import { coerceArray } from "#app/utils/common";
export const legacyCompatibleImages: string[] = [];
export class SceneBase extends Phaser.Scene {
@ -88,9 +90,7 @@ export class SceneBase extends Phaser.Scene {
} else {
folder += "/";
}
if (!Array.isArray(filenames)) {
filenames = [filenames];
}
filenames = coerceArray(filenames);
for (const f of filenames as string[]) {
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 = {
[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_Down]: [`KEY ${Button.DOWN.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)",
handler: () => changeLocaleHandler("tr")
handler: () => changeLocaleHandler("tr"),
},
{
label: "Русский (Needs Help)",
@ -967,11 +967,11 @@ export function setSetting(setting: string, value: number): boolean {
},
{
label: "Dansk (Needs Help)",
handler: () => changeLocaleHandler("da")
handler: () => changeLocaleHandler("da"),
},
{
label: "Română (Needs Help)",
handler: () => changeLocaleHandler("ro")
handler: () => changeLocaleHandler("ro"),
},
{
label: i18next.t("settings:back"),

View File

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

View File

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

View File

@ -4,11 +4,8 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next";
import { Button } from "#enums/buttons";
import { globalScene } from "#app/global-scene";
import { ConfirmUiMode } from "#enums/confirm-ui-mode";
export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
private confirmUiMode: ConfirmUiMode;
public static readonly windowWidth: number = 48;
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.confirmUiMode = args.length >= 6 ? (args[5] as ConfirmUiMode) : ConfirmUiMode.DEFAULT_YES;
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;
}
this.setCursor(this.switchCheck ? this.switchCheckCursor : 0);
return true;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -126,6 +126,11 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler
);
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");
iconAction.setOrigin(0, -0.1);
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");
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;
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");
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;
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");
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;
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:
success = this.navigationContainer.navigate(button);
break;
case Button.ACTION:
case Button.ACTION: {
const setting: Setting = this.settings[cursor];
if (setting?.activatable) {
success = this.activateSetting(setting);
}
break;
}
}
}

View File

@ -98,7 +98,7 @@ export default class MoveTouchControlsHandler {
<div id="cancelButton" class="button">${i18next.t("settings:touchCancel")}</div>
</div>
<div class="info-row">
<div class="orientation-label">
<div class="orientation-label">
${i18next.t("settings:orientation")}
<span id="orientation">
${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) {
switch (button) {
case Button.ACTION:
case Button.ACTION: {
if (this.starterSpecies.length >= 6) {
error = true;
break;
@ -1815,6 +1815,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
});
break;
}
case Button.UP:
this.randomCursorObj.setVisible(false);
this.filterBarCursor = this.filterBar.numFilters - 1;

View File

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

View File

@ -611,3 +611,12 @@ export function getShinyDescriptor(variant: Variant): string {
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");
expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 }));
}, 5000);
});
});

View File

@ -26,11 +26,12 @@ describe("Abilities - Battery", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("double");
game.override.enemySpecies(SpeciesId.SHUCKLE);
game.override.enemyAbility(AbilityId.BALL_FETCH);
game.override.moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]);
game.override.enemyMoveset(MoveId.SPLASH);
game.override
.battleStyle("double")
.enemySpecies(SpeciesId.SHUCKLE)
.enemyAbility(AbilityId.BALL_FETCH)
.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 () => {

View File

@ -47,7 +47,7 @@ describe("Abilities - Beast Boost", () => {
await game.phaseInterceptor.to("VictoryPhase");
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 () => {
game.override.enemyMoveset([MoveId.GUARD_SPLIT]);
@ -66,7 +66,7 @@ describe("Abilities - Beast Boost", () => {
await game.phaseInterceptor.to("VictoryPhase");
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, 20000);
});
it("should have order preference in case of stat ties", async () => {
// Order preference follows the order of EFFECTIVE_STAT
@ -84,5 +84,5 @@ describe("Abilities - Beast Boost", () => {
await game.phaseInterceptor.to("VictoryPhase");
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, 20000);
});
});

View File

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

View File

@ -24,10 +24,11 @@ describe("Abilities - COSTAR", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("double");
game.override.ability(AbilityId.COSTAR);
game.override.moveset([MoveId.SPLASH, MoveId.NASTY_PLOT]);
game.override.enemyMoveset(MoveId.SPLASH);
game.override
.battleStyle("double")
.ability(AbilityId.COSTAR)
.moveset([MoveId.SPLASH, MoveId.NASTY_PLOT])
.enemyMoveset(MoveId.SPLASH);
});
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 () => {
game.override.moveset([MoveId.SURGING_STRIKES]);
game.override.enemyLevel(5);
game.override.moveset([MoveId.SURGING_STRIKES]).enemyLevel(5);
await game.classicMode.startBattle();
const mimikyu = game.scene.getEnemyPokemon()!;
@ -106,8 +105,7 @@ describe("Abilities - Disguise", () => {
});
it("persists form change when switched out", async () => {
game.override.enemyMoveset([MoveId.SHADOW_SNEAK]);
game.override.starterSpecies(0);
game.override.enemyMoveset([MoveId.SHADOW_SNEAK]).starterSpecies(0);
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 () => {
game.override.starterSpecies(0);
game.override.starterForms({
game.override.starterSpecies(0).starterForms({
[SpeciesId.MIMIKYU]: bustedForm,
});
await game.classicMode.startBattle([SpeciesId.FURRET, SpeciesId.MIMIKYU]);
@ -148,11 +145,12 @@ describe("Abilities - Disguise", () => {
});
it("reverts to Disguised form on arena reset", async () => {
game.override.startingWave(4);
game.override.starterSpecies(SpeciesId.MIMIKYU);
game.override.starterForms({
[SpeciesId.MIMIKYU]: bustedForm,
});
game.override
.startingWave(4)
.starterSpecies(SpeciesId.MIMIKYU)
.starterForms({
[SpeciesId.MIMIKYU]: bustedForm,
});
await game.classicMode.startBattle();
@ -168,11 +166,12 @@ describe("Abilities - Disguise", () => {
});
it("reverts to Disguised form on biome change when fainted", async () => {
game.override.startingWave(10);
game.override.starterSpecies(0);
game.override.starterForms({
[SpeciesId.MIMIKYU]: bustedForm,
});
game.override
.startingWave(10)
.starterSpecies(0)
.starterForms({
[SpeciesId.MIMIKYU]: bustedForm,
});
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 () => {
game.override.ability(AbilityId.AERILATE);
game.override.moveset([MoveId.TACKLE]);
game.override.ability(AbilityId.AERILATE).moveset([MoveId.TACKLE]);
await game.classicMode.startBattle();

View File

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

View File

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

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 () => {
game.override.battleStyle("double");
game.override.enemyMoveset([MoveId.STEALTH_ROCK, MoveId.HAZE]);
game.override.moveset([MoveId.SWORDS_DANCE, MoveId.SAFEGUARD]);
game.override
.battleStyle("double")
.enemyMoveset([MoveId.STEALTH_ROCK, MoveId.HAZE])
.moveset([MoveId.SWORDS_DANCE, MoveId.SAFEGUARD]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]);
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 () => {
game.override.battleStyle("single");
game.override.enemyMoveset([MoveId.SPIKES]);
game.override.battleStyle("single").enemyMoveset([MoveId.SPIKES]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
game.move.select(MoveId.SPLASH, 0);
@ -96,8 +96,7 @@ describe("Abilities - Good As Gold", () => {
});
it("should block the ally's helping hand", async () => {
game.override.battleStyle("double");
game.override.moveset([MoveId.HELPING_HAND, MoveId.TACKLE]);
game.override.battleStyle("double").moveset([MoveId.HELPING_HAND, MoveId.TACKLE]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.FEEBAS]);
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 () => {
game.override.battleStyle("single");
game.override.enemyMoveset([MoveId.RAIN_DANCE]);
game.override.battleStyle("single").enemyMoveset([MoveId.RAIN_DANCE]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
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 GameManager from "#test/testUtils/gameManager";
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 { allAbilities } from "#app/data/data-lists";
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", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let healerAttrSpy: MockInstance;
let healerAttr: PostTurnResetStatusAbAttr;
beforeAll(() => {
phaserGame = new Phaser.Game({
@ -24,7 +22,6 @@ describe("Abilities - Healer", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
healerAttrSpy.mockRestore();
});
beforeEach(() => {
@ -38,30 +35,28 @@ describe("Abilities - Healer", () => {
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH);
healerAttr = allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0];
healerAttrSpy = vi
.spyOn(healerAttr, "getCondition")
.mockReturnValue((pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly()));
// Mock healer to have a 100% chance of healing its ally
vi.spyOn(allAbilities[AbilityId.HEALER].getAttrs("PostTurnResetStatusAbAttr")[0], "getCondition").mockReturnValue(
(pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly()),
);
});
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]);
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
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]);
game.move.select(MoveId.SPLASH);
// faint the ally
game.move.select(MoveId.LUNAR_DANCE, 1);
const abSpy = vi.spyOn(healerAttr, "canApplyPostTurn");
await game.phaseInterceptor.to("TurnEndPhase");
// 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
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 () => {

View File

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

View File

@ -30,10 +30,11 @@ describe("Abilities - Ice Face", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("single");
game.override.enemySpecies(SpeciesId.EISCUE);
game.override.enemyAbility(AbilityId.ICE_FACE);
game.override.moveset([MoveId.TACKLE, MoveId.ICE_BEAM, MoveId.TOXIC_THREAD, MoveId.HAIL]);
game.override
.battleStyle("single")
.enemySpecies(SpeciesId.EISCUE)
.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 () => {
@ -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 () => {
game.override.moveset([MoveId.SURGING_STRIKES]);
game.override.enemyLevel(1);
game.override.moveset([MoveId.SURGING_STRIKES]).enemyLevel(1);
await game.classicMode.startBattle([SpeciesId.HITMONLEE]);
game.move.select(MoveId.SURGING_STRIKES);
@ -196,12 +196,13 @@ describe("Abilities - Ice Face", () => {
});
it("reverts to Ice Face on arena reset", async () => {
game.override.startingWave(4);
game.override.startingLevel(4);
game.override.enemySpecies(SpeciesId.MAGIKARP);
game.override.starterForms({
[SpeciesId.EISCUE]: noiceForm,
});
game.override
.startingWave(4)
.startingLevel(4)
.enemySpecies(SpeciesId.MAGIKARP)
.starterForms({
[SpeciesId.EISCUE]: noiceForm,
});
await game.classicMode.startBattle([SpeciesId.EISCUE]);

View File

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

View File

@ -62,7 +62,7 @@ describe("Abilities - Intimidate", () => {
expect(playerPokemon.species.speciesId).toBe(SpeciesId.POOCHYENA);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
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 () => {
game.override.battleStyle("double").startingWave(3);
@ -85,11 +85,10 @@ describe("Abilities - Intimidate", () => {
expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2);
expect(playerField[0].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 () => {
game.override.startingWave(2);
game.override.moveset([MoveId.SPLASH]);
game.override.startingWave(2).moveset([MoveId.SPLASH]);
await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
const playerPokemon = game.scene.getPlayerPokemon()!;
@ -103,7 +102,7 @@ describe("Abilities - Intimidate", () => {
expect(enemyPokemon.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 () => {
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(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
}, 200000);
});
});

View File

@ -22,10 +22,11 @@ describe("Abilities - Intrepid Sword", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("single");
game.override.enemySpecies(SpeciesId.ZACIAN);
game.override.enemyAbility(AbilityId.INTREPID_SWORD);
game.override.ability(AbilityId.INTREPID_SWORD);
game.override
.battleStyle("single")
.enemySpecies(SpeciesId.ZACIAN)
.enemyAbility(AbilityId.INTREPID_SWORD)
.ability(AbilityId.INTREPID_SWORD);
});
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(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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.passiveAbility(AbilityId.REFRIGERATE);
game.override.moveset([MoveId.TACKLE]).passiveAbility(AbilityId.REFRIGERATE);
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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.enemyMoveset(MoveId.SPLASH);
game.override.moveset([MoveId.TACKLE]).enemyMoveset(MoveId.SPLASH);
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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.enemySpecies(SpeciesId.GASTLY);
game.override.moveset([MoveId.TACKLE]).enemySpecies(SpeciesId.GASTLY);
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 () => {
game.override.moveset([MoveId.TRICK_OR_TREAT]);
game.override.enemySpecies(SpeciesId.GASTLY);
game.override.moveset([MoveId.TRICK_OR_TREAT]).enemySpecies(SpeciesId.GASTLY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

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

View File

@ -30,16 +30,16 @@ describe("Abilities - Magic Guard", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
/** Player Pokemon overrides */
game.override.ability(AbilityId.MAGIC_GUARD);
game.override.moveset([MoveId.SPLASH]);
game.override.startingLevel(100);
/** Enemy Pokemon overrides */
game.override.enemySpecies(SpeciesId.SNORLAX);
game.override.enemyAbility(AbilityId.INSOMNIA);
game.override.enemyMoveset(MoveId.SPLASH);
game.override.enemyLevel(100);
game.override
/** Player Pokemon overrides */
.ability(AbilityId.MAGIC_GUARD)
.moveset([MoveId.SPLASH])
.startingLevel(100)
/** Enemy Pokemon overrides */
.enemySpecies(SpeciesId.SNORLAX)
.enemyAbility(AbilityId.INSOMNIA)
.enemyMoveset(MoveId.SPLASH)
.enemyLevel(100);
});
//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 () => {
game.override.enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED]);
game.override.statusEffect(StatusEffect.POISON);
game.override
.enemyMoveset([MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED, MoveId.WORRY_SEED])
.statusEffect(StatusEffect.POISON);
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 () => {
game.override.enemyStatusEffect(StatusEffect.BURN);
game.override.enemyAbility(AbilityId.MAGIC_GUARD);
game.override.enemyStatusEffect(StatusEffect.BURN).enemyAbility(AbilityId.MAGIC_GUARD);
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 () => {
game.override.enemyStatusEffect(StatusEffect.TOXIC);
game.override.enemyAbility(AbilityId.MAGIC_GUARD);
game.override.enemyStatusEffect(StatusEffect.TOXIC).enemyAbility(AbilityId.MAGIC_GUARD);
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 () => {
await game.classicMode.startBattle([SpeciesId.DUSKULL]);
game.override.moveset([MoveId.CURSE]);
game.override.enemyAbility(AbilityId.MAGIC_GUARD);
game.override.moveset([MoveId.CURSE]).enemyAbility(AbilityId.MAGIC_GUARD);
const leadPokemon = game.scene.getPlayerPokemon()!;
@ -331,8 +329,9 @@ describe("Abilities - Magic Guard", () => {
//Tests the ability Bad Dreams
game.override.statusEffect(StatusEffect.SLEEP);
//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.enemyAbility(AbilityId.BAD_DREAMS);
game.override
.enemyMoveset([MoveId.SPORE, MoveId.SPORE, MoveId.SPORE, MoveId.SPORE])
.enemyAbility(AbilityId.BAD_DREAMS);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -353,8 +352,7 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard prevents damage from abilities with PostFaintContactDamageAbAttr", async () => {
//Tests the abilities Innards Out/Aftermath
game.override.moveset([MoveId.TACKLE]);
game.override.enemyAbility(AbilityId.AFTERMATH);
game.override.moveset([MoveId.TACKLE]).enemyAbility(AbilityId.AFTERMATH);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -377,8 +375,7 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard prevents damage from abilities with PostDefendContactDamageAbAttr", async () => {
//Tests the abilities Iron Barbs/Rough Skin
game.override.moveset([MoveId.TACKLE]);
game.override.enemyAbility(AbilityId.IRON_BARBS);
game.override.moveset([MoveId.TACKLE]).enemyAbility(AbilityId.IRON_BARBS);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -400,8 +397,7 @@ describe("Abilities - Magic Guard", () => {
it("Magic Guard prevents damage from abilities with ReverseDrainAbAttr", async () => {
//Tests the ability Liquid Ooze
game.override.moveset([MoveId.ABSORB]);
game.override.enemyAbility(AbilityId.LIQUID_OOZE);
game.override.moveset([MoveId.ABSORB]).enemyAbility(AbilityId.LIQUID_OOZE);
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 () => {
//Tests the abilities Solar Power/Dry Skin
game.override.passiveAbility(AbilityId.SOLAR_POWER);
game.override.weather(WeatherType.SUNNY);
game.override.passiveAbility(AbilityId.SOLAR_POWER).weather(WeatherType.SUNNY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
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 () => {
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.INTIMIDATE);
game.override.ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -54,8 +53,7 @@ describe("Ability - Mirror Armor", () => {
});
it("Enemy side + single battle Intimidate - player loses stats", async () => {
game.override.enemyAbility(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.INTIMIDATE);
game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
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 () => {
game.override.battleStyle("double");
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.INTIMIDATE);
game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]);
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 () => {
game.override.battleStyle("double");
game.override.enemyAbility(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.INTIMIDATE);
game.override.battleStyle("double").enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]);
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 () => {
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.INTIMIDATE);
game.override.ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
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 () => {
game.override.battleStyle("double");
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.INTIMIDATE);
game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR).enemyAbility(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER]);
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 () => {
game.override.enemyAbility(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.INTIMIDATE);
game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
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 () => {
game.override.enemyAbility(AbilityId.WHITE_SMOKE);
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.enemyAbility(AbilityId.WHITE_SMOKE).ability(AbilityId.MIRROR_ARMOR);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
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 () => {
game.override.ability(AbilityId.WHITE_SMOKE);
game.override.enemyAbility(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.WHITE_SMOKE).enemyAbility(AbilityId.MIRROR_ARMOR);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
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 () => {
game.override.enemyAbility(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.ability(AbilityId.INTIMIDATE);
game.override.enemyAbility(AbilityId.MIRROR_ARMOR).ability(AbilityId.MIRROR_ARMOR).ability(AbilityId.INTIMIDATE);
await game.classicMode.startBattle([SpeciesId.BULBASAUR]);
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 () => {
game.override.battleStyle("double");
game.override.ability(AbilityId.MIRROR_ARMOR);
game.override.battleStyle("double").ability(AbilityId.MIRROR_ARMOR);
await game.classicMode.startBattle([SpeciesId.BULBASAUR, SpeciesId.CHARMANDER, SpeciesId.SQUIRTLE]);
const [enemy1, enemy2] = game.scene.getEnemyField();

View File

@ -27,13 +27,14 @@ describe("Abilities - Moxie", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = MoveId.AERIAL_ACE;
game.override.battleStyle("single");
game.override.enemySpecies(SpeciesId.RATTATA);
game.override.enemyAbility(AbilityId.MOXIE);
game.override.ability(AbilityId.MOXIE);
game.override.startingLevel(2000);
game.override.moveset([moveToUse]);
game.override.enemyMoveset(MoveId.SPLASH);
game.override
.battleStyle("single")
.enemySpecies(SpeciesId.RATTATA)
.enemyAbility(AbilityId.MOXIE)
.ability(AbilityId.MOXIE)
.startingLevel(2000)
.moveset([moveToUse])
.enemyMoveset(MoveId.SPLASH);
});
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);
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
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.
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 () => {
game.override.enemyMoveset(MoveId.TACKLE);
@ -86,7 +86,7 @@ describe("Abilities - Mycelium Might", () => {
await game.phaseInterceptor.to(TurnEndPhase);
// Despite the opponent's ability (Clear Body), its ATK stat stage is still reduced.
expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1);
}, 20000);
});
it("will not affect non-status moves", async () => {
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
expect(speedOrder).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 () => {
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }]);
game.override.moveset([MoveId.LEAFAGE]);
game.override
.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }])
.moveset([MoveId.LEAFAGE]);
const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower");
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 () => {
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]);
game.override.moveset([MoveId.LEAFAGE]);
game.override
.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }])
.moveset([MoveId.LEAFAGE]);
const powerSpy = vi.spyOn(allMoves[MoveId.LEAFAGE], "calculateBattlePower");
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -26,14 +26,15 @@ describe("Abilities - Parental Bond", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("single");
game.override.disableCrits();
game.override.ability(AbilityId.PARENTAL_BOND);
game.override.enemySpecies(SpeciesId.SNORLAX);
game.override.enemyAbility(AbilityId.FUR_COAT);
game.override.enemyMoveset(MoveId.SPLASH);
game.override.startingLevel(100);
game.override.enemyLevel(100);
game.override
.battleStyle("single")
.disableCrits()
.ability(AbilityId.PARENTAL_BOND)
.enemySpecies(SpeciesId.SNORLAX)
.enemyAbility(AbilityId.FUR_COAT)
.enemyMoveset(MoveId.SPLASH)
.startingLevel(100)
.enemyLevel(100);
});
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 () => {
game.override.moveset([MoveId.POWER_UP_PUNCH]);
game.override.enemySpecies(SpeciesId.AMOONGUSS);
game.override.moveset([MoveId.POWER_UP_PUNCH]).enemySpecies(SpeciesId.AMOONGUSS);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
@ -148,8 +148,7 @@ describe("Abilities - Parental Bond", () => {
});
it("should not apply multiplier to counter moves", async () => {
game.override.moveset([MoveId.COUNTER]);
game.override.enemyMoveset([MoveId.TACKLE]);
game.override.moveset([MoveId.COUNTER]).enemyMoveset([MoveId.TACKLE]);
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
@ -167,9 +166,7 @@ describe("Abilities - Parental Bond", () => {
});
it("should not apply to multi-target moves", async () => {
game.override.battleStyle("double");
game.override.moveset([MoveId.EARTHQUAKE]);
game.override.passiveAbility(AbilityId.LEVITATE);
game.override.battleStyle("double").moveset([MoveId.EARTHQUAKE]).passiveAbility(AbilityId.LEVITATE);
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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
game.override.moveset([MoveId.TACKLE]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
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 () => {
game.override.moveset([MoveId.SEISMIC_TOSS]);
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
game.override.moveset([MoveId.SEISMIC_TOSS]).startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.enemyMoveset([MoveId.KINGS_SHIELD]);
game.override.moveset([MoveId.TACKLE]).enemyMoveset([MoveId.KINGS_SHIELD]);
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 () => {
game.override.moveset([MoveId.WATER_GUN]);
game.override.enemyAbility(AbilityId.STORM_DRAIN);
game.override.moveset([MoveId.WATER_GUN]).enemyAbility(AbilityId.STORM_DRAIN);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

@ -21,19 +21,18 @@ describe("Abilities - Perish Song", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("single");
game.override.disableCrits();
game.override.enemySpecies(SpeciesId.MAGIKARP);
game.override.enemyAbility(AbilityId.BALL_FETCH);
game.override.starterSpecies(SpeciesId.CURSOLA);
game.override.ability(AbilityId.PERISH_BODY);
game.override.moveset([MoveId.SPLASH]);
game.override
.battleStyle("single")
.disableCrits()
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.CURSOLA)
.ability(AbilityId.PERISH_BODY)
.moveset([MoveId.SPLASH])
.enemyMoveset([MoveId.AQUA_JET]);
});
it("should trigger when hit with damaging move", async () => {
game.override.enemyMoveset([MoveId.AQUA_JET]);
await game.classicMode.startBattle();
const cursola = game.scene.getPlayerPokemon();
const magikarp = game.scene.getEnemyPokemon();
@ -46,7 +45,7 @@ describe("Abilities - Perish Song", () => {
});
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]);
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 () => {
game.override.enemyMoveset([MoveId.PERISH_SONG, MoveId.AQUA_JET, MoveId.SPLASH]);
game.override.moveset([MoveId.WHIRLWIND, MoveId.SPLASH]);
game.override.startingWave(5);
game.override
.enemyMoveset([MoveId.PERISH_SONG, MoveId.AQUA_JET, MoveId.SPLASH])
.moveset([MoveId.WHIRLWIND, MoveId.SPLASH])
.startingWave(5);
await game.classicMode.startBattle([SpeciesId.CURSOLA]);
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 () => {
const baseForm = 2,
completeForm = 4;
game.override.startingWave(4);
game.override.starterForms({
game.override.startingWave(4).starterForms({
[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 () => {
const baseForm = 3,
completeForm = 5;
game.override.startingWave(4);
game.override.starterForms({
game.override.startingWave(4).starterForms({
[SpeciesId.ZYGARDE]: completeForm,
});

View File

@ -26,11 +26,12 @@ describe("Abilities - Power Spot", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("double");
game.override.moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM]);
game.override.enemyMoveset(MoveId.SPLASH);
game.override.enemySpecies(SpeciesId.SHUCKLE);
game.override.enemyAbility(AbilityId.BALL_FETCH);
game.override
.battleStyle("double")
.moveset([MoveId.TACKLE, MoveId.BREAKING_SWIPE, MoveId.SPLASH, MoveId.DAZZLING_GLEAM])
.enemyMoveset(MoveId.SPLASH)
.enemySpecies(SpeciesId.SHUCKLE)
.enemyAbility(AbilityId.BALL_FETCH);
});
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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.passiveAbility(AbilityId.REFRIGERATE);
game.override.moveset([MoveId.TACKLE]).passiveAbility(AbilityId.REFRIGERATE);
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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.enemyMoveset(MoveId.SPLASH);
game.override.moveset([MoveId.TACKLE]).enemyMoveset(MoveId.SPLASH);
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 () => {
game.override.moveset([MoveId.TACKLE]);
game.override.enemySpecies(SpeciesId.GASTLY);
game.override.moveset([MoveId.TACKLE]).enemySpecies(SpeciesId.GASTLY);
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 () => {
game.override.moveset([MoveId.TRICK_OR_TREAT]);
game.override.enemySpecies(SpeciesId.GASTLY);
game.override.moveset([MoveId.TRICK_OR_TREAT]).enemySpecies(SpeciesId.GASTLY);
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);

View File

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

View File

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

View File

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

View File

@ -22,15 +22,14 @@ describe("Abilities - Seed Sower", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleStyle("single");
game.override.disableCrits();
game.override.enemySpecies(SpeciesId.MAGIKARP);
game.override.enemyAbility(AbilityId.BALL_FETCH);
game.override.starterSpecies(SpeciesId.ARBOLIVA);
game.override.ability(AbilityId.SEED_SOWER);
game.override.moveset([MoveId.SPLASH]);
game.override
.battleStyle("single")
.disableCrits()
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.starterSpecies(SpeciesId.ARBOLIVA)
.ability(AbilityId.SEED_SOWER)
.moveset([MoveId.SPLASH]);
});
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);
expect(bindMove.calculateBattlePower).toHaveLastReturnedWith(bindMove.power);
}, 20000);
});
it("Sheer Force does not boost the base damage of moves with no secondary effect", async () => {
game.override.moveset([MoveId.TACKLE]);

View File

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

View File

@ -26,17 +26,17 @@ describe("Abilities - SHIELDS DOWN", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = MoveId.SPLASH;
game.override.battleStyle("single");
game.override.ability(AbilityId.SHIELDS_DOWN);
game.override.moveset([moveToUse]);
game.override.enemyMoveset([MoveId.TACKLE]);
game.override
.battleStyle("single")
.ability(AbilityId.SHIELDS_DOWN)
.moveset([moveToUse])
.enemyMoveset([MoveId.TACKLE]);
});
test("check if fainted pokemon switched to base form on arena reset", async () => {
const meteorForm = 0,
coreForm = 7;
game.override.startingWave(4);
game.override.starterForms({
game.override.startingWave(4).starterForms({
[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 () => {
game.override.enemyAbility(AbilityId.MOLD_BREAKER);
game.override.enemyMoveset([MoveId.SPORE]);
game.override.enemyAbility(AbilityId.MOLD_BREAKER).enemyMoveset([MoveId.SPORE]);
await game.classicMode.startBattle([SpeciesId.MINIOR]);
@ -94,8 +93,7 @@ describe("Abilities - SHIELDS DOWN", () => {
});
test("should ignore status moves even through mold breaker", async () => {
game.override.enemyMoveset([MoveId.SPORE]);
game.override.enemyAbility(AbilityId.MOLD_BREAKER);
game.override.enemyMoveset([MoveId.SPORE]).enemyAbility(AbilityId.MOLD_BREAKER);
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
test.todo("should become poisoned by toxic spikes when grounded", async () => {
game.override.enemyMoveset([MoveId.GRAVITY, MoveId.TOXIC_SPIKES, MoveId.SPLASH]);
game.override.moveset([MoveId.GRAVITY, MoveId.SPLASH]);
game.override
.enemyMoveset([MoveId.GRAVITY, MoveId.TOXIC_SPIKES, MoveId.SPLASH])
.moveset([MoveId.GRAVITY, MoveId.SPLASH]);
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.
test.todo("ditto should not be immune to status after transforming", async () => {
game.override.enemySpecies(SpeciesId.DITTO);
game.override.enemyAbility(AbilityId.IMPOSTER);
game.override.moveset([MoveId.SPLASH, MoveId.SPORE]);
game.override.enemySpecies(SpeciesId.DITTO).enemyAbility(AbilityId.IMPOSTER).moveset([MoveId.SPLASH, MoveId.SPORE]);
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 () => {
game.override.enemyMoveset([MoveId.TACKLE]);
game.override.moveset([MoveId.THUNDERBOLT]);
game.override.startingLevel(100);
game.override.startingWave(5);
game.override.enemySpecies(SpeciesId.MINIOR);
game.override
.enemyMoveset([MoveId.TACKLE])
.moveset([MoveId.THUNDERBOLT])
.startingLevel(100)
.startingWave(5)
.enemySpecies(SpeciesId.MINIOR);
await game.classicMode.startBattle([SpeciesId.REGIELEKI]);
const minior = game.scene.getEnemyPokemon()!;

View File

@ -36,5 +36,5 @@ describe("Abilities - Simple", () => {
const enemyPokemon = game.scene.getEnemyPokemon()!;
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.
expect(speedOrder).toEqual([enemyIndex, playerIndex]);
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 () => {
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.
expect(speedOrder).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 () => {
game.override.ability(AbilityId.STALL);
@ -91,5 +91,5 @@ describe("Abilities - Stall", () => {
// The player Pokemon (with Stall) goes second because its speed is lower.
expect(speedOrder).toEqual([enemyIndex, playerIndex]);
expect(commandOrder).toEqual([enemyIndex, playerIndex]);
}, 20000);
});
});

View File

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

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