mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-29 13:02:46 +02:00
Merge branch 'beta' into held-item-refactor
This commit is contained in:
commit
802f65e6d5
2
.github/workflows/deploy-beta.yml
vendored
2
.github/workflows/deploy-beta.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
|||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version-file: '.nvmrc'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Build
|
- name: Build
|
||||||
|
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version-file: '.nvmrc'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Build
|
- name: Build
|
||||||
|
10
.github/workflows/github-pages.yml
vendored
10
.github/workflows/github-pages.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository for Typedoc
|
- name: Checkout repository for Typedoc
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
path: pokerogue_docs
|
path: pokerogue_docs
|
||||||
@ -34,14 +34,14 @@ jobs:
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y git openssh-client
|
sudo apt install -y git openssh-client
|
||||||
|
|
||||||
- name: Setup Node 20.13.1
|
- name: Setup Node 22.14.1
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version-file: "pokerogue_docs/.nvmrc"
|
||||||
|
|
||||||
- name: Checkout repository for Github Pages
|
- name: Checkout repository for Github Pages
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: pokerogue_gh
|
path: pokerogue_gh
|
||||||
ref: gh-pages
|
ref: gh-pages
|
||||||
|
1
.github/workflows/quality.yml
vendored
1
.github/workflows/quality.yml
vendored
@ -29,6 +29,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v4 # Use the setup-node action version 4
|
uses: actions/setup-node@v4 # Use the setup-node action version 4
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install Node.js dependencies # Step to install Node.js dependencies
|
- name: Install Node.js dependencies # Step to install Node.js dependencies
|
||||||
run: npm ci # Use 'npm ci' to install dependencies
|
run: npm ci # Use 'npm ci' to install dependencies
|
||||||
|
5
.github/workflows/test-shard-template.yml
vendored
5
.github/workflows/test-shard-template.yml
vendored
@ -19,13 +19,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
- name: Install Node.js dependencies
|
- name: Install Node.js dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
@ -12,7 +12,7 @@ If you have the motivation and experience with Typescript/Javascript (or are wil
|
|||||||
|
|
||||||
#### Prerequisites
|
#### Prerequisites
|
||||||
|
|
||||||
- node: 20.13.1
|
- node: 22.14.0
|
||||||
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
|
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
|
||||||
|
|
||||||
#### Running Locally
|
#### Running Locally
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"src/overrides.ts",
|
"src/overrides.ts",
|
||||||
// TODO: these files are too big and complex, ignore them until their respective refactors
|
// TODO: these files are too big and complex, ignore them until their respective refactors
|
||||||
"src/data/moves/move.ts",
|
"src/data/moves/move.ts",
|
||||||
"src/data/ability.ts",
|
"src/data/abilities/ability.ts",
|
||||||
"src/field/pokemon.ts",
|
"src/field/pokemon.ts",
|
||||||
|
|
||||||
// this file is just too big:
|
// this file is just too big:
|
||||||
|
17
package-lock.json
generated
17
package-lock.json
generated
@ -27,7 +27,7 @@
|
|||||||
"@hpcc-js/wasm": "^2.22.4",
|
"@hpcc-js/wasm": "^2.22.4",
|
||||||
"@stylistic/eslint-plugin-ts": "^4.1.0",
|
"@stylistic/eslint-plugin-ts": "^4.1.0",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^20.12.13",
|
"@types/node": "^22.13.14",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||||
"@typescript-eslint/parser": "^8.28.0",
|
"@typescript-eslint/parser": "^8.28.0",
|
||||||
"@vitest/coverage-istanbul": "^3.0.9",
|
"@vitest/coverage-istanbul": "^3.0.9",
|
||||||
@ -2582,12 +2582,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.14.11",
|
"version": "22.13.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
|
||||||
"integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
|
"integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/statuses": {
|
"node_modules/@types/statuses": {
|
||||||
@ -7312,9 +7313,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "6.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
"@hpcc-js/wasm": "^2.22.4",
|
"@hpcc-js/wasm": "^2.22.4",
|
||||||
"@stylistic/eslint-plugin-ts": "^4.1.0",
|
"@stylistic/eslint-plugin-ts": "^4.1.0",
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^20.12.13",
|
"@types/node": "^22.13.14",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
"@typescript-eslint/eslint-plugin": "^8.28.0",
|
||||||
"@typescript-eslint/parser": "^8.28.0",
|
"@typescript-eslint/parser": "^8.28.0",
|
||||||
"@vitest/coverage-istanbul": "^3.0.9",
|
"@vitest/coverage-istanbul": "^3.0.9",
|
||||||
@ -67,6 +67,6 @@
|
|||||||
"phaser3-rex-plugins": "^1.80.14"
|
"phaser3-rex-plugins": "^1.80.14"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=22.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
src/@types/ability-types.ts
Normal file
11
src/@types/ability-types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
|
||||||
|
import type Move from "#app/data/moves/move";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import type { BattleStat } from "#enums/stat";
|
||||||
|
|
||||||
|
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => void;
|
||||||
|
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean;
|
||||||
|
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
||||||
|
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
||||||
|
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
||||||
|
export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
|
@ -67,7 +67,6 @@ import {
|
|||||||
} from "#app/modifier/modifier-type";
|
} from "#app/modifier/modifier-type";
|
||||||
import AbilityBar from "#app/ui/ability-bar";
|
import AbilityBar from "#app/ui/ability-bar";
|
||||||
import {
|
import {
|
||||||
allAbilities,
|
|
||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
applyPostBattleInitAbAttrs,
|
applyPostBattleInitAbAttrs,
|
||||||
applyPostItemLostAbAttrs,
|
applyPostItemLostAbAttrs,
|
||||||
@ -75,7 +74,8 @@ import {
|
|||||||
DoubleBattleChanceAbAttr,
|
DoubleBattleChanceAbAttr,
|
||||||
PostBattleInitAbAttr,
|
PostBattleInitAbAttr,
|
||||||
PostItemLostAbAttr,
|
PostItemLostAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "./data/data-lists";
|
||||||
import type { FixedBattleConfig } from "#app/battle";
|
import type { FixedBattleConfig } from "#app/battle";
|
||||||
import Battle, { BattleType } from "#app/battle";
|
import Battle, { BattleType } from "#app/battle";
|
||||||
import type { GameMode } from "#app/game-mode";
|
import type { GameMode } from "#app/game-mode";
|
||||||
|
54
src/data/abilities/ab-attrs/ab-attr.ts
Normal file
54
src/data/abilities/ab-attrs/ab-attr.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import type { AbAttrCondition } from "#app/@types/ability-types";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
import type * as Utils from "#app/utils";
|
||||||
|
|
||||||
|
export abstract class AbAttr {
|
||||||
|
public showAbility: boolean;
|
||||||
|
private extraCondition: AbAttrCondition;
|
||||||
|
|
||||||
|
constructor(showAbility = true) {
|
||||||
|
this.showAbility = showAbility;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies ability effects without checking conditions
|
||||||
|
* @param _pokemon - The pokemon to apply this ability to
|
||||||
|
* @param _passive - Whether or not the ability is a passive
|
||||||
|
* @param _simulated - Whether the call is simulated
|
||||||
|
* @param _args - Extra args passed to the function. Handled by child classes.
|
||||||
|
* @see {@linkcode canApply}
|
||||||
|
*/
|
||||||
|
apply(
|
||||||
|
_pokemon: Pokemon,
|
||||||
|
_passive: boolean,
|
||||||
|
_simulated: boolean,
|
||||||
|
_cancelled: Utils.BooleanHolder | null,
|
||||||
|
_args: any[],
|
||||||
|
): void {}
|
||||||
|
|
||||||
|
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCondition(): AbAttrCondition | null {
|
||||||
|
return this.extraCondition || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
addCondition(condition: AbAttrCondition): AbAttr {
|
||||||
|
this.extraCondition = condition;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a boolean describing whether the ability can be applied under current conditions
|
||||||
|
* @param _pokemon - The pokemon to apply this ability to
|
||||||
|
* @param _passive - Whether or not the ability is a passive
|
||||||
|
* @param _simulated - Whether the call is simulated
|
||||||
|
* @param _args - Extra args passed to the function. Handled by child classes.
|
||||||
|
* @returns `true` if the ability can be applied, `false` otherwise
|
||||||
|
* @see {@linkcode apply}
|
||||||
|
*/
|
||||||
|
canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
137
src/data/abilities/ability-class.ts
Normal file
137
src/data/abilities/ability-class.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import type { AbAttrCondition } from "#app/@types/ability-types";
|
||||||
|
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import type { Localizable } from "#app/interfaces/locales";
|
||||||
|
import type { Constructor } from "#app/utils";
|
||||||
|
|
||||||
|
export class Ability implements Localizable {
|
||||||
|
public id: Abilities;
|
||||||
|
|
||||||
|
private nameAppend: string;
|
||||||
|
public name: string;
|
||||||
|
public description: string;
|
||||||
|
public generation: number;
|
||||||
|
public isBypassFaint: boolean;
|
||||||
|
public isIgnorable: boolean;
|
||||||
|
public isSuppressable = true;
|
||||||
|
public isCopiable = true;
|
||||||
|
public isReplaceable = true;
|
||||||
|
public attrs: AbAttr[];
|
||||||
|
public conditions: AbAttrCondition[];
|
||||||
|
|
||||||
|
constructor(id: Abilities, generation: number) {
|
||||||
|
this.id = id;
|
||||||
|
|
||||||
|
this.nameAppend = "";
|
||||||
|
this.generation = generation;
|
||||||
|
this.attrs = [];
|
||||||
|
this.conditions = [];
|
||||||
|
|
||||||
|
this.isSuppressable = true;
|
||||||
|
this.isCopiable = true;
|
||||||
|
this.isReplaceable = true;
|
||||||
|
|
||||||
|
this.localize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isSwappable(): boolean {
|
||||||
|
return this.isCopiable && this.isReplaceable;
|
||||||
|
}
|
||||||
|
localize(): void {
|
||||||
|
const i18nKey = Abilities[this.id]
|
||||||
|
.split("_")
|
||||||
|
.filter(f => f)
|
||||||
|
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
|
||||||
|
.join("") as string;
|
||||||
|
|
||||||
|
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
|
||||||
|
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all ability attributes that match `attrType`
|
||||||
|
* @param attrType any attribute that extends {@linkcode AbAttr}
|
||||||
|
* @returns Array of attributes that match `attrType`, Empty Array if none match.
|
||||||
|
*/
|
||||||
|
getAttrs<T extends AbAttr>(attrType: Constructor<T>): T[] {
|
||||||
|
return this.attrs.filter((a): a is T => a instanceof attrType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an ability has an attribute that matches `attrType`
|
||||||
|
* @param attrType any attribute that extends {@linkcode AbAttr}
|
||||||
|
* @returns true if the ability has attribute `attrType`
|
||||||
|
*/
|
||||||
|
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
|
||||||
|
return this.attrs.some(attr => attr instanceof attrType);
|
||||||
|
}
|
||||||
|
|
||||||
|
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
|
||||||
|
const attr = new AttrType(...args);
|
||||||
|
this.attrs.push(attr);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionalAttr<T extends Constructor<AbAttr>>(
|
||||||
|
condition: AbAttrCondition,
|
||||||
|
AttrType: T,
|
||||||
|
...args: ConstructorParameters<T>
|
||||||
|
): Ability {
|
||||||
|
const attr = new AttrType(...args);
|
||||||
|
attr.addCondition(condition);
|
||||||
|
this.attrs.push(attr);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bypassFaint(): Ability {
|
||||||
|
this.isBypassFaint = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ignorable(): Ability {
|
||||||
|
this.isIgnorable = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsuppressable(): Ability {
|
||||||
|
this.isSuppressable = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uncopiable(): Ability {
|
||||||
|
this.isCopiable = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreplaceable(): Ability {
|
||||||
|
this.isReplaceable = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
condition(condition: AbAttrCondition): Ability {
|
||||||
|
this.conditions.push(condition);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
partial(): this {
|
||||||
|
this.nameAppend += " (P)";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
unimplemented(): this {
|
||||||
|
this.nameAppend += " (N)";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
|
||||||
|
* @returns the ability
|
||||||
|
*/
|
||||||
|
edgeCase(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,228 +1,75 @@
|
|||||||
import type { EnemyPokemon, PokemonMove } from "../field/pokemon";
|
import { HitResult, MoveResult, PlayerPokemon } from "#app/field/pokemon";
|
||||||
import type Pokemon from "../field/pokemon";
|
|
||||||
import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon";
|
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
|
||||||
import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils";
|
import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import type { Weather } from "#app/data/weather";
|
import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags";
|
||||||
import type { BattlerTag } from "./battler-tags";
|
|
||||||
import { BattlerTagLapseType, GroundedTag } from "./battler-tags";
|
|
||||||
import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect";
|
import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect";
|
||||||
import { Gender } from "./gender";
|
import { Gender } from "#app/data/gender";
|
||||||
import type Move from "./moves/move";
|
import {
|
||||||
import { AttackMove, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./moves/move";
|
AttackMove,
|
||||||
import { MoveFlags } from "#enums/MoveFlags";
|
FlinchAttr,
|
||||||
import { MoveTarget } from "#enums/MoveTarget";
|
OneHitKOAttr,
|
||||||
import { MoveCategory } from "#enums/MoveCategory";
|
HitHealAttr,
|
||||||
import type { ArenaTrapTag, SuppressAbilitiesTag } from "./arena-tag";
|
allMoves,
|
||||||
import { ArenaTagSide } from "./arena-tag";
|
StatusMove,
|
||||||
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier";
|
SelfStatusMove,
|
||||||
import { TerrainType } from "./terrain";
|
VariablePowerAttr,
|
||||||
import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "./pokemon-forms";
|
applyMoveAttrs,
|
||||||
|
VariableMoveTypeAttr,
|
||||||
|
RandomMovesetMoveAttr,
|
||||||
|
RandomMoveAttr,
|
||||||
|
NaturePowerAttr,
|
||||||
|
CopyMoveAttr,
|
||||||
|
NeutralDamageAgainstFlyingTypeMultiplierAttr,
|
||||||
|
FixedDamageAttr,
|
||||||
|
} from "#app/data/moves/move";
|
||||||
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
|
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
|
import { TerrainType } from "#app/data/terrain";
|
||||||
|
import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import type { Localizable } from "#app/interfaces/locales";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import { Command } from "../ui/command-ui-handler";
|
|
||||||
import { BerryModifierType } from "#app/modifier/modifier-type";
|
import { BerryModifierType } from "#app/modifier/modifier-type";
|
||||||
import { getPokeballName } from "./pokeball";
|
import { getPokeballName } from "#app/data/pokeball";
|
||||||
import type { BattlerIndex } from "#app/battle";
|
|
||||||
import { BattleType } from "#app/battle";
|
import { BattleType } from "#app/battle";
|
||||||
import { Abilities } from "#enums/abilities";
|
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
||||||
import { Moves } from "#enums/moves";
|
|
||||||
import { Species } from "#enums/species";
|
|
||||||
import { Stat, type BattleStat, type EffectiveStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
|
|
||||||
import { MovePhase } from "#app/phases/move-phase";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
|
||||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { SwitchType } from "#app/enums/switch-type";
|
|
||||||
import { SwitchPhase } from "#app/phases/switch-phase";
|
import { SwitchPhase } from "#app/phases/switch-phase";
|
||||||
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
||||||
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
||||||
import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||||
|
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
|
import { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
|
||||||
|
import { Ability } from "#app/data/abilities/ability-class";
|
||||||
|
|
||||||
|
// Enum imports
|
||||||
|
import { Stat, type BattleStat , BATTLE_STATS, EFFECTIVE_STATS, getStatKey, type EffectiveStat } from "#enums/stat";
|
||||||
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
import { PokemonAnimType } from "#enums/pokemon-anim-type";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { SwitchType } from "#enums/switch-type";
|
||||||
|
import { MoveFlags } from "#enums/MoveFlags";
|
||||||
|
import { MoveTarget } from "#enums/MoveTarget";
|
||||||
|
import { MoveCategory } from "#enums/MoveCategory";
|
||||||
|
|
||||||
export class Ability implements Localizable {
|
// Type imports
|
||||||
public id: Abilities;
|
import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
private nameAppend: string;
|
import type { Weather } from "#app/data/weather";
|
||||||
public name: string;
|
import type { BattlerTag } from "#app/data/battler-tags";
|
||||||
public description: string;
|
import type { AbAttrCondition, PokemonDefendCondition, PokemonStatStageChangeCondition, PokemonAttackCondition, AbAttrApplyFunc, AbAttrSuccessFunc } from "#app/@types/ability-types";
|
||||||
public generation: number;
|
import type { BattlerIndex } from "#app/battle";
|
||||||
public isBypassFaint: boolean;
|
import type Move from "#app/data/moves/move";
|
||||||
public isIgnorable: boolean;
|
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||||
public isSuppressable = true;
|
|
||||||
public isCopiable = true;
|
|
||||||
public isReplaceable = true;
|
|
||||||
public attrs: AbAttr[];
|
|
||||||
public conditions: AbAttrCondition[];
|
|
||||||
|
|
||||||
constructor(id: Abilities, generation: number) {
|
|
||||||
this.id = id;
|
|
||||||
|
|
||||||
this.nameAppend = "";
|
|
||||||
this.generation = generation;
|
|
||||||
this.attrs = [];
|
|
||||||
this.conditions = [];
|
|
||||||
|
|
||||||
this.isSuppressable = true;
|
|
||||||
this.isCopiable = true;
|
|
||||||
this.isReplaceable = true;
|
|
||||||
|
|
||||||
this.localize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public get isSwappable(): boolean {
|
|
||||||
return this.isCopiable && this.isReplaceable;
|
|
||||||
}
|
|
||||||
localize(): void {
|
|
||||||
const i18nKey = Abilities[this.id].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as string;
|
|
||||||
|
|
||||||
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
|
|
||||||
this.description = this.id ? i18next.t(`ability:${i18nKey}.description`) as string : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all ability attributes that match `attrType`
|
|
||||||
* @param attrType any attribute that extends {@linkcode AbAttr}
|
|
||||||
* @returns Array of attributes that match `attrType`, Empty Array if none match.
|
|
||||||
*/
|
|
||||||
getAttrs<T extends AbAttr>(attrType: Constructor<T> ): T[] {
|
|
||||||
return this.attrs.filter((a): a is T => a instanceof attrType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an ability has an attribute that matches `attrType`
|
|
||||||
* @param attrType any attribute that extends {@linkcode AbAttr}
|
|
||||||
* @returns true if the ability has attribute `attrType`
|
|
||||||
*/
|
|
||||||
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
|
|
||||||
return this.attrs.some((attr) => attr instanceof attrType);
|
|
||||||
}
|
|
||||||
|
|
||||||
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
|
|
||||||
const attr = new AttrType(...args);
|
|
||||||
this.attrs.push(attr);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
conditionalAttr<T extends Constructor<AbAttr>>(condition: AbAttrCondition, AttrType: T, ...args: ConstructorParameters<T>): Ability {
|
|
||||||
const attr = new AttrType(...args);
|
|
||||||
attr.addCondition(condition);
|
|
||||||
this.attrs.push(attr);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bypassFaint(): Ability {
|
|
||||||
this.isBypassFaint = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
ignorable(): Ability {
|
|
||||||
this.isIgnorable = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsuppressable(): Ability {
|
|
||||||
this.isSuppressable = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
uncopiable(): Ability {
|
|
||||||
this.isCopiable = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unreplaceable(): Ability {
|
|
||||||
this.isReplaceable = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
condition(condition: AbAttrCondition): Ability {
|
|
||||||
this.conditions.push(condition);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial(): this {
|
|
||||||
this.nameAppend += " (P)";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
unimplemented(): this {
|
|
||||||
this.nameAppend += " (N)";
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
|
|
||||||
* @returns the ability
|
|
||||||
*/
|
|
||||||
edgeCase(): this {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => void;
|
|
||||||
type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean;
|
|
||||||
type AbAttrCondition = (pokemon: Pokemon) => boolean;
|
|
||||||
|
|
||||||
// TODO: Can this be improved?
|
|
||||||
type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
|
|
||||||
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
|
|
||||||
type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
|
|
||||||
|
|
||||||
export abstract class AbAttr {
|
|
||||||
public showAbility: boolean;
|
|
||||||
private extraCondition: AbAttrCondition;
|
|
||||||
|
|
||||||
constructor(showAbility = true) {
|
|
||||||
this.showAbility = showAbility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies ability effects without checking conditions
|
|
||||||
* @param pokemon - The pokemon to apply this ability to
|
|
||||||
* @param passive - Whether or not the ability is a passive
|
|
||||||
* @param simulated - Whether the call is simulated
|
|
||||||
* @param args - Extra args passed to the function. Handled by child classes.
|
|
||||||
* @see {@linkcode canApply}
|
|
||||||
*/
|
|
||||||
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder | null, args: any[]): void {}
|
|
||||||
|
|
||||||
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCondition(): AbAttrCondition | null {
|
|
||||||
return this.extraCondition || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
addCondition(condition: AbAttrCondition): AbAttr {
|
|
||||||
this.extraCondition = condition;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a boolean describing whether the ability can be applied under current conditions
|
|
||||||
* @param pokemon - The pokemon to apply this ability to
|
|
||||||
* @param passive - Whether or not the ability is a passive
|
|
||||||
* @param simulated - Whether the call is simulated
|
|
||||||
* @param args - Extra args passed to the function. Handled by child classes.
|
|
||||||
* @returns `true` if the ability can be applied, `false` otherwise
|
|
||||||
* @see {@linkcode apply}
|
|
||||||
*/
|
|
||||||
canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BlockRecoilDamageAttr extends AbAttr {
|
export class BlockRecoilDamageAttr extends AbAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -233,7 +80,7 @@ export class BlockRecoilDamageAttr extends AbAttr {
|
|||||||
cancelled.value = true;
|
cancelled.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) {
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]) {
|
||||||
return i18next.t("abilityTriggers:blockRecoilDamage", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName });
|
return i18next.t("abilityTriggers:blockRecoilDamage", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4186,7 +4033,9 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
|
|||||||
} else {
|
} else {
|
||||||
this.target = pokemon;
|
this.target = pokemon;
|
||||||
}
|
}
|
||||||
return !isNullOrUndefined(this.target?.status);
|
|
||||||
|
const effect = this.target?.status?.effect;
|
||||||
|
return !!effect && effect !== StatusEffect.FAINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
|
||||||
@ -6453,10 +6302,9 @@ function getPokemonWithWeatherBasedForms() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const allAbilities = [ new Ability(Abilities.NONE, 3) ];
|
|
||||||
|
|
||||||
export function initAbilities() {
|
export function initAbilities() {
|
||||||
allAbilities.push(
|
allAbilities.push(
|
||||||
|
new Ability(Abilities.NONE, 3),
|
||||||
new Ability(Abilities.STENCH, 3)
|
new Ability(Abilities.STENCH, 3)
|
||||||
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr(FlinchAttr) && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),
|
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr(FlinchAttr) && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),
|
||||||
new Ability(Abilities.DRIZZLE, 3)
|
new Ability(Abilities.DRIZZLE, 3)
|
||||||
@ -6856,8 +6704,8 @@ export function initAbilities() {
|
|||||||
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT))
|
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT))
|
||||||
.condition(getSheerForceHitDisableAbCondition()),
|
.condition(getSheerForceHitDisableAbCondition()),
|
||||||
new Ability(Abilities.SHEER_FORCE, 5)
|
new Ability(Abilities.SHEER_FORCE, 5)
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
|
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 1.3)
|
||||||
.attr(MoveEffectChanceMultiplierAbAttr, 0), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
|
.attr(MoveEffectChanceMultiplierAbAttr, 0), // This attribute does not seem to function - Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
|
||||||
new Ability(Abilities.CONTRARY, 5)
|
new Ability(Abilities.CONTRARY, 5)
|
||||||
.attr(StatStageChangeMultiplierAbAttr, -1)
|
.attr(StatStageChangeMultiplierAbAttr, -1)
|
||||||
.ignorable(),
|
.ignorable(),
|
@ -18,7 +18,7 @@ import {
|
|||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
applyOnGainAbAttrs,
|
applyOnGainAbAttrs,
|
||||||
applyOnLoseAbAttrs,
|
applyOnLoseAbAttrs,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
|
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import {
|
import {
|
||||||
allAbilities,
|
|
||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
BlockNonDirectDamageAbAttr,
|
BlockNonDirectDamageAbAttr,
|
||||||
FlinchEffectAbAttr,
|
FlinchEffectAbAttr,
|
||||||
ProtectStatAbAttr,
|
ProtectStatAbAttr,
|
||||||
ConditionalUserFieldProtectStatAbAttr,
|
ConditionalUserFieldProtectStatAbAttr,
|
||||||
ReverseDrainAbAttr,
|
ReverseDrainAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "./data-lists";
|
||||||
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
|
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
|
||||||
import type Move from "#app/data/moves/move";
|
import type Move from "#app/data/moves/move";
|
||||||
import {
|
import {
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
ReduceBerryUseThresholdAbAttr,
|
ReduceBerryUseThresholdAbAttr,
|
||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
applyPostItemLostAbAttrs,
|
applyPostItemLostAbAttrs,
|
||||||
} from "./ability";
|
} from "./abilities/ability";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
|
3
src/data/data-lists.ts
Normal file
3
src/data/data-lists.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import type { Ability } from "./abilities/ability-class";
|
||||||
|
|
||||||
|
export const allAbilities: Ability[] = [];
|
@ -34,7 +34,6 @@ import { WeatherType } from "#enums/weather-type";
|
|||||||
import type { ArenaTrapTag } from "../arena-tag";
|
import type { ArenaTrapTag } from "../arena-tag";
|
||||||
import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag";
|
import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag";
|
||||||
import {
|
import {
|
||||||
allAbilities,
|
|
||||||
AllyMoveCategoryPowerBoostAbAttr,
|
AllyMoveCategoryPowerBoostAbAttr,
|
||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
applyPostAttackAbAttrs,
|
applyPostAttackAbAttrs,
|
||||||
@ -65,7 +64,8 @@ import {
|
|||||||
UserFieldMoveTypePowerBoostAbAttr,
|
UserFieldMoveTypePowerBoostAbAttr,
|
||||||
VariableMovePowerAbAttr,
|
VariableMovePowerAbAttr,
|
||||||
WonderSkinAbAttr,
|
WonderSkinAbAttr,
|
||||||
} from "../ability";
|
} from "../abilities/ability";
|
||||||
|
import { allAbilities } from "../data-lists";
|
||||||
import {
|
import {
|
||||||
AttackTypeBoosterModifier,
|
AttackTypeBoosterModifier,
|
||||||
BerryModifier,
|
BerryModifier,
|
||||||
@ -3467,8 +3467,7 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
|
|||||||
/**
|
/**
|
||||||
* Attribute implementing the stat boosting effect of {@link https://bulbapedia.bulbagarden.net/wiki/Order_Up_(move) | Order Up}.
|
* Attribute implementing the stat boosting effect of {@link https://bulbapedia.bulbagarden.net/wiki/Order_Up_(move) | Order Up}.
|
||||||
* If the user has a Pokemon with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander} in their mouth,
|
* If the user has a Pokemon with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander} in their mouth,
|
||||||
* one of the user's stats are increased by 1 stage, depending on the "commanding" Pokemon's form. This effect does not respect
|
* one of the user's stats are increased by 1 stage, depending on the "commanding" Pokemon's form.
|
||||||
* effect chance, but Order Up itself may be boosted by Sheer Force.
|
|
||||||
*/
|
*/
|
||||||
export class OrderUpStatBoostAttr extends MoveEffectAttr {
|
export class OrderUpStatBoostAttr extends MoveEffectAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -9727,7 +9726,7 @@ export function initMoves() {
|
|||||||
.ignoresProtect()
|
.ignoresProtect()
|
||||||
.target(MoveTarget.BOTH_SIDES)
|
.target(MoveTarget.BOTH_SIDES)
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
new AttackMove(Moves.SMACK_DOWN, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5)
|
new AttackMove(Moves.SMACK_DOWN, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, -1, 0, 5)
|
||||||
.attr(FallDownAttr)
|
.attr(FallDownAttr)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
||||||
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ])
|
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ])
|
||||||
@ -9894,7 +9893,7 @@ export function initMoves() {
|
|||||||
.attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
.attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||||
new AttackMove(Moves.FROST_BREATH, PokemonType.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5)
|
new AttackMove(Moves.FROST_BREATH, PokemonType.ICE, MoveCategory.SPECIAL, 60, 90, 10, -1, 0, 5)
|
||||||
.attr(CritOnlyAttr),
|
.attr(CritOnlyAttr),
|
||||||
new AttackMove(Moves.DRAGON_TAIL, PokemonType.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
new AttackMove(Moves.DRAGON_TAIL, PokemonType.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
||||||
.attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH)
|
.attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH)
|
||||||
@ -10536,7 +10535,7 @@ export function initMoves() {
|
|||||||
.attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, false, true),
|
.attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, false, true),
|
||||||
new AttackMove(Moves.BADDY_BAD, PokemonType.DARK, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7)
|
new AttackMove(Moves.BADDY_BAD, PokemonType.DARK, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7)
|
||||||
.attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, false, true),
|
.attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, false, true),
|
||||||
new AttackMove(Moves.SAPPY_SEED, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 7)
|
new AttackMove(Moves.SAPPY_SEED, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7)
|
||||||
.attr(LeechSeedAttr)
|
.attr(LeechSeedAttr)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.FREEZY_FROST, PokemonType.ICE, MoveCategory.SPECIAL, 100, 90, 10, -1, 0, 7)
|
new AttackMove(Moves.FREEZY_FROST, PokemonType.ICE, MoveCategory.SPECIAL, 100, 90, 10, -1, 0, 7)
|
||||||
@ -10864,7 +10863,7 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true),
|
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true),
|
||||||
new AttackMove(Moves.BITTER_MALICE, PokemonType.GHOST, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 8)
|
new AttackMove(Moves.BITTER_MALICE, PokemonType.GHOST, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 8)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
||||||
new SelfStatusMove(Moves.SHELTER, PokemonType.STEEL, -1, 10, 100, 0, 8)
|
new SelfStatusMove(Moves.SHELTER, PokemonType.STEEL, -1, 10, -1, 0, 8)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
|
||||||
new AttackMove(Moves.TRIPLE_ARROWS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
|
new AttackMove(Moves.TRIPLE_ARROWS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
@ -11019,7 +11018,7 @@ export function initMoves() {
|
|||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.LUMINA_CRASH, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
new AttackMove(Moves.LUMINA_CRASH, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -2),
|
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -2),
|
||||||
new AttackMove(Moves.ORDER_UP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
|
new AttackMove(Moves.ORDER_UP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 9)
|
||||||
.attr(OrderUpStatBoostAttr)
|
.attr(OrderUpStatBoostAttr)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.JET_PUNCH, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
|
new AttackMove(Moves.JET_PUNCH, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
|
||||||
@ -11073,7 +11072,7 @@ export function initMoves() {
|
|||||||
.attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 2, 2),
|
.attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 2, 2),
|
||||||
new AttackMove(Moves.KOWTOW_CLEAVE, PokemonType.DARK, MoveCategory.PHYSICAL, 85, -1, 10, -1, 0, 9)
|
new AttackMove(Moves.KOWTOW_CLEAVE, PokemonType.DARK, MoveCategory.PHYSICAL, 85, -1, 10, -1, 0, 9)
|
||||||
.slicingMove(),
|
.slicingMove(),
|
||||||
new AttackMove(Moves.FLOWER_TRICK, PokemonType.GRASS, MoveCategory.PHYSICAL, 70, -1, 10, 100, 0, 9)
|
new AttackMove(Moves.FLOWER_TRICK, PokemonType.GRASS, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 9)
|
||||||
.attr(CritOnlyAttr)
|
.attr(CritOnlyAttr)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
new AttackMove(Moves.TORCH_SONG, PokemonType.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
new AttackMove(Moves.TORCH_SONG, PokemonType.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
||||||
@ -11192,7 +11191,7 @@ export function initMoves() {
|
|||||||
.attr(StatusEffectAttr, StatusEffect.BURN)
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||||
.triageMove(),
|
.triageMove(),
|
||||||
new AttackMove(Moves.SYRUP_BOMB, PokemonType.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9)
|
new AttackMove(Moves.SYRUP_BOMB, PokemonType.GRASS, MoveCategory.SPECIAL, 60, 85, 10, 100, 0, 9)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.SYRUP_BOMB, false, false, 3)
|
.attr(AddBattlerTagAttr, BattlerTagType.SYRUP_BOMB, false, false, 3)
|
||||||
.ballBombMove(),
|
.ballBombMove(),
|
||||||
new AttackMove(Moves.IVY_CUDGEL, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.IVY_CUDGEL, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9)
|
||||||
@ -11206,11 +11205,12 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.TERA_STARSTORM, PokemonType.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
new AttackMove(Moves.TERA_STARSTORM, PokemonType.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
||||||
.attr(TeraMoveCategoryAttr)
|
.attr(TeraMoveCategoryAttr)
|
||||||
.attr(TeraStarstormTypeAttr)
|
.attr(TeraStarstormTypeAttr)
|
||||||
.attr(VariableTargetAttr, (user, target, move) => user.hasSpecies(Species.TERAPAGOS) && user.isTerastallized ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER)
|
.attr(VariableTargetAttr, (user, target, move) => user.hasSpecies(Species.TERAPAGOS) && (user.isTerastallized || globalScene.currentBattle.preTurnCommands[user.getFieldIndex()]?.command === Command.TERA) ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER)
|
||||||
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
|
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
|
||||||
new AttackMove(Moves.FICKLE_BEAM, PokemonType.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9)
|
new AttackMove(Moves.FICKLE_BEAM, PokemonType.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9)
|
||||||
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
|
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
|
||||||
.attr(DoublePowerChanceAttr),
|
.attr(DoublePowerChanceAttr)
|
||||||
|
.edgeCase(), // Should not interact with Sheer Force
|
||||||
new SelfStatusMove(Moves.BURNING_BULWARK, PokemonType.FIRE, -1, 10, -1, 4, 9)
|
new SelfStatusMove(Moves.BURNING_BULWARK, PokemonType.FIRE, -1, 10, -1, 4, 9)
|
||||||
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK)
|
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK)
|
||||||
.condition(failIfLastCondition),
|
.condition(failIfLastCondition),
|
||||||
@ -11233,7 +11233,7 @@ export function initMoves() {
|
|||||||
new StatusMove(Moves.DRAGON_CHEER, PokemonType.DRAGON, -1, 15, -1, 0, 9)
|
new StatusMove(Moves.DRAGON_CHEER, PokemonType.DRAGON, -1, 15, -1, 0, 9)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true)
|
||||||
.target(MoveTarget.NEAR_ALLY),
|
.target(MoveTarget.NEAR_ALLY),
|
||||||
new AttackMove(Moves.ALLURING_VOICE, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.ALLURING_VOICE, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
||||||
.attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED)
|
.attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED)
|
||||||
.soundBased(),
|
.soundBased(),
|
||||||
new AttackMove(Moves.TEMPER_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.TEMPER_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
|
||||||
@ -11242,7 +11242,7 @@ export function initMoves() {
|
|||||||
.attr(MissEffectAttr, crashDamageFunc)
|
.attr(MissEffectAttr, crashDamageFunc)
|
||||||
.attr(NoEffectAttr, crashDamageFunc)
|
.attr(NoEffectAttr, crashDamageFunc)
|
||||||
.recklessMove(),
|
.recklessMove(),
|
||||||
new AttackMove(Moves.PSYCHIC_NOISE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 9)
|
new AttackMove(Moves.PSYCHIC_NOISE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 9)
|
||||||
.soundBased()
|
.soundBased()
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, false, 2),
|
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, false, 2),
|
||||||
new AttackMove(Moves.UPPER_HAND, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
|
new AttackMove(Moves.UPPER_HAND, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
|
||||||
|
@ -38,7 +38,7 @@ import i18next from "i18next";
|
|||||||
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/field/pokemon";
|
import { PokemonMove } from "#app/field/pokemon";
|
||||||
import { Ability } from "#app/data/ability";
|
import { Ability } from "#app/data/abilities/ability-class";
|
||||||
import { BerryModifier } from "#app/modifier/modifier";
|
import { BerryModifier } from "#app/modifier/modifier";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
@ -46,7 +46,7 @@ import { Abilities } from "#enums/abilities";
|
|||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { Ability } from "#app/data/ability";
|
import { Ability } from "#app/data/abilities/ability-class";
|
||||||
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
|
@ -24,7 +24,7 @@ import { PokemonType } from "#enums/pokemon-type";
|
|||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms";
|
||||||
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/ability";
|
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability";
|
||||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { Ability } from "#app/data/ability";
|
import type { Ability } from "#app/data/abilities/ability-class";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import {
|
import {
|
||||||
initBattleWithEnemyConfig,
|
initBattleWithEnemyConfig,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "../data-lists";
|
||||||
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms";
|
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms";
|
||||||
|
@ -27,11 +27,12 @@ import {
|
|||||||
} from "#app/data/balance/pokemon-level-moves";
|
} from "#app/data/balance/pokemon-level-moves";
|
||||||
import type { Stat } from "#enums/stat";
|
import type { Stat } from "#enums/stat";
|
||||||
import type { Variant, VariantSet } from "#app/sprites/variant";
|
import type { Variant, VariantSet } from "#app/sprites/variant";
|
||||||
import { variantData } from "#app/sprites/variant";
|
import { populateVariantColorCache, variantData } from "#app/sprites/variant";
|
||||||
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
|
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
|
||||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||||
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
||||||
import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite";
|
import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite";
|
||||||
|
import { hasExpSprite } from "#app/sprites/sprite-utils";
|
||||||
|
|
||||||
export enum Region {
|
export enum Region {
|
||||||
NORMAL,
|
NORMAL,
|
||||||
@ -388,8 +389,7 @@ export abstract class PokemonSpeciesForm {
|
|||||||
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compute the sprite ID of the pokemon form. */
|
getBaseSpriteKey(female: boolean, formIndex?: number): string {
|
||||||
getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back?: boolean): string {
|
|
||||||
if (formIndex === undefined || this instanceof PokemonForm) {
|
if (formIndex === undefined || this instanceof PokemonForm) {
|
||||||
formIndex = this.formIndex;
|
formIndex = this.formIndex;
|
||||||
}
|
}
|
||||||
@ -400,7 +400,12 @@ export abstract class PokemonSpeciesForm {
|
|||||||
female &&
|
female &&
|
||||||
![SpeciesFormKey.MEGA, SpeciesFormKey.GIGANTAMAX].includes(formSpriteKey as SpeciesFormKey);
|
![SpeciesFormKey.MEGA, SpeciesFormKey.GIGANTAMAX].includes(formSpriteKey as SpeciesFormKey);
|
||||||
|
|
||||||
const baseSpriteKey = `${showGenderDiffs ? "female__" : ""}${this.speciesId}${formSpriteKey ? `-${formSpriteKey}` : ""}`;
|
return `${showGenderDiffs ? "female__" : ""}${this.speciesId}${formSpriteKey ? `-${formSpriteKey}` : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Compute the sprite ID of the pokemon form. */
|
||||||
|
getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back?: boolean): string {
|
||||||
|
const baseSpriteKey = this.getBaseSpriteKey(female, formIndex);
|
||||||
|
|
||||||
let config = variantData;
|
let config = variantData;
|
||||||
`${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => (config ? (config = config[p]) : null));
|
`${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => (config ? (config = config[p]) : null));
|
||||||
@ -597,10 +602,19 @@ export abstract class PokemonSpeciesForm {
|
|||||||
startLoad = false,
|
startLoad = false,
|
||||||
back = false,
|
back = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
// We need to populate the color cache for this species' variant
|
||||||
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back);
|
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back);
|
||||||
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back));
|
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back));
|
||||||
globalScene.load.audio(this.getCryKey(formIndex), `audio/${this.getCryKey(formIndex)}.m4a`);
|
globalScene.load.audio(this.getCryKey(formIndex), `audio/${this.getCryKey(formIndex)}.m4a`);
|
||||||
|
|
||||||
|
const baseSpriteKey = this.getBaseSpriteKey(female, formIndex);
|
||||||
|
|
||||||
|
// Force the variant color cache to be loaded for the form
|
||||||
|
await populateVariantColorCache(
|
||||||
|
"pkmn__" + baseSpriteKey,
|
||||||
|
globalScene.experimentalSprites && hasExpSprite(spriteKey),
|
||||||
|
baseSpriteKey,
|
||||||
|
);
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>(resolve => {
|
||||||
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
||||||
const originalWarn = console.warn;
|
const originalWarn = console.warn;
|
||||||
|
@ -116,7 +116,6 @@ export class TrainerConfig {
|
|||||||
public modifierRewardFuncs: ModifierTypeFunc[] = [];
|
public modifierRewardFuncs: ModifierTypeFunc[] = [];
|
||||||
public partyTemplates: TrainerPartyTemplate[];
|
public partyTemplates: TrainerPartyTemplate[];
|
||||||
public partyTemplateFunc: PartyTemplateFunc;
|
public partyTemplateFunc: PartyTemplateFunc;
|
||||||
public eventRewardFuncs: ModifierTypeFunc[] = [];
|
|
||||||
public partyMemberFuncs: PartyMemberFuncs = {};
|
public partyMemberFuncs: PartyMemberFuncs = {};
|
||||||
public speciesPools: TrainerTierPools;
|
public speciesPools: TrainerTierPools;
|
||||||
public speciesFilter: PokemonSpeciesFilter;
|
public speciesFilter: PokemonSpeciesFilter;
|
||||||
@ -517,16 +516,6 @@ export class TrainerConfig {
|
|||||||
// return ret;
|
// return ret;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets eventRewardFuncs to the active event rewards for the specified wave
|
|
||||||
* @param wave Associated with {@linkcode getFixedBattleEventRewards}
|
|
||||||
* @returns this
|
|
||||||
*/
|
|
||||||
setEventModifierRewardFuncs(wave: number): TrainerConfig {
|
|
||||||
this.eventRewardFuncs = timedEventManager.getFixedBattleEventRewards(wave).map(r => modifierTypes[r]);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
|
setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
|
||||||
this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => {
|
this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => {
|
||||||
const modifierTypeFunc = func();
|
const modifierTypeFunc = func();
|
||||||
@ -1329,7 +1318,16 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
[TrainerPoolTier.RARE]: [Species.BELLOSSOM, Species.HITMONTOP, Species.MIME_JR, Species.ORICORIO],
|
[TrainerPoolTier.RARE]: [Species.BELLOSSOM, Species.HITMONTOP, Species.MIME_JR, Species.ORICORIO],
|
||||||
[TrainerPoolTier.SUPER_RARE]: [Species.QUAXLY, Species.JANGMO_O],
|
[TrainerPoolTier.SUPER_RARE]: [Species.QUAXLY, Species.JANGMO_O],
|
||||||
}),
|
}),
|
||||||
[TrainerType.DEPOT_AGENT]: new TrainerConfig(++t).setMoneyMultiplier(1.45).setEncounterBgm(TrainerType.CLERK),
|
[TrainerType.DEPOT_AGENT]: new TrainerConfig(++t)
|
||||||
|
.setMoneyMultiplier(1.45)
|
||||||
|
.setEncounterBgm(TrainerType.CLERK)
|
||||||
|
.setPartyTemplates(
|
||||||
|
trainerPartyTemplates.TWO_AVG,
|
||||||
|
trainerPartyTemplates.THREE_WEAK,
|
||||||
|
trainerPartyTemplates.THREE_AVG,
|
||||||
|
trainerPartyTemplates.FOUR_WEAK,
|
||||||
|
)
|
||||||
|
.setSpeciesFilter(s => s.isOfType(PokemonType.GROUND)),
|
||||||
[TrainerType.DOCTOR]: new TrainerConfig(++t)
|
[TrainerType.DOCTOR]: new TrainerConfig(++t)
|
||||||
.setHasGenders("Nurse", "lass")
|
.setHasGenders("Nurse", "lass")
|
||||||
.setHasDouble("Medical Team")
|
.setHasDouble("Medical Team")
|
||||||
@ -1380,7 +1378,6 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
Species.CHINCHOU,
|
Species.CHINCHOU,
|
||||||
Species.CORSOLA,
|
Species.CORSOLA,
|
||||||
Species.WAILMER,
|
Species.WAILMER,
|
||||||
Species.BARBOACH,
|
|
||||||
Species.CLAMPERL,
|
Species.CLAMPERL,
|
||||||
Species.LUVDISC,
|
Species.LUVDISC,
|
||||||
Species.MANTYKE,
|
Species.MANTYKE,
|
||||||
@ -3692,7 +3689,6 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
() => modifierTypes.SUPER_EXP_CHARM,
|
() => modifierTypes.SUPER_EXP_CHARM,
|
||||||
() => modifierTypes.EXP_SHARE,
|
() => modifierTypes.EXP_SHARE,
|
||||||
)
|
)
|
||||||
.setEventModifierRewardFuncs(8)
|
|
||||||
.setPartyMemberFunc(
|
.setPartyMemberFunc(
|
||||||
0,
|
0,
|
||||||
getRandomPartyMemberFunc(
|
getRandomPartyMemberFunc(
|
||||||
@ -3760,7 +3756,6 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
.setMixedBattleBgm("battle_rival")
|
.setMixedBattleBgm("battle_rival")
|
||||||
.setPartyTemplates(trainerPartyTemplates.RIVAL_2)
|
.setPartyTemplates(trainerPartyTemplates.RIVAL_2)
|
||||||
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
|
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
|
||||||
.setEventModifierRewardFuncs(25)
|
|
||||||
.setPartyMemberFunc(
|
.setPartyMemberFunc(
|
||||||
0,
|
0,
|
||||||
getRandomPartyMemberFunc(
|
getRandomPartyMemberFunc(
|
||||||
|
@ -6,7 +6,7 @@ import { PokemonType } from "#enums/pokemon-type";
|
|||||||
import type Move from "./moves/move";
|
import type Move from "./moves/move";
|
||||||
import { AttackMove } from "./moves/move";
|
import { AttackMove } from "./moves/move";
|
||||||
import { randSeedInt } from "#app/utils";
|
import { randSeedInt } from "#app/utils";
|
||||||
import { SuppressWeatherEffectAbAttr } from "./ability";
|
import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
|
||||||
import { TerrainType, getTerrainName } from "./terrain";
|
import { TerrainType, getTerrainName } from "./terrain";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
@ -26,7 +26,7 @@ import {
|
|||||||
PostTerrainChangeAbAttr,
|
PostTerrainChangeAbAttr,
|
||||||
PostWeatherChangeAbAttr,
|
PostWeatherChangeAbAttr,
|
||||||
TerrainEventTypeChangeAbAttr,
|
TerrainEventTypeChangeAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
|
@ -136,7 +136,8 @@ import {
|
|||||||
WeakenMoveScreenTag,
|
WeakenMoveScreenTag,
|
||||||
} from "#app/data/arena-tag";
|
} from "#app/data/arena-tag";
|
||||||
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||||
import type { Ability, AbAttr } from "#app/data/ability";
|
import type { Ability } from "#app/data/abilities/ability-class";
|
||||||
|
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
|
||||||
import {
|
import {
|
||||||
StatMultiplierAbAttr,
|
StatMultiplierAbAttr,
|
||||||
BlockCritAbAttr,
|
BlockCritAbAttr,
|
||||||
@ -151,7 +152,6 @@ import {
|
|||||||
StatusEffectImmunityAbAttr,
|
StatusEffectImmunityAbAttr,
|
||||||
TypeImmunityAbAttr,
|
TypeImmunityAbAttr,
|
||||||
WeightMultiplierAbAttr,
|
WeightMultiplierAbAttr,
|
||||||
allAbilities,
|
|
||||||
applyAbAttrs,
|
applyAbAttrs,
|
||||||
applyStatMultiplierAbAttrs,
|
applyStatMultiplierAbAttrs,
|
||||||
applyPreApplyBattlerTagAbAttrs,
|
applyPreApplyBattlerTagAbAttrs,
|
||||||
@ -188,7 +188,8 @@ import {
|
|||||||
applyAllyStatMultiplierAbAttrs,
|
applyAllyStatMultiplierAbAttrs,
|
||||||
AllyStatMultiplierAbAttr,
|
AllyStatMultiplierAbAttr,
|
||||||
MoveAbilityBypassAbAttr
|
MoveAbilityBypassAbAttr
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import type PokemonData from "#app/system/pokemon-data";
|
import type PokemonData from "#app/system/pokemon-data";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
@ -844,12 +845,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
await Promise.allSettled(loadPromises);
|
await Promise.allSettled(loadPromises);
|
||||||
|
|
||||||
// Wait for the assets we queued to load to finish loading, then...
|
// This must be initiated before we queue loading, otherwise the load could have finished before
|
||||||
|
// we reach the line of code that adds the listener, causing a deadlock.
|
||||||
|
const waitOnLoadPromise = new Promise<void>(resolve => globalScene.load.once(Phaser.Loader.Events.COMPLETE, resolve));
|
||||||
|
|
||||||
if (!globalScene.load.isLoading()) {
|
if (!globalScene.load.isLoading()) {
|
||||||
globalScene.load.start();
|
globalScene.load.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the assets we queued to load to finish loading, then...
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#creating_a_promise_around_an_old_callback_api
|
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#creating_a_promise_around_an_old_callback_api
|
||||||
await new Promise(resolve => globalScene.load.once(Phaser.Loader.Events.COMPLETE, resolve));
|
await waitOnLoadPromise;
|
||||||
|
|
||||||
// With the sprites loaded, generate the animation frame information
|
// With the sprites loaded, generate the animation frame information
|
||||||
if (this.isPlayer()) {
|
if (this.isPlayer()) {
|
||||||
@ -4155,6 +4161,62 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return baseDamage;
|
return baseDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Determine the STAB multiplier for a move used against this pokemon.
|
||||||
|
*
|
||||||
|
* @param source - The attacking {@linkcode Pokemon}
|
||||||
|
* @param move - The {@linkcode Move} used in the attack
|
||||||
|
* @param ignoreSourceAbility - If `true`, ignores the attacking Pokemon's ability effects
|
||||||
|
* @param simulated - If `true`, suppresses changes to game state during the calculation
|
||||||
|
*
|
||||||
|
* @returns The STAB multiplier for the move used against this Pokemon
|
||||||
|
*/
|
||||||
|
calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number {
|
||||||
|
// If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle)
|
||||||
|
if (move.hasAttr(TypelessAttr)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const sourceTypes = source.getTypes();
|
||||||
|
const sourceTeraType = source.getTeraType();
|
||||||
|
const moveType = source.getMoveType(move);
|
||||||
|
const matchesSourceType = sourceTypes.includes(source.getMoveType(move));
|
||||||
|
const stabMultiplier = new NumberHolder(1);
|
||||||
|
if (matchesSourceType && moveType !== PokemonType.STELLAR) {
|
||||||
|
stabMultiplier.value += 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyMoveAttrs(
|
||||||
|
CombinedPledgeStabBoostAttr,
|
||||||
|
source,
|
||||||
|
this,
|
||||||
|
move,
|
||||||
|
stabMultiplier,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ignoreSourceAbility) {
|
||||||
|
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
source.isTerastallized &&
|
||||||
|
sourceTeraType === moveType &&
|
||||||
|
moveType !== PokemonType.STELLAR
|
||||||
|
) {
|
||||||
|
stabMultiplier.value += 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
source.isTerastallized &&
|
||||||
|
source.getTeraType() === PokemonType.STELLAR &&
|
||||||
|
(!source.stellarTypesBoosted.includes(moveType) ||
|
||||||
|
source.hasSpecies(Species.TERAPAGOS))
|
||||||
|
) {
|
||||||
|
stabMultiplier.value += matchesSourceType ? 0.5 : 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(stabMultiplier.value, 2.25);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the damage of an attack made by another Pokemon against this Pokemon
|
* Calculates the damage of an attack made by another Pokemon against this Pokemon
|
||||||
* @param source {@linkcode Pokemon} the attacking Pokemon
|
* @param source {@linkcode Pokemon} the attacking Pokemon
|
||||||
@ -4337,70 +4399,29 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
? 1
|
? 1
|
||||||
: this.randSeedIntRange(85, 100) / 100;
|
: this.randSeedIntRange(85, 100) / 100;
|
||||||
|
|
||||||
const sourceTypes = source.getTypes();
|
|
||||||
const sourceTeraType = source.getTeraType();
|
|
||||||
const matchesSourceType = sourceTypes.includes(moveType);
|
|
||||||
/** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */
|
/** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */
|
||||||
const stabMultiplier = new NumberHolder(1);
|
const stabMultiplier = this.calculateStabMultiplier(source, move, ignoreSourceAbility, simulated);
|
||||||
if (matchesSourceType && moveType !== PokemonType.STELLAR) {
|
|
||||||
stabMultiplier.value += 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ignoreSourceAbility) {
|
|
||||||
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyMoveAttrs(
|
|
||||||
CombinedPledgeStabBoostAttr,
|
|
||||||
source,
|
|
||||||
this,
|
|
||||||
move,
|
|
||||||
stabMultiplier,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
source.isTerastallized &&
|
|
||||||
sourceTeraType === moveType &&
|
|
||||||
moveType !== PokemonType.STELLAR
|
|
||||||
) {
|
|
||||||
stabMultiplier.value += 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
source.isTerastallized &&
|
|
||||||
source.getTeraType() === PokemonType.STELLAR &&
|
|
||||||
(!source.stellarTypesBoosted.includes(moveType) ||
|
|
||||||
source.hasSpecies(Species.TERAPAGOS))
|
|
||||||
) {
|
|
||||||
if (matchesSourceType) {
|
|
||||||
stabMultiplier.value += 0.5;
|
|
||||||
} else {
|
|
||||||
stabMultiplier.value += 0.2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stabMultiplier.value = Math.min(stabMultiplier.value, 2.25);
|
|
||||||
|
|
||||||
/** Halves damage if the attacker is using a physical attack while burned */
|
/** Halves damage if the attacker is using a physical attack while burned */
|
||||||
const burnMultiplier = new NumberHolder(1);
|
let burnMultiplier = 1;
|
||||||
if (
|
if (
|
||||||
isPhysical &&
|
isPhysical &&
|
||||||
source.status &&
|
source.status &&
|
||||||
source.status.effect === StatusEffect.BURN
|
source.status.effect === StatusEffect.BURN &&
|
||||||
|
!move.hasAttr(BypassBurnDamageReductionAttr)
|
||||||
) {
|
) {
|
||||||
if (!move.hasAttr(BypassBurnDamageReductionAttr)) {
|
const burnDamageReductionCancelled = new BooleanHolder(false);
|
||||||
const burnDamageReductionCancelled = new BooleanHolder(false);
|
if (!ignoreSourceAbility) {
|
||||||
if (!ignoreSourceAbility) {
|
applyAbAttrs(
|
||||||
applyAbAttrs(
|
BypassBurnDamageReductionAbAttr,
|
||||||
BypassBurnDamageReductionAbAttr,
|
source,
|
||||||
source,
|
burnDamageReductionCancelled,
|
||||||
burnDamageReductionCancelled,
|
simulated,
|
||||||
simulated,
|
);
|
||||||
);
|
}
|
||||||
}
|
if (!burnDamageReductionCancelled.value) {
|
||||||
if (!burnDamageReductionCancelled.value) {
|
burnMultiplier = 0.5;
|
||||||
burnMultiplier.value = 0.5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4451,9 +4472,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
glaiveRushMultiplier.value *
|
glaiveRushMultiplier.value *
|
||||||
criticalMultiplier.value *
|
criticalMultiplier.value *
|
||||||
randomMultiplier *
|
randomMultiplier *
|
||||||
stabMultiplier.value *
|
stabMultiplier *
|
||||||
typeMultiplier *
|
typeMultiplier *
|
||||||
burnMultiplier.value *
|
burnMultiplier *
|
||||||
screenMultiplier.value *
|
screenMultiplier.value *
|
||||||
hitsTagMultiplier.value *
|
hitsTagMultiplier.value *
|
||||||
mistyTerrainMultiplier,
|
mistyTerrainMultiplier,
|
||||||
|
@ -11,7 +11,7 @@ import { initEggMoves } from "#app/data/balance/egg-moves";
|
|||||||
import { initPokemonForms } from "#app/data/pokemon-forms";
|
import { initPokemonForms } from "#app/data/pokemon-forms";
|
||||||
import { initSpecies } from "#app/data/pokemon-species";
|
import { initSpecies } from "#app/data/pokemon-species";
|
||||||
import { initMoves } from "#app/data/moves/move";
|
import { initMoves } from "#app/data/moves/move";
|
||||||
import { initAbilities } from "#app/data/ability";
|
import { initAbilities } from "#app/data/abilities/ability";
|
||||||
import { initAchievements } from "#app/system/achv";
|
import { initAchievements } from "#app/system/achv";
|
||||||
import { initTrainerTypeDialogue } from "#app/data/dialogue";
|
import { initTrainerTypeDialogue } from "#app/data/dialogue";
|
||||||
import { initChallenges } from "#app/data/challenge";
|
import { initChallenges } from "#app/data/challenge";
|
||||||
|
@ -47,7 +47,7 @@ import {
|
|||||||
} from "./modifier-type";
|
} from "./modifier-type";
|
||||||
import { Color, ShadowColor } from "#enums/color";
|
import { Color, ShadowColor } from "#enums/color";
|
||||||
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
|
||||||
import { applyAbAttrs, CommanderAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, CommanderAbAttr } from "#app/data/abilities/ability";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export type ModifierPredicate = (modifier: Modifier) => boolean;
|
export type ModifierPredicate = (modifier: Modifier) => boolean;
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import { applyAbAttrs, applyPreLeaveFieldAbAttrs, PreLeaveFieldAbAttr, RunSuccessAbAttr } from "#app/data/ability";
|
import {
|
||||||
|
applyAbAttrs,
|
||||||
|
applyPreLeaveFieldAbAttrs,
|
||||||
|
PreLeaveFieldAbAttr,
|
||||||
|
RunSuccessAbAttr,
|
||||||
|
} from "#app/data/abilities/ability";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/ability";
|
import { applyPostBattleAbAttrs, PostBattleAbAttr } from "#app/data/abilities/ability";
|
||||||
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
import { GameOverPhase } from "./game-over-phase";
|
import { GameOverPhase } from "./game-over-phase";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyAbAttrs, PreventBerryUseAbAttr, HealFromBerryUseAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, PreventBerryUseAbAttr, HealFromBerryUseAbAttr } from "#app/data/abilities/ability";
|
||||||
import { CommonAnim } from "#app/data/battle-anims";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import { BerryUsedEvent } from "#app/events/battle-scene";
|
import { BerryUsedEvent } from "#app/events/battle-scene";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BattlerIndex, BattleType } from "#app/battle";
|
import { BattlerIndex, BattleType } from "#app/battle";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||||
import { applyAbAttrs, SyncEncounterNatureAbAttr, applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, SyncEncounterNatureAbAttr, applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/ability";
|
||||||
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
import { initEncounterAnims, loadEncounterAnimAssets } from "#app/data/battle-anims";
|
||||||
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
import { getCharVariantFromDialogue } from "#app/data/dialogue";
|
||||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
PostFaintAbAttr,
|
PostFaintAbAttr,
|
||||||
PostKnockOutAbAttr,
|
PostKnockOutAbAttr,
|
||||||
PostVictoryAbAttr,
|
PostVictoryAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import type { DestinyBondTag, GrudgeTag } from "#app/data/battler-tags";
|
import type { DestinyBondTag, GrudgeTag } from "#app/data/battler-tags";
|
||||||
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
||||||
import { battleSpecDialogue } from "#app/data/dialogue";
|
import { battleSpecDialogue } from "#app/data/dialogue";
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
PostDefendAbAttr,
|
PostDefendAbAttr,
|
||||||
ReflectStatusMoveAbAttr,
|
ReflectStatusMoveAbAttr,
|
||||||
TypeImmunityAbAttr,
|
TypeImmunityAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
|
||||||
import { MoveAnim } from "#app/data/battle-anims";
|
import { MoveAnim } from "#app/data/battle-anims";
|
||||||
import {
|
import {
|
||||||
|
@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import type { BattlerIndex } from "#app/battle";
|
import type { BattlerIndex } from "#app/battle";
|
||||||
import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/ability";
|
import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
|
||||||
export class MoveEndPhase extends PokemonPhase {
|
export class MoveEndPhase extends PokemonPhase {
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
PostMoveUsedAbAttr,
|
PostMoveUsedAbAttr,
|
||||||
RedirectMoveAbAttr,
|
RedirectMoveAbAttr,
|
||||||
ReduceStatusEffectDurationAbAttr,
|
ReduceStatusEffectDurationAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
import type { DelayedAttackTag } from "#app/data/arena-tag";
|
||||||
import { CommonAnim } from "#app/data/battle-anims";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, PostBiomeChangeAbAttr } from "#app/data/abilities/ability";
|
||||||
import { getRandomWeatherType } from "#app/data/weather";
|
import { getRandomWeatherType } from "#app/data/weather";
|
||||||
import { NextEncounterPhase } from "./next-encounter-phase";
|
import { NextEncounterPhase } from "./next-encounter-phase";
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import type Pokemon from "#app/field/pokemon";
|
|||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
|
||||||
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/ability";
|
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, applyPostSummonAbAttrs, CommanderAbAttr, PostSummonAbAttr } from "#app/data/abilities/ability";
|
||||||
import { ArenaTrapTag } from "#app/data/arena-tag";
|
import { ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
import { StatusEffect } from "#app/enums/status-effect";
|
import { StatusEffect } from "#app/enums/status-effect";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
BlockStatusDamageAbAttr,
|
BlockStatusDamageAbAttr,
|
||||||
PostDamageAbAttr,
|
PostDamageAbAttr,
|
||||||
ReduceBurnDamageAbAttr,
|
ReduceBurnDamageAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims";
|
import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims";
|
||||||
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
import { getStatusEffectActivationText } from "#app/data/status-effect";
|
||||||
import { BattleSpec } from "#app/enums/battle-spec";
|
import { BattleSpec } from "#app/enums/battle-spec";
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
ClearTerrainAbAttr,
|
ClearTerrainAbAttr,
|
||||||
ClearWeatherAbAttr,
|
ClearWeatherAbAttr,
|
||||||
PostTeraFormChangeStatChangeAbAttr,
|
PostTeraFormChangeStatChangeAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
|
|
||||||
export class QuietFormChangePhase extends BattlePhase {
|
export class QuietFormChangePhase extends BattlePhase {
|
||||||
protected pokemon: Pokemon;
|
protected pokemon: Pokemon;
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
ReflectStatStageChangeAbAttr,
|
ReflectStatStageChangeAbAttr,
|
||||||
StatStageChangeCopyAbAttr,
|
StatStageChangeCopyAbAttr,
|
||||||
StatStageChangeMultiplierAbAttr,
|
StatStageChangeMultiplierAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import { ArenaTagSide, MistTag } from "#app/data/arena-tag";
|
import { ArenaTagSide, MistTag } from "#app/data/arena-tag";
|
||||||
import type { ArenaTag } from "#app/data/arena-tag";
|
import type { ArenaTag } from "#app/data/arena-tag";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
@ -13,7 +13,7 @@ import { PostSummonPhase } from "./post-summon-phase";
|
|||||||
import { GameOverPhase } from "./game-over-phase";
|
import { GameOverPhase } from "./game-over-phase";
|
||||||
import { ShinySparklePhase } from "./shiny-sparkle-phase";
|
import { ShinySparklePhase } from "./shiny-sparkle-phase";
|
||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/ability";
|
import { applyPreSummonAbAttrs, PreSummonAbAttr } from "#app/data/abilities/ability";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
export class SummonPhase extends PartyMemberPokemonPhase {
|
export class SummonPhase extends PartyMemberPokemonPhase {
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
PostDamageForceSwitchAbAttr,
|
PostDamageForceSwitchAbAttr,
|
||||||
PreSummonAbAttr,
|
PreSummonAbAttr,
|
||||||
PreSwitchOutAbAttr,
|
PreSwitchOutAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move";
|
import { allMoves, ForceSwitchOutAttr } from "#app/data/moves/move";
|
||||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||||
|
@ -26,12 +26,6 @@ export class TrainerVictoryPhase extends BattlePhase {
|
|||||||
globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc));
|
globalScene.unshiftPhase(new ModifierRewardPhase(modifierRewardFunc));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timedEventManager.isEventActive()) {
|
|
||||||
for (const rewardFunc of globalScene.currentBattle.trainer?.config.eventRewardFuncs!) {
|
|
||||||
globalScene.unshiftPhase(new ModifierRewardPhase(rewardFunc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct?
|
const trainerType = globalScene.currentBattle.trainer?.config.trainerType!; // TODO: is this bang correct?
|
||||||
// Validate Voucher for boss trainers
|
// Validate Voucher for boss trainers
|
||||||
if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
|
if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/ability";
|
import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/abilities/ability";
|
||||||
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
import { BattlerTagLapseType } from "#app/data/battler-tags";
|
||||||
import { TerrainType } from "#app/data/terrain";
|
import { TerrainType } from "#app/data/terrain";
|
||||||
import { WeatherType } from "#app/enums/weather-type";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, BypassSpeedChanceAbAttr, PreventBypassSpeedChanceAbAttr } from "#app/data/abilities/ability";
|
||||||
import { allMoves, MoveHeaderAttr } from "#app/data/moves/move";
|
import { allMoves, MoveHeaderAttr } from "#app/data/moves/move";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Stat } from "#app/enums/stat";
|
import { Stat } from "#app/enums/stat";
|
||||||
|
@ -13,6 +13,7 @@ import { SelectModifierPhase } from "./select-modifier-phase";
|
|||||||
import { TrainerVictoryPhase } from "./trainer-victory-phase";
|
import { TrainerVictoryPhase } from "./trainer-victory-phase";
|
||||||
import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { timedEventManager } from "#app/global-event-manager";
|
||||||
|
|
||||||
export class VictoryPhase extends PokemonPhase {
|
export class VictoryPhase extends PokemonPhase {
|
||||||
/** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */
|
/** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */
|
||||||
@ -53,12 +54,20 @@ export class VictoryPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
|
if (globalScene.gameMode.isEndless || !globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) {
|
||||||
globalScene.pushPhase(new EggLapsePhase());
|
globalScene.pushPhase(new EggLapsePhase());
|
||||||
if (
|
if (globalScene.gameMode.isClassic) {
|
||||||
globalScene.gameMode.isClassic &&
|
switch (globalScene.currentBattle.waveIndex) {
|
||||||
globalScene.currentBattle.waveIndex === ClassicFixedBossWaves.EVIL_BOSS_2
|
case ClassicFixedBossWaves.RIVAL_1:
|
||||||
) {
|
case ClassicFixedBossWaves.RIVAL_2:
|
||||||
// Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop
|
// Get event modifiers for this wave
|
||||||
globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE));
|
timedEventManager
|
||||||
|
.getFixedBattleEventRewards(globalScene.currentBattle.waveIndex)
|
||||||
|
.map(r => globalScene.pushPhase(new ModifierRewardPhase(modifierTypes[r])));
|
||||||
|
break;
|
||||||
|
case ClassicFixedBossWaves.EVIL_BOSS_2:
|
||||||
|
// Should get Lock Capsule on 165 before shop phase so it can be used in the rewards shop
|
||||||
|
globalScene.pushPhase(new ModifierRewardPhase(modifierTypes.LOCK_CAPSULE));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (globalScene.currentBattle.waveIndex % 10) {
|
if (globalScene.currentBattle.waveIndex % 10) {
|
||||||
globalScene.pushPhase(new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers()));
|
globalScene.pushPhase(new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers()));
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
BlockNonDirectDamageAbAttr,
|
BlockNonDirectDamageAbAttr,
|
||||||
applyPostWeatherLapseAbAttrs,
|
applyPostWeatherLapseAbAttrs,
|
||||||
PostWeatherLapseAbAttr,
|
PostWeatherLapseAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import { CommonAnim } from "#app/data/battle-anims";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import type { Weather } from "#app/data/weather";
|
import type { Weather } from "#app/data/weather";
|
||||||
import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
import { getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
||||||
|
@ -5,7 +5,7 @@ import { getVariantTint, getVariantIcon } from "#app/sprites/variant";
|
|||||||
import { argbFromRgba } from "@material/material-color-utilities";
|
import { argbFromRgba } from "@material/material-color-utilities";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { starterColors } from "#app/battle-scene";
|
import { starterColors } from "#app/battle-scene";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
||||||
import { GrowthRate, getGrowthRateColor } from "#app/data/exp";
|
import { GrowthRate, getGrowthRateColor } from "#app/data/exp";
|
||||||
import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender";
|
import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender";
|
||||||
|
@ -6,7 +6,7 @@ import type { OptionSelectItem } from "./abstact-option-select-ui-handler";
|
|||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
import { Mode } from "./ui";
|
import { Mode } from "./ui";
|
||||||
import { FilterTextRow } from "./filter-text";
|
import { FilterTextRow } from "./filter-text";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves } from "#app/data/moves/move";
|
||||||
import { allSpecies } from "#app/data/pokemon-species";
|
import { allSpecies } from "#app/data/pokemon-species";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
@ -36,7 +36,7 @@ import type { Nature } from "#enums/nature";
|
|||||||
import { addWindow } from "./ui-theme";
|
import { addWindow } from "./ui-theme";
|
||||||
import type { OptionSelectConfig } from "./abstact-option-select-ui-handler";
|
import type { OptionSelectConfig } from "./abstact-option-select-ui-handler";
|
||||||
import { FilterText, FilterTextRow } from "./filter-text";
|
import { FilterText, FilterTextRow } from "./filter-text";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
import { starterPassiveAbilities } from "#app/data/balance/passives";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves } from "#app/data/moves/move";
|
||||||
import { speciesTmMoves } from "#app/data/balance/tms";
|
import { speciesTmMoves } from "#app/data/balance/tms";
|
||||||
|
@ -8,8 +8,8 @@ import i18next from "i18next";
|
|||||||
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
|
||||||
import { starterColors } from "#app/battle-scene";
|
import { starterColors } from "#app/battle-scene";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import type { Ability } from "#app/data/ability";
|
import type { Ability } from "#app/data/abilities/ability-class";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
import { speciesEggMoves } from "#app/data/balance/egg-moves";
|
||||||
import { GrowthRate, getGrowthRateColor } from "#app/data/exp";
|
import { GrowthRate, getGrowthRateColor } from "#app/data/exp";
|
||||||
import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender";
|
import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender";
|
||||||
|
@ -31,7 +31,7 @@ import { loggedInUser } from "#app/account";
|
|||||||
import type { Variant } from "#app/sprites/variant";
|
import type { Variant } from "#app/sprites/variant";
|
||||||
import { getVariantTint } from "#app/sprites/variant";
|
import { getVariantTint } from "#app/sprites/variant";
|
||||||
import { Button } from "#enums/buttons";
|
import { Button } from "#enums/buttons";
|
||||||
import type { Ability } from "#app/data/ability";
|
import type { Ability } from "#app/data/abilities/ability-class";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { modifierSortFunc } from "#app/modifier/modifier";
|
import { modifierSortFunc } from "#app/modifier/modifier";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Stat } from "#app/enums/stat";
|
import { Stat } from "#app/enums/stat";
|
||||||
import { WeatherType } from "#app/enums/weather-type";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
|
@ -9,7 +9,7 @@ import Phaser from "phaser";
|
|||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves } from "#app/data/moves/move";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
|
|
||||||
describe("Abilities - Flower Veil", () => {
|
describe("Abilities - Flower Veil", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { WeatherType } from "#app/enums/weather-type";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
|
||||||
|
@ -5,7 +5,7 @@ import GameManager from "#test/testUtils/gameManager";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves } from "#app/data/moves/move";
|
||||||
import { MoveCategory } from "#enums/MoveCategory";
|
import { MoveCategory } from "#enums/MoveCategory";
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
|
97
test/abilities/healer.test.ts
Normal file
97
test/abilities/healer.test.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
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 { isNullOrUndefined } from "#app/utils";
|
||||||
|
import { PostTurnResetStatusAbAttr } from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
|
import type Pokemon from "#app/field/pokemon";
|
||||||
|
|
||||||
|
describe("Abilities - Healer", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
let healerAttrSpy: MockInstance;
|
||||||
|
let healerAttr: PostTurnResetStatusAbAttr;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
healerAttrSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([Moves.SPLASH])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("double")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
|
||||||
|
healerAttr = allAbilities[Abilities.HEALER].getAttrs(PostTurnResetStatusAbAttr)[0];
|
||||||
|
healerAttrSpy = vi
|
||||||
|
.spyOn(healerAttr, "getCondition")
|
||||||
|
.mockReturnValue((pokemon: Pokemon) => !isNullOrUndefined(pokemon.getAlly()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not queue a message phase for healing if the ally has fainted", async () => {
|
||||||
|
game.override.moveset([Moves.SPLASH, Moves.LUNAR_DANCE]);
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP]);
|
||||||
|
const user = game.scene.getPlayerPokemon()!;
|
||||||
|
// Only want one magikarp to have the ability.
|
||||||
|
vi.spyOn(user, "getAbility").mockReturnValue(allAbilities[Abilities.HEALER]);
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
// faint the ally
|
||||||
|
game.move.select(Moves.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 () => {
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP]);
|
||||||
|
const [user, ally] = game.scene.getPlayerField();
|
||||||
|
// Only want one magikarp to have the ability.
|
||||||
|
vi.spyOn(user, "getAbility").mockReturnValue(allAbilities[Abilities.HEALER]);
|
||||||
|
expect(ally.trySetStatus(StatusEffect.BURN)).toBe(true);
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(ally.status?.effect, "status effect was not healed").toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Healer is currently checked before the
|
||||||
|
it.todo("should heal a burn before its end of turn damage", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP]);
|
||||||
|
const [user, ally] = game.scene.getPlayerField();
|
||||||
|
// Only want one magikarp to have the ability.
|
||||||
|
vi.spyOn(user, "getAbility").mockReturnValue(allAbilities[Abilities.HEALER]);
|
||||||
|
expect(ally.trySetStatus(StatusEffect.BURN)).toBe(true);
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(ally.status?.effect, "status effect was not healed").toBeFalsy();
|
||||||
|
expect(ally.hp).toBe(ally.getMaxHp());
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves } from "#app/data/moves/move";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import type { CommandPhase } from "#app/phases/command-phase";
|
import type { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { Command } from "#app/ui/command-ui-handler";
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
import { PostSummonWeatherChangeAbAttr } from "#app/data/ability";
|
import { PostSummonWeatherChangeAbAttr } from "#app/data/abilities/ability";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { allAbilities, BypassSpeedChanceAbAttr } from "#app/data/ability";
|
import { BypassSpeedChanceAbAttr } from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { FaintPhase } from "#app/phases/faint-phase";
|
import { FaintPhase } from "#app/phases/faint-phase";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { StatMultiplierAbAttr, allAbilities } from "#app/data/ability";
|
import { StatMultiplierAbAttr } from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||||
|
@ -34,7 +34,7 @@ describe("Abilities - Sheer Force", () => {
|
|||||||
.disableCrits();
|
.disableCrits();
|
||||||
});
|
});
|
||||||
|
|
||||||
const SHEER_FORCE_MULT = 5461 / 4096;
|
const SHEER_FORCE_MULT = 1.3;
|
||||||
|
|
||||||
it("Sheer Force should boost the power of the move but disable secondary effects", async () => {
|
it("Sheer Force should boost the power of the move but disable secondary effects", async () => {
|
||||||
game.override.moveset([Moves.AIR_SLASH]);
|
game.override.moveset([Moves.AIR_SLASH]);
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
applyPreDefendAbAttrs,
|
applyPreDefendAbAttrs,
|
||||||
IgnoreMoveEffectsAbAttr,
|
IgnoreMoveEffectsAbAttr,
|
||||||
MoveEffectChanceMultiplierAbAttr,
|
MoveEffectChanceMultiplierAbAttr,
|
||||||
} from "#app/data/ability";
|
} from "#app/data/abilities/ability";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { NumberHolder } from "#app/utils";
|
import { NumberHolder } from "#app/utils";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { allMoves } from "#app/data/moves/move";
|
import { allMoves } from "#app/data/moves/move";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { PostItemLostAbAttr } from "#app/data/ability";
|
import { PostItemLostAbAttr } from "#app/data/abilities/ability";
|
||||||
import { allMoves, StealHeldItemChanceAttr } from "#app/data/moves/move";
|
import { allMoves, StealHeldItemChanceAttr } from "#app/data/moves/move";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier";
|
import type { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { Stat } from "#app/enums/stat";
|
import { Stat } from "#app/enums/stat";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
@ -89,7 +89,7 @@ describe("Moves - Burning Jealousy", () => {
|
|||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(allMoves[Moves.BURNING_JEALOUSY].calculateBattlePower).toHaveReturnedWith(
|
expect(allMoves[Moves.BURNING_JEALOUSY].calculateBattlePower).toHaveReturnedWith(
|
||||||
(allMoves[Moves.BURNING_JEALOUSY].power * 5461) / 4096,
|
allMoves[Moves.BURNING_JEALOUSY].power * 1.3,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||||
|
@ -65,23 +65,4 @@ describe("Moves - Order Up", () => {
|
|||||||
affectedStats.forEach(st => expect(dondozo.getStatStage(st)).toBe(st === stat ? 3 : 2));
|
affectedStats.forEach(st => expect(dondozo.getStatStage(st)).toBe(st === stat ? 3 : 2));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
it("should be boosted by Sheer Force while still applying a stat boost", async () => {
|
|
||||||
game.override.passiveAbility(Abilities.SHEER_FORCE).starterForms({ [Species.TATSUGIRI]: 0 });
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([Species.TATSUGIRI, Species.DONDOZO]);
|
|
||||||
|
|
||||||
const [tatsugiri, dondozo] = game.scene.getPlayerField();
|
|
||||||
|
|
||||||
expect(game.scene.triggerPokemonBattleAnim).toHaveBeenLastCalledWith(tatsugiri, PokemonAnimType.COMMANDER_APPLY);
|
|
||||||
expect(dondozo.getTag(BattlerTagType.COMMANDED)).toBeDefined();
|
|
||||||
|
|
||||||
game.move.select(Moves.ORDER_UP, 1, BattlerIndex.ENEMY);
|
|
||||||
expect(game.scene.currentBattle.turnCommands[0]?.skip).toBeTruthy();
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
|
|
||||||
expect(dondozo.battleData.abilitiesApplied.includes(Abilities.SHEER_FORCE)).toBeTruthy();
|
|
||||||
expect(dondozo.getStatStage(Stat.ATK)).toBe(3);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
import { allMoves, FlinchAttr } from "#app/data/moves/move";
|
import { allMoves, FlinchAttr } from "#app/data/moves/move";
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { PokemonType } from "#enums/pokemon-type";
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { allAbilities, PostDefendContactApplyStatusEffectAbAttr } from "#app/data/ability";
|
import { PostDefendContactApplyStatusEffectAbAttr } from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { StatusEffect } from "#app/enums/status-effect";
|
import { StatusEffect } from "#app/enums/status-effect";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
@ -11,7 +11,8 @@ import { StatusEffect } from "#enums/status-effect";
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
import { ArenaTagSide } from "#app/data/arena-tag";
|
import { ArenaTagSide } from "#app/data/arena-tag";
|
||||||
import { allAbilities, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability";
|
import { MoveEffectChanceMultiplierAbAttr } from "#app/data/abilities/ability";
|
||||||
|
import { allAbilities } from "#app/data/data-lists";
|
||||||
|
|
||||||
describe("Moves - Secret Power", () => {
|
describe("Moves - Secret Power", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
|
65
test/moves/struggle.test.ts
Normal file
65
test/moves/struggle.test.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Struggle", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([Moves.SPLASH])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.battleType("single")
|
||||||
|
.disableCrits()
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not have its power boosted by adaptability or stab", async () => {
|
||||||
|
game.override.moveset([Moves.STRUGGLE]).ability(Abilities.ADAPTABILITY);
|
||||||
|
await game.classicMode.startBattle([Species.RATTATA]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
game.move.select(Moves.STRUGGLE);
|
||||||
|
|
||||||
|
const stabSpy = vi.spyOn(enemy, "calculateStabMultiplier");
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(stabSpy).toHaveReturnedWith(1);
|
||||||
|
|
||||||
|
stabSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ignore type effectiveness", async () => {
|
||||||
|
game.override.moveset([Moves.STRUGGLE]);
|
||||||
|
await game.classicMode.startBattle([Species.GASTLY]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
game.move.select(Moves.STRUGGLE);
|
||||||
|
|
||||||
|
const moveEffectivenessSpy = vi.spyOn(enemy, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(moveEffectivenessSpy).toHaveReturnedWith(1);
|
||||||
|
|
||||||
|
moveEffectivenessSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
@ -69,6 +69,40 @@ describe("Moves - Tera Starstorm", () => {
|
|||||||
expect(enemyField.every(pokemon => pokemon.isFullHp())).toBe(false);
|
expect(enemyField.every(pokemon => pokemon.isFullHp())).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("targets both opponents in a double battle when used by Terapagos immediately after terastallizing", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.TERAPAGOS]);
|
||||||
|
|
||||||
|
const terapagos = game.scene.getPlayerParty()[0];
|
||||||
|
terapagos.isTerastallized = false;
|
||||||
|
|
||||||
|
game.move.selectWithTera(Moves.TERA_STARSTORM, 0);
|
||||||
|
|
||||||
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
||||||
|
|
||||||
|
const enemyField = game.scene.getEnemyField();
|
||||||
|
|
||||||
|
// Terapagos in Stellar Form should hit both targets
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(enemyField.some(pokemon => pokemon.isFullHp())).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("targets only one opponent in a double battle when used by Terapagos without terastallizing", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.TERAPAGOS]);
|
||||||
|
|
||||||
|
const terapagos = game.scene.getPlayerParty()[0];
|
||||||
|
terapagos.isTerastallized = false;
|
||||||
|
|
||||||
|
game.move.select(Moves.TERA_STARSTORM, 0, BattlerIndex.ENEMY);
|
||||||
|
|
||||||
|
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
|
||||||
|
|
||||||
|
const enemyField = game.scene.getEnemyField();
|
||||||
|
|
||||||
|
// Terapagos in Stellar Form should hit both targets
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(enemyField.some(pokemon => pokemon.isFullHp())).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("applies the effects when Terapagos in Stellar Form is fused with another Pokemon", async () => {
|
it("applies the effects when Terapagos in Stellar Form is fused with another Pokemon", async () => {
|
||||||
await game.classicMode.startBattle([Species.TERAPAGOS, Species.CHARMANDER, Species.MAGIKARP]);
|
await game.classicMode.startBattle([Species.TERAPAGOS, Species.CHARMANDER, Species.MAGIKARP]);
|
||||||
|
|
||||||
|
@ -65,6 +65,33 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the move to be used by the given Pokemon(-index), **which will also terastallize on this turn**.
|
||||||
|
* Triggers during the next {@linkcode CommandPhase}
|
||||||
|
* @param move - the move to use
|
||||||
|
* @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0)
|
||||||
|
* @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required
|
||||||
|
*/
|
||||||
|
public selectWithTera(move: Moves, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null) {
|
||||||
|
const movePosition = getMovePosition(this.game.scene, pkmIndex, move);
|
||||||
|
this.game.scene.getPlayerParty()[pkmIndex].isTerastallized = false;
|
||||||
|
|
||||||
|
this.game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||||
|
this.game.scene.ui.setMode(
|
||||||
|
Mode.FIGHT,
|
||||||
|
(this.game.scene.getCurrentPhase() as CommandPhase).getFieldIndex(),
|
||||||
|
Command.TERA,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||||
|
(this.game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.TERA, movePosition, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetIndex !== null) {
|
||||||
|
this.game.selectTarget(movePosition, targetIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forces the Paralysis or Freeze status to activate on the next move by temporarily mocking {@linkcode Overrides.STATUS_ACTIVATION_OVERRIDE},
|
* Forces the Paralysis or Freeze status to activate on the next move by temporarily mocking {@linkcode Overrides.STATUS_ACTIVATION_OVERRIDE},
|
||||||
* advancing to the next `MovePhase`, and then resetting the override to `null`
|
* advancing to the next `MovePhase`, and then resetting the override to `null`
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SESSION_ID_COOKIE_NAME } from "#app/constants";
|
import { SESSION_ID_COOKIE_NAME } from "#app/constants";
|
||||||
import { initLoggedInUser } from "#app/account";
|
import { initLoggedInUser } from "#app/account";
|
||||||
import { initAbilities } from "#app/data/ability";
|
import { initAbilities } from "#app/data/abilities/ability";
|
||||||
import { initBiomes } from "#app/data/balance/biomes";
|
import { initBiomes } from "#app/data/balance/biomes";
|
||||||
import { initEggMoves } from "#app/data/balance/egg-moves";
|
import { initEggMoves } from "#app/data/balance/egg-moves";
|
||||||
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
|
||||||
|
Loading…
Reference in New Issue
Block a user