Merge remote-tracking branch 'upstream/beta' into move-power-trick
78
.github/workflows/tests.yml
vendored
@ -15,8 +15,8 @@ on:
|
|||||||
types: [checks_requested]
|
types: [checks_requested]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-tests: # Define a job named "run-tests"
|
run-misc-tests: # Define a job named "run-tests"
|
||||||
name: Run tests # Human-readable name for the job
|
name: Run misc tests # Human-readable name for the job
|
||||||
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
|
runs-on: ubuntu-latest # Specify the latest Ubuntu runner for the job
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -31,5 +31,75 @@ jobs:
|
|||||||
- 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
|
||||||
|
|
||||||
- name: tests # Step to run tests
|
- name: pre-test # pre-test to check overrides
|
||||||
run: npm run test:silent
|
run: npx vitest run --project pre
|
||||||
|
- name: test misc
|
||||||
|
run: npx vitest --project misc
|
||||||
|
|
||||||
|
run-abilities-tests:
|
||||||
|
name: Run abilities tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: pre-test
|
||||||
|
run: npx vitest run --project pre
|
||||||
|
- name: test abilities
|
||||||
|
run: npx vitest --project abilities
|
||||||
|
|
||||||
|
run-items-tests:
|
||||||
|
name: Run items tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: pre-test
|
||||||
|
run: npx vitest run --project pre
|
||||||
|
- name: test items
|
||||||
|
run: npx vitest --project items
|
||||||
|
|
||||||
|
run-moves-tests:
|
||||||
|
name: Run moves tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: pre-test
|
||||||
|
run: npx vitest run --project pre
|
||||||
|
- name: test moves
|
||||||
|
run: npx vitest --project moves
|
||||||
|
|
||||||
|
run-battle-tests:
|
||||||
|
name: Run battle tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out Git repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: pre-test
|
||||||
|
run: npx vitest run --project pre
|
||||||
|
- name: test battle
|
||||||
|
run: npx vitest --project battle
|
@ -4,7 +4,8 @@ import { fileURLToPath } from 'url';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This script creates a test boilerplate file for a move or ability.
|
* This script creates a test boilerplate file for a move or ability.
|
||||||
* @param {string} type - The type of test to create. Either "move" or "ability".
|
* @param {string} type - The type of test to create. Either "move", "ability",
|
||||||
|
* or "item".
|
||||||
* @param {string} fileName - The name of the file to create.
|
* @param {string} fileName - The name of the file to create.
|
||||||
* @example npm run create-test move tackle
|
* @example npm run create-test move tackle
|
||||||
*/
|
*/
|
||||||
@ -19,7 +20,7 @@ const type = args[0]; // "move" or "ability"
|
|||||||
let fileName = args[1]; // The file name
|
let fileName = args[1]; // The file name
|
||||||
|
|
||||||
if (!type || !fileName) {
|
if (!type || !fileName) {
|
||||||
console.error('Please provide both a type ("move" or "ability") and a file name.');
|
console.error('Please provide both a type ("move", "ability", or "item") and a file name.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,8 +41,11 @@ if (type === 'move') {
|
|||||||
} else if (type === 'ability') {
|
} else if (type === 'ability') {
|
||||||
dir = path.join(__dirname, 'src', 'test', 'abilities');
|
dir = path.join(__dirname, 'src', 'test', 'abilities');
|
||||||
description = `Abilities - ${formattedName}`;
|
description = `Abilities - ${formattedName}`;
|
||||||
|
} else if (type === "item") {
|
||||||
|
dir = path.join(__dirname, 'src', 'test', 'items');
|
||||||
|
description = `Items - ${formattedName}`;
|
||||||
} else {
|
} else {
|
||||||
console.error('Invalid type. Please use "move" or "ability".');
|
console.error('Invalid type. Please use "move", "ability", or "item".');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
lefthook.yml
@ -2,6 +2,15 @@ pre-commit:
|
|||||||
parallel: true
|
parallel: true
|
||||||
commands:
|
commands:
|
||||||
eslint:
|
eslint:
|
||||||
glob: '*.{js,jsx,ts,tsx}'
|
glob: "*.{js,jsx,ts,tsx}"
|
||||||
run: npx eslint --fix {staged_files}
|
run: npx eslint --fix {staged_files}
|
||||||
stage_fixed: true
|
stage_fixed: true
|
||||||
|
skip:
|
||||||
|
- merge
|
||||||
|
- rebase
|
||||||
|
|
||||||
|
pre-push:
|
||||||
|
commands:
|
||||||
|
eslint:
|
||||||
|
glob: "*.{js,ts,jsx,tsx}"
|
||||||
|
run: npx eslint --fix {push_files}
|
22
public/images/pokemon/variant/465.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"1": {
|
||||||
|
"529cc5": "8153c7",
|
||||||
|
"d65a94": "5ad662",
|
||||||
|
"3a73ad": "6b3aad",
|
||||||
|
"bd216b": "21bd69",
|
||||||
|
"5a193a": "195a2a",
|
||||||
|
"193a63": "391963",
|
||||||
|
"295a84": "472984"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"529cc5": "ffedb6",
|
||||||
|
"d65a94": "e67d2f",
|
||||||
|
"3a73ad": "ebc582",
|
||||||
|
"bd216b": "b35131",
|
||||||
|
"31313a": "3d1519",
|
||||||
|
"5a193a": "752e2e",
|
||||||
|
"193a63": "705040",
|
||||||
|
"295a84": "ad875a",
|
||||||
|
"4a4a52": "57211a"
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 43 KiB |
@ -1691,8 +1691,8 @@
|
|||||||
],
|
],
|
||||||
"465": [
|
"465": [
|
||||||
0,
|
0,
|
||||||
2,
|
1,
|
||||||
2
|
1
|
||||||
],
|
],
|
||||||
"466": [
|
"466": [
|
||||||
1,
|
1,
|
||||||
@ -3980,6 +3980,11 @@
|
|||||||
1,
|
1,
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
|
"465": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
"592": [
|
"592": [
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
@ -5690,7 +5695,7 @@
|
|||||||
"465": [
|
"465": [
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
2
|
1
|
||||||
],
|
],
|
||||||
"466": [
|
"466": [
|
||||||
2,
|
2,
|
||||||
@ -8008,6 +8013,11 @@
|
|||||||
1,
|
1,
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
|
"465": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
],
|
||||||
"592": [
|
"592": [
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
@ -8,5 +8,14 @@
|
|||||||
"bd216b": "21bd69",
|
"bd216b": "21bd69",
|
||||||
"31313a": "31313a",
|
"31313a": "31313a",
|
||||||
"d65a94": "5ad662"
|
"d65a94": "5ad662"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"5a193a": "752e2e",
|
||||||
|
"31313a": "3d1519",
|
||||||
|
"d65a94": "e67d2f",
|
||||||
|
"3a73ad": "ebc582",
|
||||||
|
"295a84": "ad875a",
|
||||||
|
"bd216b": "b35131",
|
||||||
|
"193a63": "705040"
|
||||||
}
|
}
|
||||||
}
|
}
|
Before Width: | Height: | Size: 34 KiB |
21
public/images/pokemon/variant/back/female/465.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"1": {
|
||||||
|
"193a63": "391963",
|
||||||
|
"295a84": "472984",
|
||||||
|
"3a73ad": "6b3aad",
|
||||||
|
"000000": "000000",
|
||||||
|
"5a193a": "195a2a",
|
||||||
|
"bd216b": "21bd69",
|
||||||
|
"31313a": "31313a",
|
||||||
|
"d65a94": "5ad662"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"5a193a": "752e2e",
|
||||||
|
"31313a": "3d1519",
|
||||||
|
"d65a94": "e67d2f",
|
||||||
|
"3a73ad": "ebc582",
|
||||||
|
"295a84": "ad875a",
|
||||||
|
"bd216b": "b35131",
|
||||||
|
"193a63": "705040"
|
||||||
|
}
|
||||||
|
}
|
22
public/images/pokemon/variant/female/465.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"1": {
|
||||||
|
"529cc5": "8153c7",
|
||||||
|
"d65a94": "5ad662",
|
||||||
|
"3a73ad": "6b3aad",
|
||||||
|
"bd216b": "21bd69",
|
||||||
|
"5a193a": "195a2a",
|
||||||
|
"193a63": "391963",
|
||||||
|
"295a84": "472984"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"529cc5": "ffedb6",
|
||||||
|
"d65a94": "e67d2f",
|
||||||
|
"3a73ad": "ebc582",
|
||||||
|
"bd216b": "b35131",
|
||||||
|
"31313a": "3d1519",
|
||||||
|
"5a193a": "752e2e",
|
||||||
|
"193a63": "705040",
|
||||||
|
"295a84": "ad875a",
|
||||||
|
"4a4a52": "57211a"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/images/ui/egg_summary_bg.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
public/images/ui/egg_summary_bg_blank.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
public/images/ui/icon_egg_move.png
Normal file
After Width: | Height: | Size: 179 B |
BIN
public/images/ui/legacy/egg_summary_bg.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/ui/legacy/icon_egg_move.png
Normal file
After Width: | Height: | Size: 179 B |
BIN
public/images/ui/legacy/settings_icon.png
Normal file
After Width: | Height: | Size: 261 B |
BIN
public/images/ui/settings_icon.png
Normal file
After Width: | Height: | Size: 261 B |
@ -855,7 +855,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
overrideModifiers(this, false);
|
overrideModifiers(this, false);
|
||||||
overrideHeldItems(this, pokemon, false);
|
overrideHeldItems(this, pokemon, false);
|
||||||
if (boss && !dataSource) {
|
if (boss && !dataSource) {
|
||||||
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967295));
|
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
|
||||||
|
|
||||||
for (let s = 0; s < pokemon.ivs.length; s++) {
|
for (let s = 0; s < pokemon.ivs.length; s++) {
|
||||||
pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75));
|
pokemon.ivs[s] = Math.round(Phaser.Math.Linear(Math.min(pokemon.ivs[s], secondaryIvs[s]), Math.max(pokemon.ivs[s], secondaryIvs[s]), 0.75));
|
||||||
@ -961,6 +961,16 @@ export default class BattleScene extends SceneBase {
|
|||||||
this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym();
|
this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random number using the current battle's seed
|
||||||
|
*
|
||||||
|
* This calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
|
||||||
|
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`
|
||||||
|
*
|
||||||
|
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
|
||||||
|
* @param min The minimum integer to pick, default `0`
|
||||||
|
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||||
|
*/
|
||||||
randBattleSeedInt(range: integer, min: integer = 0): integer {
|
randBattleSeedInt(range: integer, min: integer = 0): integer {
|
||||||
return this.currentBattle?.randSeedInt(this, range, min);
|
return this.currentBattle?.randSeedInt(this, range, min);
|
||||||
}
|
}
|
||||||
@ -1112,7 +1122,8 @@ export default class BattleScene extends SceneBase {
|
|||||||
doubleTrainer = false;
|
doubleTrainer = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, doubleTrainer ? TrainerVariant.DOUBLE : Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
|
const variant = doubleTrainer ? TrainerVariant.DOUBLE : (Utils.randSeedInt(2) ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT);
|
||||||
|
newTrainer = trainerData !== undefined ? trainerData.toTrainer(this) : new Trainer(this, trainerType, variant);
|
||||||
this.field.add(newTrainer);
|
this.field.add(newTrainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2620,7 +2631,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
if (mods.length < 1) {
|
if (mods.length < 1) {
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
const rand = Math.floor(Utils.randSeedInt(mods.length));
|
const rand = Utils.randSeedInt(mods.length);
|
||||||
return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))];
|
return [mods[rand], ...shuffleModifiers(mods.filter((_, i) => i !== rand))];
|
||||||
};
|
};
|
||||||
modifiers = shuffleModifiers(modifiers);
|
modifiers = shuffleModifiers(modifiers);
|
||||||
@ -2742,6 +2753,35 @@ export default class BattleScene extends SceneBase {
|
|||||||
(window as any).gameInfo = gameInfo;
|
(window as any).gameInfo = gameInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function retrieves the sprite and audio keys for active Pokemon.
|
||||||
|
* Active Pokemon include both enemy and player Pokemon of the current wave.
|
||||||
|
* Note: Questions on garbage collection go to @frutescens
|
||||||
|
* @returns a string array of active sprite and audio keys that should not be deleted
|
||||||
|
*/
|
||||||
|
getActiveKeys(): string[] {
|
||||||
|
const keys: string[] = [];
|
||||||
|
const playerParty = this.getParty();
|
||||||
|
playerParty.forEach(p => {
|
||||||
|
keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant));
|
||||||
|
keys.push("pkmn__" + p.species.getSpriteId(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant, true));
|
||||||
|
keys.push("cry/" + p.species.getCryKey(p.species.formIndex));
|
||||||
|
if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) {
|
||||||
|
keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// enemyParty has to be operated on separately from playerParty because playerPokemon =/= enemyPokemon
|
||||||
|
const enemyParty = this.getEnemyParty();
|
||||||
|
enemyParty.forEach(p => {
|
||||||
|
keys.push(p.species.getSpriteKey(p.gender === Gender.FEMALE, p.species.formIndex, p.shiny, p.variant));
|
||||||
|
keys.push("cry/" + p.species.getCryKey(p.species.formIndex));
|
||||||
|
if (p.fusionSpecies && p.getSpeciesForm() !== p.getFusionSpeciesForm()) {
|
||||||
|
keys.push("cry/"+p.getFusionSpeciesForm().getCryKey(p.fusionSpecies.formIndex));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialized the 2nd phase of the final boss (e.g. form-change for Eternatus)
|
* Initialized the 2nd phase of the final boss (e.g. form-change for Eternatus)
|
||||||
* @param pokemon The (enemy) pokemon
|
* @param pokemon The (enemy) pokemon
|
||||||
|
@ -6,7 +6,7 @@ import Trainer, { TrainerVariant } from "./field/trainer";
|
|||||||
import { GameMode } from "./game-mode";
|
import { GameMode } from "./game-mode";
|
||||||
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
|
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
|
||||||
import { PokeballType } from "./data/pokeball";
|
import { PokeballType } from "./data/pokeball";
|
||||||
import {trainerConfigs} from "#app/data/trainer-config";
|
import { trainerConfigs } from "#app/data/trainer-config";
|
||||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
import { BattleSpec } from "#enums/battle-spec";
|
import { BattleSpec } from "#enums/battle-spec";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -31,7 +31,7 @@ export enum BattlerIndex {
|
|||||||
|
|
||||||
export interface TurnCommand {
|
export interface TurnCommand {
|
||||||
command: Command;
|
command: Command;
|
||||||
cursor?: integer;
|
cursor?: number;
|
||||||
move?: QueuedMove;
|
move?: QueuedMove;
|
||||||
targets?: BattlerIndex[];
|
targets?: BattlerIndex[];
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
@ -39,38 +39,40 @@ export interface TurnCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface TurnCommands {
|
interface TurnCommands {
|
||||||
[key: integer]: TurnCommand | null
|
[key: number]: TurnCommand | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Battle {
|
export default class Battle {
|
||||||
protected gameMode: GameMode;
|
protected gameMode: GameMode;
|
||||||
public waveIndex: integer;
|
public waveIndex: number;
|
||||||
public battleType: BattleType;
|
public battleType: BattleType;
|
||||||
public battleSpec: BattleSpec;
|
public battleSpec: BattleSpec;
|
||||||
public trainer: Trainer | null;
|
public trainer: Trainer | null;
|
||||||
public enemyLevels: integer[] | undefined;
|
public enemyLevels: number[] | undefined;
|
||||||
public enemyParty: EnemyPokemon[];
|
public enemyParty: EnemyPokemon[] = [];
|
||||||
public seenEnemyPartyMemberIds: Set<integer>;
|
public seenEnemyPartyMemberIds: Set<number> = new Set<number>();
|
||||||
public double: boolean;
|
public double: boolean;
|
||||||
public started: boolean;
|
public started: boolean = false;
|
||||||
public enemySwitchCounter: integer;
|
public enemySwitchCounter: number = 0;
|
||||||
public turn: integer;
|
public turn: number = 0;
|
||||||
public turnCommands: TurnCommands;
|
public turnCommands: TurnCommands;
|
||||||
public playerParticipantIds: Set<integer>;
|
public playerParticipantIds: Set<number> = new Set<number>();
|
||||||
public battleScore: integer;
|
public battleScore: number = 0;
|
||||||
public postBattleLoot: PokemonHeldItemModifier[];
|
public postBattleLoot: PokemonHeldItemModifier[] = [];
|
||||||
public escapeAttempts: integer;
|
public escapeAttempts: number = 0;
|
||||||
public lastMove: Moves;
|
public lastMove: Moves;
|
||||||
public battleSeed: string;
|
public battleSeed: string = Utils.randomString(16, true);
|
||||||
private battleSeedState: string | null;
|
private battleSeedState: string | null = null;
|
||||||
public moneyScattered: number;
|
public moneyScattered: number = 0;
|
||||||
public lastUsedPokeball: PokeballType | null;
|
public lastUsedPokeball: PokeballType | null = null;
|
||||||
public playerFaints: number; // The amount of times pokemon on the players side have fainted
|
/** The number of times a Pokemon on the player's side has fainted this battle */
|
||||||
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
|
public playerFaints: number = 0;
|
||||||
|
/** The number of times a Pokemon on the enemy's side has fainted this battle */
|
||||||
|
public enemyFaints: number = 0;
|
||||||
|
|
||||||
private rngCounter: integer = 0;
|
private rngCounter: number = 0;
|
||||||
|
|
||||||
constructor(gameMode: GameMode, waveIndex: integer, battleType: BattleType, trainer?: Trainer, double?: boolean) {
|
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double?: boolean) {
|
||||||
this.gameMode = gameMode;
|
this.gameMode = gameMode;
|
||||||
this.waveIndex = waveIndex;
|
this.waveIndex = waveIndex;
|
||||||
this.battleType = battleType;
|
this.battleType = battleType;
|
||||||
@ -79,22 +81,7 @@ export default class Battle {
|
|||||||
this.enemyLevels = battleType !== BattleType.TRAINER
|
this.enemyLevels = battleType !== BattleType.TRAINER
|
||||||
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
? new Array(double ? 2 : 1).fill(null).map(() => this.getLevelForWave())
|
||||||
: trainer?.getPartyLevels(this.waveIndex);
|
: trainer?.getPartyLevels(this.waveIndex);
|
||||||
this.enemyParty = [];
|
this.double = double ?? false;
|
||||||
this.seenEnemyPartyMemberIds = new Set<integer>();
|
|
||||||
this.double = !!double;
|
|
||||||
this.enemySwitchCounter = 0;
|
|
||||||
this.turn = 0;
|
|
||||||
this.playerParticipantIds = new Set<integer>();
|
|
||||||
this.battleScore = 0;
|
|
||||||
this.postBattleLoot = [];
|
|
||||||
this.escapeAttempts = 0;
|
|
||||||
this.started = false;
|
|
||||||
this.battleSeed = Utils.randomString(16, true);
|
|
||||||
this.battleSeedState = null;
|
|
||||||
this.moneyScattered = 0;
|
|
||||||
this.lastUsedPokeball = null;
|
|
||||||
this.playerFaints = 0;
|
|
||||||
this.enemyFaints = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initBattleSpec(): void {
|
private initBattleSpec(): void {
|
||||||
@ -105,7 +92,7 @@ export default class Battle {
|
|||||||
this.battleSpec = spec;
|
this.battleSpec = spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLevelForWave(): integer {
|
private getLevelForWave(): number {
|
||||||
const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex);
|
const levelWaveIndex = this.gameMode.getWaveForDifficulty(this.waveIndex);
|
||||||
const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2);
|
const baseLevel = 1 + levelWaveIndex / 2 + Math.pow(levelWaveIndex / 25, 2);
|
||||||
const bossMultiplier = 1.2;
|
const bossMultiplier = 1.2;
|
||||||
@ -138,7 +125,7 @@ export default class Battle {
|
|||||||
return rand / value;
|
return rand / value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBattlerCount(): integer {
|
getBattlerCount(): number {
|
||||||
return this.double ? 2 : 1;
|
return this.double ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +354,13 @@ export default class Battle {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
randSeedInt(scene: BattleScene, range: integer, min: integer = 0): integer {
|
/**
|
||||||
|
* Generates a random number using the current battle's seed. Calls {@linkcode Utils.randSeedInt}
|
||||||
|
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
|
||||||
|
* @param min The minimum integer to pick, default `0`
|
||||||
|
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||||
|
*/
|
||||||
|
randSeedInt(scene: BattleScene, range: number, min: number = 0): number {
|
||||||
if (range <= 1) {
|
if (range <= 1) {
|
||||||
return min;
|
return min;
|
||||||
}
|
}
|
||||||
@ -392,7 +385,7 @@ export default class Battle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FixedBattle extends Battle {
|
export class FixedBattle extends Battle {
|
||||||
constructor(scene: BattleScene, waveIndex: integer, config: FixedBattleConfig) {
|
constructor(scene: BattleScene, waveIndex: number, config: FixedBattleConfig) {
|
||||||
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double);
|
super(scene.gameMode, waveIndex, config.battleType, config.battleType === BattleType.TRAINER ? config.getTrainer(scene) : undefined, config.double);
|
||||||
if (config.getEnemyParty) {
|
if (config.getEnemyParty) {
|
||||||
this.enemyParty = config.getEnemyParty(scene);
|
this.enemyParty = config.getEnemyParty(scene);
|
||||||
@ -408,7 +401,7 @@ export class FixedBattleConfig {
|
|||||||
public double: boolean;
|
public double: boolean;
|
||||||
public getTrainer: GetTrainerFunc;
|
public getTrainer: GetTrainerFunc;
|
||||||
public getEnemyParty: GetEnemyPartyFunc;
|
public getEnemyParty: GetEnemyPartyFunc;
|
||||||
public seedOffsetWaveIndex: integer;
|
public seedOffsetWaveIndex: number;
|
||||||
|
|
||||||
setBattleType(battleType: BattleType): FixedBattleConfig {
|
setBattleType(battleType: BattleType): FixedBattleConfig {
|
||||||
this.battleType = battleType;
|
this.battleType = battleType;
|
||||||
@ -430,7 +423,7 @@ export class FixedBattleConfig {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSeedOffsetWave(seedOffsetWaveIndex: integer): FixedBattleConfig {
|
setSeedOffsetWave(seedOffsetWaveIndex: number): FixedBattleConfig {
|
||||||
this.seedOffsetWaveIndex = seedOffsetWaveIndex;
|
this.seedOffsetWaveIndex = seedOffsetWaveIndex;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -476,7 +469,7 @@ function getRandomTrainerFunc(trainerPool: (TrainerType | TrainerType[])[], rand
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FixedBattleConfigs {
|
export interface FixedBattleConfigs {
|
||||||
[key: integer]: FixedBattleConfig
|
[key: number]: FixedBattleConfig
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Youngster/Lass on 5
|
* Youngster/Lass on 5
|
||||||
|
64
src/data/ability.ts
Normal file → Executable file
@ -310,7 +310,7 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
|
|||||||
|
|
||||||
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
|
||||||
constructor(moveType: Type, damageMultiplier: number) {
|
constructor(moveType: Type, damageMultiplier: number) {
|
||||||
super((user, target, move) => move.type === moveType, damageMultiplier);
|
super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +455,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(pokemon.getMoveType(move), attacker) < 2) {
|
if (move instanceof AttackMove && pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker) < 2) {
|
||||||
cancelled.value = true; // Suppresses "No Effect" message
|
cancelled.value = true; // Suppresses "No Effect" message
|
||||||
(args[0] as Utils.NumberHolder).value = 0;
|
(args[0] as Utils.NumberHolder).value = 0;
|
||||||
return true;
|
return true;
|
||||||
@ -1085,7 +1085,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||||
if (!attacker.summonData.disabledMove) {
|
if (attacker.getTag(BattlerTagType.DISABLED) === null) {
|
||||||
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) {
|
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !attacker.isMax()) {
|
||||||
if (simulated) {
|
if (simulated) {
|
||||||
return true;
|
return true;
|
||||||
@ -1093,21 +1093,12 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
|
|||||||
|
|
||||||
this.attacker = attacker;
|
this.attacker = attacker;
|
||||||
this.move = move;
|
this.move = move;
|
||||||
|
this.attacker.addTag(BattlerTagType.DISABLED, 4, 0, pokemon.id);
|
||||||
attacker.summonData.disabledMove = move.id;
|
|
||||||
attacker.summonData.disabledTurns = 4;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
|
||||||
return i18next.t("abilityTriggers:postDefendMoveDisable", {
|
|
||||||
pokemonNameWithAffix: getPokemonNameWithAffix(this.attacker),
|
|
||||||
moveName: this.move.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChangeAbAttr {
|
export class PostStatStageChangeStatStageChangeAbAttr extends PostStatStageChangeAbAttr {
|
||||||
@ -1462,7 +1453,7 @@ export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr {
|
|||||||
|
|
||||||
export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr {
|
export class MoveTypePowerBoostAbAttr extends MovePowerBoostAbAttr {
|
||||||
constructor(boostedType: Type, powerMultiplier?: number) {
|
constructor(boostedType: Type, powerMultiplier?: number) {
|
||||||
super((pokemon, defender, move) => move.type === boostedType, powerMultiplier || 1.5);
|
super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1546,7 +1537,7 @@ export class PreAttackFieldMoveTypePowerBoostAbAttr extends FieldMovePowerBoostA
|
|||||||
* @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided.
|
* @param powerMultiplier - The multiplier to apply to the move's power, defaults to 1.5 if not provided.
|
||||||
*/
|
*/
|
||||||
constructor(boostedType: Type, powerMultiplier?: number) {
|
constructor(boostedType: Type, powerMultiplier?: number) {
|
||||||
super((pokemon, defender, move) => move.type === boostedType, powerMultiplier || 1.5);
|
super((pokemon, defender, move) => pokemon?.getMoveType(move) === boostedType, powerMultiplier || 1.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2651,7 +2642,7 @@ export class ConfusionOnStatusEffectAbAttr extends PostAttackAbAttr {
|
|||||||
if (simulated) {
|
if (simulated) {
|
||||||
return defender.canAddTag(BattlerTagType.CONFUSED);
|
return defender.canAddTag(BattlerTagType.CONFUSED);
|
||||||
} else {
|
} else {
|
||||||
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedInt(3, 2), move.id, defender.id);
|
return defender.addTag(BattlerTagType.CONFUSED, pokemon.randSeedIntRange(2, 5), move.id, defender.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -5100,9 +5091,9 @@ export function initAbilities() {
|
|||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.TINTED_LENS, 4)
|
new Ability(Abilities.TINTED_LENS, 4)
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
.attr(DamageBoostAbAttr, 2, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) <= 0.5), // TODO: fix TS issues
|
.attr(DamageBoostAbAttr, 2, (user, target, move) => target?.getMoveEffectiveness(user, move) <= 0.5), // TODO: fix TS issues
|
||||||
new Ability(Abilities.FILTER, 4)
|
new Ability(Abilities.FILTER, 4)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SLOW_START, 4)
|
new Ability(Abilities.SLOW_START, 4)
|
||||||
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5),
|
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5),
|
||||||
@ -5118,7 +5109,7 @@ export function initAbilities() {
|
|||||||
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW)
|
.attr(PostWeatherLapseHealAbAttr, 1, WeatherType.HAIL, WeatherType.SNOW)
|
||||||
.partial(), // Healing not blocked by Heal Block
|
.partial(), // Healing not blocked by Heal Block
|
||||||
new Ability(Abilities.SOLID_ROCK, 4)
|
new Ability(Abilities.SOLID_ROCK, 4)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75)
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SNOW_WARNING, 4)
|
new Ability(Abilities.SNOW_WARNING, 4)
|
||||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SNOW)
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.SNOW)
|
||||||
@ -5236,10 +5227,13 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.MOXIE, 5)
|
new Ability(Abilities.MOXIE, 5)
|
||||||
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
|
.attr(PostVictoryStatStageChangeAbAttr, Stat.ATK, 1),
|
||||||
new Ability(Abilities.JUSTIFIED, 5)
|
new Ability(Abilities.JUSTIFIED, 5)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.type === Type.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.DARK && move.category !== MoveCategory.STATUS, Stat.ATK, 1),
|
||||||
new Ability(Abilities.RATTLED, 5)
|
new Ability(Abilities.RATTLED, 5)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS && (move.type === Type.DARK || move.type === Type.BUG ||
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => {
|
||||||
move.type === Type.GHOST), Stat.SPD, 1)
|
const moveType = user.getMoveType(move);
|
||||||
|
return move.category !== MoveCategory.STATUS
|
||||||
|
&& (moveType === Type.DARK || moveType === Type.BUG || moveType === Type.GHOST);
|
||||||
|
}, Stat.SPD, 1)
|
||||||
.attr(PostIntimidateStatStageChangeAbAttr, [Stat.SPD], 1),
|
.attr(PostIntimidateStatStageChangeAbAttr, [Stat.SPD], 1),
|
||||||
new Ability(Abilities.MAGIC_BOUNCE, 5)
|
new Ability(Abilities.MAGIC_BOUNCE, 5)
|
||||||
.ignorable()
|
.ignorable()
|
||||||
@ -5313,7 +5307,7 @@ export function initAbilities() {
|
|||||||
.attr(UnsuppressableAbilityAbAttr)
|
.attr(UnsuppressableAbilityAbAttr)
|
||||||
.attr(NoFusionAbilityAbAttr),
|
.attr(NoFusionAbilityAbAttr),
|
||||||
new Ability(Abilities.GALE_WINGS, 6)
|
new Ability(Abilities.GALE_WINGS, 6)
|
||||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && move.type === Type.FLYING, 1),
|
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && pokemon.getMoveType(move) === Type.FLYING, 1),
|
||||||
new Ability(Abilities.MEGA_LAUNCHER, 6)
|
new Ability(Abilities.MEGA_LAUNCHER, 6)
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5),
|
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5),
|
||||||
new Ability(Abilities.GRASS_PELT, 6)
|
new Ability(Abilities.GRASS_PELT, 6)
|
||||||
@ -5339,8 +5333,10 @@ export function initAbilities() {
|
|||||||
.attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3),
|
.attr(FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 4 / 3),
|
||||||
new Ability(Abilities.AURA_BREAK, 6)
|
new Ability(Abilities.AURA_BREAK, 6)
|
||||||
.ignorable()
|
.ignorable()
|
||||||
.conditionalAttr(target => target.hasAbility(Abilities.DARK_AURA), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16)
|
.conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA)), FieldMoveTypePowerBoostAbAttr, Type.DARK, 9 / 16)
|
||||||
.conditionalAttr(target => target.hasAbility(Abilities.FAIRY_AURA), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16),
|
.conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.FAIRY_AURA)), FieldMoveTypePowerBoostAbAttr, Type.FAIRY, 9 / 16)
|
||||||
|
.conditionalAttr(pokemon => pokemon.scene.getField(true).some(p => p.hasAbility(Abilities.DARK_AURA) || p.hasAbility(Abilities.FAIRY_AURA)),
|
||||||
|
PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonAuraBreak", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })),
|
||||||
new Ability(Abilities.PRIMORDIAL_SEA, 6)
|
new Ability(Abilities.PRIMORDIAL_SEA, 6)
|
||||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
||||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
||||||
@ -5368,7 +5364,7 @@ export function initAbilities() {
|
|||||||
.condition(getSheerForceHitDisableAbCondition())
|
.condition(getSheerForceHitDisableAbCondition())
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
new Ability(Abilities.WATER_COMPACTION, 7)
|
new Ability(Abilities.WATER_COMPACTION, 7)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.WATER && move.category !== MoveCategory.STATUS, Stat.DEF, 2),
|
||||||
new Ability(Abilities.MERCILESS, 7)
|
new Ability(Abilities.MERCILESS, 7)
|
||||||
.attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
|
.attr(ConditionalCritAbAttr, (user, target, move) => target?.status?.effect === StatusEffect.TOXIC || target?.status?.effect === StatusEffect.POISON),
|
||||||
new Ability(Abilities.SHIELDS_DOWN, 7)
|
new Ability(Abilities.SHIELDS_DOWN, 7)
|
||||||
@ -5424,7 +5420,7 @@ export function initAbilities() {
|
|||||||
.attr(NoFusionAbilityAbAttr)
|
.attr(NoFusionAbilityAbAttr)
|
||||||
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
|
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
|
||||||
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
|
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
|
||||||
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getAttackTypeEffectiveness(move.type, user) > 0, 0, BattlerTagType.DISGUISE,
|
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
|
||||||
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
|
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
|
||||||
(pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8))
|
(pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8))
|
||||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
||||||
@ -5469,7 +5465,7 @@ export function initAbilities() {
|
|||||||
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL], 1.3),
|
.attr(AllyMoveCategoryPowerBoostAbAttr, [MoveCategory.SPECIAL], 1.3),
|
||||||
new Ability(Abilities.FLUFFY, 7)
|
new Ability(Abilities.FLUFFY, 7)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5)
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.type === Type.FIRE, 2)
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE, 2)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.DAZZLING, 7)
|
new Ability(Abilities.DAZZLING, 7)
|
||||||
.attr(FieldPriorityMoveImmunityAbAttr)
|
.attr(FieldPriorityMoveImmunityAbAttr)
|
||||||
@ -5519,10 +5515,10 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.SHADOW_SHIELD, 7)
|
new Ability(Abilities.SHADOW_SHIELD, 7)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.isFullHp(), 0.5),
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.isFullHp(), 0.5),
|
||||||
new Ability(Abilities.PRISM_ARMOR, 7)
|
new Ability(Abilities.PRISM_ARMOR, 7)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 0.75),
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75),
|
||||||
new Ability(Abilities.NEUROFORCE, 7)
|
new Ability(Abilities.NEUROFORCE, 7)
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2, 1.25), // TODO: fix TS issues
|
.attr(MovePowerBoostAbAttr, (user, target, move) => target?.getMoveEffectiveness(user, move) >= 2, 1.25), // TODO: fix TS issues
|
||||||
new Ability(Abilities.INTREPID_SWORD, 8)
|
new Ability(Abilities.INTREPID_SWORD, 8)
|
||||||
.attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
|
.attr(PostSummonStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
|
||||||
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
|
.condition(getOncePerBattleCondition(Abilities.INTREPID_SWORD)),
|
||||||
@ -5553,7 +5549,11 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.STALWART, 8)
|
new Ability(Abilities.STALWART, 8)
|
||||||
.attr(BlockRedirectAbAttr),
|
.attr(BlockRedirectAbAttr),
|
||||||
new Ability(Abilities.STEAM_ENGINE, 8)
|
new Ability(Abilities.STEAM_ENGINE, 8)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => (move.type === Type.FIRE || move.type === Type.WATER) && move.category !== MoveCategory.STATUS, Stat.SPD, 6),
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => {
|
||||||
|
const moveType = user.getMoveType(move);
|
||||||
|
return move.category !== MoveCategory.STATUS
|
||||||
|
&& (moveType === Type.FIRE || moveType === Type.WATER);
|
||||||
|
}, Stat.SPD, 6),
|
||||||
new Ability(Abilities.PUNK_ROCK, 8)
|
new Ability(Abilities.PUNK_ROCK, 8)
|
||||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3)
|
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED), 1.3)
|
||||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5)
|
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.SOUND_BASED), 0.5)
|
||||||
@ -5653,7 +5653,7 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.SEED_SOWER, 9)
|
new Ability(Abilities.SEED_SOWER, 9)
|
||||||
.attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY),
|
.attr(PostDefendTerrainChangeAbAttr, TerrainType.GRASSY),
|
||||||
new Ability(Abilities.THERMAL_EXCHANGE, 9)
|
new Ability(Abilities.THERMAL_EXCHANGE, 9)
|
||||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.type === Type.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => user.getMoveType(move) === Type.FIRE && move.category !== MoveCategory.STATUS, Stat.ATK, 1)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.BURN)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.ANGER_SHELL, 9)
|
new Ability(Abilities.ANGER_SHELL, 9)
|
||||||
|
@ -39,13 +39,15 @@ export class BattlerTag {
|
|||||||
public turnCount: number;
|
public turnCount: number;
|
||||||
public sourceMove: Moves;
|
public sourceMove: Moves;
|
||||||
public sourceId?: number;
|
public sourceId?: number;
|
||||||
|
public isBatonPassable: boolean;
|
||||||
|
|
||||||
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number) {
|
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType | BattlerTagLapseType[], turnCount: number, sourceMove?: Moves, sourceId?: number, isBatonPassable: boolean = false) {
|
||||||
this.tagType = tagType;
|
this.tagType = tagType;
|
||||||
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
|
this.lapseTypes = Array.isArray(lapseType) ? lapseType : [ lapseType ];
|
||||||
this.turnCount = turnCount;
|
this.turnCount = turnCount;
|
||||||
this.sourceMove = sourceMove!; // TODO: is this bang correct?
|
this.sourceMove = sourceMove!; // TODO: is this bang correct?
|
||||||
this.sourceId = sourceId;
|
this.sourceId = sourceId;
|
||||||
|
this.isBatonPassable = isBatonPassable;
|
||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
@ -96,6 +98,127 @@ export interface TerrainBattlerTag {
|
|||||||
terrainTypes: TerrainType[];
|
terrainTypes: TerrainType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for tags that restrict the usage of moves. This effect is generally referred to as "disabling" a move
|
||||||
|
* in-game. This is not to be confused with {@linkcode Moves.DISABLE}.
|
||||||
|
*
|
||||||
|
* Descendants can override {@linkcode isMoveRestricted} to restrict moves that
|
||||||
|
* match a condition. A restricted move gets cancelled before it is used. Players and enemies should not be allowed
|
||||||
|
* to select restricted moves.
|
||||||
|
*/
|
||||||
|
export abstract class MoveRestrictionBattlerTag extends BattlerTag {
|
||||||
|
constructor(tagType: BattlerTagType, turnCount: integer, sourceMove?: Moves, sourceId?: integer) {
|
||||||
|
super(tagType, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.TURN_END ], turnCount, sourceMove, sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
|
||||||
|
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
|
||||||
|
// Cancel the affected pokemon's selected move
|
||||||
|
const phase = pokemon.scene.getCurrentPhase() as MovePhase;
|
||||||
|
const move = phase.move;
|
||||||
|
|
||||||
|
if (this.isMoveRestricted(move.moveId)) {
|
||||||
|
pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId));
|
||||||
|
phase.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.lapse(pokemon, lapseType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether this tag is restricting a move.
|
||||||
|
*
|
||||||
|
* @param {Moves} move {@linkcode Moves} ID to check restriction for.
|
||||||
|
* @returns {boolean} `true` if the move is restricted by this tag, otherwise `false`.
|
||||||
|
*/
|
||||||
|
abstract isMoveRestricted(move: Moves): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the text to display when the player attempts to select a move that is restricted by this tag.
|
||||||
|
*
|
||||||
|
* @param {Pokemon} pokemon {@linkcode Pokemon} for which the player is attempting to select the restricted move
|
||||||
|
* @param {Moves} move {@linkcode Moves} ID of the move that is having its selection denied
|
||||||
|
* @returns {string} text to display when the player attempts to select the restricted move
|
||||||
|
*/
|
||||||
|
abstract selectionDeniedText(pokemon: Pokemon, move: Moves): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the text to display when a move's execution is prevented as a result of the restriction.
|
||||||
|
* Because restriction effects also prevent selection of the move, this situation can only arise if a
|
||||||
|
* pokemon first selects a move, then gets outsped by a pokemon using a move that restricts the selected move.
|
||||||
|
*
|
||||||
|
* @param {Pokemon} pokemon {@linkcode Pokemon} attempting to use the restricted move
|
||||||
|
* @param {Moves} move {@linkcode Moves} ID of the move being interrupted
|
||||||
|
* @returns {string} text to display when the move is interrupted
|
||||||
|
*/
|
||||||
|
abstract interruptedText(pokemon: Pokemon, move: Moves): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag representing the "disabling" effect performed by {@linkcode Moves.DISABLE} and {@linkcode Abilities.CURSED_BODY}.
|
||||||
|
* When the tag is added, the last-used move of the tag holder is set as the disabled move.
|
||||||
|
*/
|
||||||
|
export class DisabledTag extends MoveRestrictionBattlerTag {
|
||||||
|
/** The move being disabled. Gets set when {@linkcode onAdd} is called for this tag. */
|
||||||
|
private moveId: Moves = Moves.NONE;
|
||||||
|
|
||||||
|
constructor(sourceId: number) {
|
||||||
|
super(BattlerTagType.DISABLED, 4, Moves.DISABLE, sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
override isMoveRestricted(move: Moves): boolean {
|
||||||
|
return move === this.moveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*
|
||||||
|
* Ensures that move history exists on `pokemon` and has a valid move. If so, sets the {@link moveId} and shows a message.
|
||||||
|
* Otherwise the move ID will not get assigned and this tag will get removed next turn.
|
||||||
|
*/
|
||||||
|
override onAdd(pokemon: Pokemon): void {
|
||||||
|
super.onAdd(pokemon);
|
||||||
|
|
||||||
|
const move = pokemon.getLastXMoves()
|
||||||
|
.find(m => m.move !== Moves.NONE && m.move !== Moves.STRUGGLE && !m.virtual);
|
||||||
|
if (move === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.moveId = move.move;
|
||||||
|
|
||||||
|
pokemon.scene.queueMessage(i18next.t("battlerTags:disabledOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
override onRemove(pokemon: Pokemon): void {
|
||||||
|
super.onRemove(pokemon);
|
||||||
|
|
||||||
|
pokemon.scene.queueMessage(i18next.t("battlerTags:disabledLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[this.moveId].name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
override selectionDeniedText(pokemon: Pokemon, move: Moves): string {
|
||||||
|
return i18next.t("battle:moveDisabled", { moveName: allMoves[move].name });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
override interruptedText(pokemon: Pokemon, move: Moves): string {
|
||||||
|
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
override loadTag(source: BattlerTag | any): void {
|
||||||
|
super.loadTag(source);
|
||||||
|
this.moveId = source.moveId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BattlerTag that represents the "recharge" effects of moves like Hyper Beam.
|
* BattlerTag that represents the "recharge" effects of moves like Hyper Beam.
|
||||||
*/
|
*/
|
||||||
@ -206,7 +329,7 @@ export class ShellTrapTag extends BattlerTag {
|
|||||||
|
|
||||||
export class TrappedTag extends BattlerTag {
|
export class TrappedTag extends BattlerTag {
|
||||||
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
|
constructor(tagType: BattlerTagType, lapseType: BattlerTagLapseType, turnCount: number, sourceMove: Moves, sourceId: number) {
|
||||||
super(tagType, lapseType, turnCount, sourceMove, sourceId);
|
super(tagType, lapseType, turnCount, sourceMove, sourceId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
@ -326,7 +449,7 @@ export class InterruptedTag extends BattlerTag {
|
|||||||
*/
|
*/
|
||||||
export class ConfusedTag extends BattlerTag {
|
export class ConfusedTag extends BattlerTag {
|
||||||
constructor(turnCount: number, sourceMove: Moves) {
|
constructor(turnCount: number, sourceMove: Moves) {
|
||||||
super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove);
|
super(BattlerTagType.CONFUSED, BattlerTagLapseType.MOVE, turnCount, sourceMove, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
@ -363,7 +486,7 @@ export class ConfusedTag extends BattlerTag {
|
|||||||
if (pokemon.randSeedInt(3) === 0) {
|
if (pokemon.randSeedInt(3) === 0) {
|
||||||
const atk = pokemon.getEffectiveStat(Stat.ATK);
|
const atk = pokemon.getEffectiveStat(Stat.ATK);
|
||||||
const def = pokemon.getEffectiveStat(Stat.DEF);
|
const def = pokemon.getEffectiveStat(Stat.DEF);
|
||||||
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
|
const damage = Utils.toDmgValue(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedIntRange(85, 100) / 100));
|
||||||
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
|
pokemon.scene.queueMessage(i18next.t("battlerTags:confusedLapseHurtItself"));
|
||||||
pokemon.damageAndUpdate(damage);
|
pokemon.damageAndUpdate(damage);
|
||||||
pokemon.battleData.hitCount++;
|
pokemon.battleData.hitCount++;
|
||||||
@ -386,7 +509,7 @@ export class ConfusedTag extends BattlerTag {
|
|||||||
*/
|
*/
|
||||||
export class DestinyBondTag extends BattlerTag {
|
export class DestinyBondTag extends BattlerTag {
|
||||||
constructor(sourceMove: Moves, sourceId: number) {
|
constructor(sourceMove: Moves, sourceId: number) {
|
||||||
super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId);
|
super(BattlerTagType.DESTINY_BOND, BattlerTagLapseType.PRE_MOVE, 1, sourceMove, sourceId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -505,7 +628,7 @@ export class SeedTag extends BattlerTag {
|
|||||||
private sourceIndex: number;
|
private sourceIndex: number;
|
||||||
|
|
||||||
constructor(sourceId: number) {
|
constructor(sourceId: number) {
|
||||||
super(BattlerTagType.SEEDED, BattlerTagLapseType.TURN_END, 1, Moves.LEECH_SEED, sourceId);
|
super(BattlerTagType.SEEDED, BattlerTagLapseType.TURN_END, 1, Moves.LEECH_SEED, sourceId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -776,7 +899,7 @@ export class OctolockTag extends TrappedTag {
|
|||||||
|
|
||||||
export class AquaRingTag extends BattlerTag {
|
export class AquaRingTag extends BattlerTag {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined);
|
super(BattlerTagType.AQUA_RING, BattlerTagLapseType.TURN_END, 1, Moves.AQUA_RING, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -808,7 +931,7 @@ export class AquaRingTag extends BattlerTag {
|
|||||||
/** Tag used to allow moves that interact with {@link Moves.MINIMIZE} to function */
|
/** Tag used to allow moves that interact with {@link Moves.MINIMIZE} to function */
|
||||||
export class MinimizeTag extends BattlerTag {
|
export class MinimizeTag extends BattlerTag {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE, undefined);
|
super(BattlerTagType.MINIMIZED, BattlerTagLapseType.TURN_END, 1, Moves.MINIMIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
@ -1206,7 +1329,7 @@ export class SturdyTag extends BattlerTag {
|
|||||||
|
|
||||||
export class PerishSongTag extends BattlerTag {
|
export class PerishSongTag extends BattlerTag {
|
||||||
constructor(turnCount: number) {
|
constructor(turnCount: number) {
|
||||||
super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG);
|
super(BattlerTagType.PERISH_SONG, BattlerTagLapseType.TURN_END, turnCount, Moves.PERISH_SONG, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
canAdd(pokemon: Pokemon): boolean {
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
@ -1262,7 +1385,7 @@ export class AbilityBattlerTag extends BattlerTag {
|
|||||||
public ability: Abilities;
|
public ability: Abilities;
|
||||||
|
|
||||||
constructor(tagType: BattlerTagType, ability: Abilities, lapseType: BattlerTagLapseType, turnCount: number) {
|
constructor(tagType: BattlerTagType, ability: Abilities, lapseType: BattlerTagLapseType, turnCount: number) {
|
||||||
super(tagType, lapseType, turnCount, undefined);
|
super(tagType, lapseType, turnCount);
|
||||||
|
|
||||||
this.ability = ability;
|
this.ability = ability;
|
||||||
}
|
}
|
||||||
@ -1438,7 +1561,7 @@ export class TypeImmuneTag extends BattlerTag {
|
|||||||
public immuneType: Type;
|
public immuneType: Type;
|
||||||
|
|
||||||
constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number = 1) {
|
constructor(tagType: BattlerTagType, sourceMove: Moves, immuneType: Type, length: number = 1) {
|
||||||
super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove);
|
super(tagType, BattlerTagLapseType.TURN_END, length, sourceMove, undefined, true);
|
||||||
|
|
||||||
this.immuneType = immuneType;
|
this.immuneType = immuneType;
|
||||||
}
|
}
|
||||||
@ -1502,7 +1625,7 @@ export class TypeBoostTag extends BattlerTag {
|
|||||||
|
|
||||||
export class CritBoostTag extends BattlerTag {
|
export class CritBoostTag extends BattlerTag {
|
||||||
constructor(tagType: BattlerTagType, sourceMove: Moves) {
|
constructor(tagType: BattlerTagType, sourceMove: Moves) {
|
||||||
super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove);
|
super(tagType, BattlerTagLapseType.TURN_END, 1, sourceMove, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(pokemon: Pokemon): void {
|
onAdd(pokemon: Pokemon): void {
|
||||||
@ -1594,7 +1717,7 @@ export class CursedTag extends BattlerTag {
|
|||||||
private sourceIndex: number;
|
private sourceIndex: number;
|
||||||
|
|
||||||
constructor(sourceId: number) {
|
constructor(sourceId: number) {
|
||||||
super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, Moves.CURSE, sourceId);
|
super(BattlerTagType.CURSED, BattlerTagLapseType.TURN_END, 1, Moves.CURSE, sourceId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1993,6 +2116,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||||||
return new StockpilingTag(sourceMove);
|
return new StockpilingTag(sourceMove);
|
||||||
case BattlerTagType.OCTOLOCK:
|
case BattlerTagType.OCTOLOCK:
|
||||||
return new OctolockTag(sourceId);
|
return new OctolockTag(sourceId);
|
||||||
|
case BattlerTagType.DISABLED:
|
||||||
|
return new DisabledTag(sourceId);
|
||||||
case BattlerTagType.IGNORE_GHOST:
|
case BattlerTagType.IGNORE_GHOST:
|
||||||
return new ExposedTag(tagType, sourceMove, Type.GHOST, [Type.NORMAL, Type.FIGHTING]);
|
return new ExposedTag(tagType, sourceMove, Type.GHOST, [Type.NORMAL, Type.FIGHTING]);
|
||||||
case BattlerTagType.IGNORE_DARK:
|
case BattlerTagType.IGNORE_DARK:
|
||||||
|
98
src/data/egg-hatch-data.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import { PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import { DexEntry, StarterDataEntry } from "#app/system/game-data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores data associated with a specific egg and the hatched pokemon
|
||||||
|
* Allows hatch info to be stored at hatch then retrieved for display during egg summary
|
||||||
|
*/
|
||||||
|
export class EggHatchData {
|
||||||
|
/** the pokemon that hatched from the file (including shiny, IVs, ability) */
|
||||||
|
public pokemon: PlayerPokemon;
|
||||||
|
/** index of the egg move from the hatched pokemon (not stored in PlayerPokemon) */
|
||||||
|
public eggMoveIndex: number;
|
||||||
|
/** boolean indicating if the egg move for the hatch is new */
|
||||||
|
public eggMoveUnlocked: boolean;
|
||||||
|
/** stored copy of the hatched pokemon's dex entry before it was updated due to hatch */
|
||||||
|
public dexEntryBeforeUpdate: DexEntry;
|
||||||
|
/** stored copy of the hatched pokemon's starter entry before it was updated due to hatch */
|
||||||
|
public starterDataEntryBeforeUpdate: StarterDataEntry;
|
||||||
|
/** reference to the battle scene to get gamedata and update dex */
|
||||||
|
private scene: BattleScene;
|
||||||
|
|
||||||
|
constructor(scene: BattleScene, pokemon: PlayerPokemon, eggMoveIndex: number) {
|
||||||
|
this.scene = scene;
|
||||||
|
this.pokemon = pokemon;
|
||||||
|
this.eggMoveIndex = eggMoveIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the boolean for if the egg move for the hatch is a new unlock
|
||||||
|
* @param unlocked True if the EM is new
|
||||||
|
*/
|
||||||
|
setEggMoveUnlocked(unlocked: boolean) {
|
||||||
|
this.eggMoveUnlocked = unlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a copy of the current DexEntry of the pokemon and StarterDataEntry of its starter
|
||||||
|
* Used before updating the dex, so comparing the pokemon to these entries will show the new attributes
|
||||||
|
*/
|
||||||
|
setDex() {
|
||||||
|
const currDexEntry = this.scene.gameData.dexData[this.pokemon.species.speciesId];
|
||||||
|
const currStarterDataEntry = this.scene.gameData.starterData[this.pokemon.species.getRootSpeciesId()];
|
||||||
|
this.dexEntryBeforeUpdate = {
|
||||||
|
seenAttr: currDexEntry.seenAttr,
|
||||||
|
caughtAttr: currDexEntry.caughtAttr,
|
||||||
|
natureAttr: currDexEntry.natureAttr,
|
||||||
|
seenCount: currDexEntry.seenCount,
|
||||||
|
caughtCount: currDexEntry.caughtCount,
|
||||||
|
hatchedCount: currDexEntry.hatchedCount,
|
||||||
|
ivs: [...currDexEntry.ivs]
|
||||||
|
};
|
||||||
|
this.starterDataEntryBeforeUpdate = {
|
||||||
|
moveset: currStarterDataEntry.moveset,
|
||||||
|
eggMoves: currStarterDataEntry.eggMoves,
|
||||||
|
candyCount: currStarterDataEntry.candyCount,
|
||||||
|
friendship: currStarterDataEntry.friendship,
|
||||||
|
abilityAttr: currStarterDataEntry.abilityAttr,
|
||||||
|
passiveAttr: currStarterDataEntry.passiveAttr,
|
||||||
|
valueReduction: currStarterDataEntry.valueReduction,
|
||||||
|
classicWinCount: currStarterDataEntry.classicWinCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the dex entry before update
|
||||||
|
* @returns Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
|
||||||
|
*/
|
||||||
|
getDex(): DexEntry {
|
||||||
|
return this.dexEntryBeforeUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the starter dex entry before update
|
||||||
|
* @returns Starter Dex Entry corresponding to this pokemon before the pokemon was added / updated to dex
|
||||||
|
*/
|
||||||
|
getStarterEntry(): StarterDataEntry {
|
||||||
|
return this.starterDataEntryBeforeUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the pokedex data corresponding with the new hatch's pokemon data
|
||||||
|
* Also sets whether the egg move is a new unlock or not
|
||||||
|
* @param showMessage boolean to show messages for the new catches and egg moves (false by default)
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
updatePokemon(showMessage : boolean = false) {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
this.scene.gameData.setPokemonCaught(this.pokemon, true, true, showMessage).then(() => {
|
||||||
|
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
|
||||||
|
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex, showMessage).then((value) => {
|
||||||
|
this.setEggMoveUnlocked(value);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ export const EGG_SEED = 1073741824;
|
|||||||
// Rates for specific random properties in 1/x
|
// Rates for specific random properties in 1/x
|
||||||
const DEFAULT_SHINY_RATE = 128;
|
const DEFAULT_SHINY_RATE = 128;
|
||||||
const GACHA_SHINY_UP_SHINY_RATE = 64;
|
const GACHA_SHINY_UP_SHINY_RATE = 64;
|
||||||
const SAME_SPECIES_EGG_SHINY_RATE = 24;
|
const SAME_SPECIES_EGG_SHINY_RATE = 12;
|
||||||
const SAME_SPECIES_EGG_HA_RATE = 8;
|
const SAME_SPECIES_EGG_HA_RATE = 8;
|
||||||
const MANAPHY_EGG_MANAPHY_RATE = 8;
|
const MANAPHY_EGG_MANAPHY_RATE = 8;
|
||||||
const GACHA_EGG_HA_RATE = 192;
|
const GACHA_EGG_HA_RATE = 192;
|
||||||
|
125
src/data/move.ts
@ -757,12 +757,14 @@ export default class Move implements Localizable {
|
|||||||
|
|
||||||
const fieldAuras = new Set(
|
const fieldAuras = new Set(
|
||||||
source.scene.getField(true)
|
source.scene.getField(true)
|
||||||
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[])
|
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr).filter(attr => {
|
||||||
|
const condition = attr.getCondition();
|
||||||
|
return (!condition || condition(p));
|
||||||
|
}) as FieldMoveTypePowerBoostAbAttr[])
|
||||||
.flat(),
|
.flat(),
|
||||||
);
|
);
|
||||||
for (const aura of fieldAuras) {
|
for (const aura of fieldAuras) {
|
||||||
// The only relevant values are `move` and the `power` holder
|
aura.applyPreAttack(source, null, simulated, target, this, [power]);
|
||||||
aura.applyPreAttack(null, null, simulated, null, this, [power]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField();
|
const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField();
|
||||||
@ -4333,72 +4335,6 @@ export class TypelessAttr extends MoveAttr { }
|
|||||||
*/
|
*/
|
||||||
export class BypassRedirectAttr extends MoveAttr { }
|
export class BypassRedirectAttr extends MoveAttr { }
|
||||||
|
|
||||||
export class DisableMoveAttr extends MoveEffectAttr {
|
|
||||||
constructor() {
|
|
||||||
super(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
||||||
if (!super.apply(user, target, move, args)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveQueue = target.getLastXMoves();
|
|
||||||
let turnMove: TurnMove | undefined;
|
|
||||||
while (moveQueue.length) {
|
|
||||||
turnMove = moveQueue.shift();
|
|
||||||
if (turnMove?.virtual) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveIndex = target.getMoveset().findIndex(m => m?.moveId === turnMove?.move);
|
|
||||||
if (moveIndex === -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const disabledMove = target.getMoveset()[moveIndex];
|
|
||||||
target.summonData.disabledMove = disabledMove?.moveId!; // TODO: is this bang correct?
|
|
||||||
target.summonData.disabledTurns = 4;
|
|
||||||
|
|
||||||
user.scene.queueMessage(i18next.t("abilityTriggers:postDefendMoveDisable", { pokemonNameWithAffix: getPokemonNameWithAffix(target), moveName: disabledMove?.getName()}));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCondition(): MoveConditionFunc {
|
|
||||||
return (user, target, move): boolean => { // TODO: Not sure what to do here
|
|
||||||
if (target.summonData.disabledMove || target.isMax()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveQueue = target.getLastXMoves();
|
|
||||||
let turnMove: TurnMove | undefined;
|
|
||||||
while (moveQueue.length) {
|
|
||||||
turnMove = moveQueue.shift();
|
|
||||||
if (turnMove?.virtual) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const move = target.getMoveset().find(m => m?.moveId === turnMove?.move);
|
|
||||||
if (!move) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
||||||
return -5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FrenzyAttr extends MoveEffectAttr {
|
export class FrenzyAttr extends MoveEffectAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(true, MoveEffectTrigger.HIT, false, true);
|
super(true, MoveEffectTrigger.HIT, false, true);
|
||||||
@ -4467,7 +4403,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||||
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id);
|
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedIntRange(this.turnCountMin, this.turnCountMax), move.id, user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -4489,6 +4425,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||||||
case BattlerTagType.INFATUATED:
|
case BattlerTagType.INFATUATED:
|
||||||
case BattlerTagType.NIGHTMARE:
|
case BattlerTagType.NIGHTMARE:
|
||||||
case BattlerTagType.DROWSY:
|
case BattlerTagType.DROWSY:
|
||||||
|
case BattlerTagType.DISABLED:
|
||||||
return -5;
|
return -5;
|
||||||
case BattlerTagType.SEEDED:
|
case BattlerTagType.SEEDED:
|
||||||
case BattlerTagType.SALT_CURED:
|
case BattlerTagType.SALT_CURED:
|
||||||
@ -6345,6 +6282,8 @@ const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target:
|
|||||||
|
|
||||||
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
|
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
|
||||||
|
|
||||||
|
const failIfLastCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => user.scene.phaseQueue.find(phase => phase instanceof MovePhase) !== undefined;
|
||||||
|
|
||||||
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
||||||
|
|
||||||
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
|
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon | null, target: Pokemon | null, move: Move, args: any[]): Promise<void> {
|
||||||
@ -6719,7 +6658,8 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.SONIC_BOOM, Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, 0, 1)
|
new AttackMove(Moves.SONIC_BOOM, Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, 0, 1)
|
||||||
.attr(FixedDamageAttr, 20),
|
.attr(FixedDamageAttr, 20),
|
||||||
new StatusMove(Moves.DISABLE, Type.NORMAL, 100, 20, -1, 0, 1)
|
new StatusMove(Moves.DISABLE, Type.NORMAL, 100, 20, -1, 0, 1)
|
||||||
.attr(DisableMoveAttr)
|
.attr(AddBattlerTagAttr, BattlerTagType.DISABLED, false, true)
|
||||||
|
.condition((user, target, move) => target.getMoveHistory().reverse().find(m => m.move !== Moves.NONE && m.move !== Moves.STRUGGLE && !m.virtual) !== undefined)
|
||||||
.condition(failOnMaxCondition),
|
.condition(failOnMaxCondition),
|
||||||
new AttackMove(Moves.ACID, Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
new AttackMove(Moves.ACID, Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
|
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
|
||||||
@ -7082,7 +7022,8 @@ export function initMoves() {
|
|||||||
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new SelfStatusMove(Moves.PROTECT, Type.NORMAL, -1, 10, -1, 4, 2)
|
new SelfStatusMove(Moves.PROTECT, Type.NORMAL, -1, 10, -1, 4, 2)
|
||||||
.attr(ProtectAttr),
|
.attr(ProtectAttr)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2)
|
new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2)
|
||||||
.punchingMove(),
|
.punchingMove(),
|
||||||
new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2)
|
new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2)
|
||||||
@ -7133,7 +7074,8 @@ export function initMoves() {
|
|||||||
.windMove()
|
.windMove()
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2)
|
new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2)
|
||||||
.attr(ProtectAttr),
|
.attr(ProtectAttr)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new AttackMove(Moves.BONE_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 2)
|
new AttackMove(Moves.BONE_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 2)
|
||||||
.attr(MultiHitAttr)
|
.attr(MultiHitAttr)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
@ -7151,7 +7093,8 @@ export function initMoves() {
|
|||||||
.attr(HitHealAttr)
|
.attr(HitHealAttr)
|
||||||
.triageMove(),
|
.triageMove(),
|
||||||
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
|
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
|
||||||
.attr(ProtectAttr, BattlerTagType.ENDURING),
|
.attr(ProtectAttr, BattlerTagType.ENDURING)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
|
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -2),
|
.attr(StatStageChangeAttr, [ Stat.ATK ], -2),
|
||||||
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
|
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
|
||||||
@ -7898,7 +7841,8 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true),
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.ACC ], 1, true),
|
||||||
new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5)
|
new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5)
|
||||||
.target(MoveTarget.USER_SIDE)
|
.target(MoveTarget.USER_SIDE)
|
||||||
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true),
|
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
||||||
.attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"),
|
.attr(AverageStatsAttr, [ Stat.DEF, Stat.SPDEF ], "moveTriggers:sharedGuard"),
|
||||||
new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
||||||
@ -7986,7 +7930,8 @@ export function initMoves() {
|
|||||||
.attr(PositiveStatStagePowerAttr),
|
.attr(PositiveStatStagePowerAttr),
|
||||||
new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5)
|
new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5)
|
||||||
.target(MoveTarget.USER_SIDE)
|
.target(MoveTarget.USER_SIDE)
|
||||||
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true),
|
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new SelfStatusMove(Moves.ALLY_SWITCH, Type.PSYCHIC, -1, 15, -1, 2, 5)
|
new SelfStatusMove(Moves.ALLY_SWITCH, Type.PSYCHIC, -1, 15, -1, 2, 5)
|
||||||
.ignoresProtect()
|
.ignoresProtect()
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
@ -8157,7 +8102,8 @@ export function initMoves() {
|
|||||||
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
||||||
.target(MoveTarget.USER_SIDE)
|
.target(MoveTarget.USER_SIDE)
|
||||||
.attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true)
|
.attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true)
|
||||||
.condition(new FirstMoveCondition()),
|
.condition(new FirstMoveCondition())
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
|
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
|
||||||
.condition((user, target, move) => user.battleData.berriesEaten.length > 0),
|
.condition((user, target, move) => user.battleData.berriesEaten.length > 0),
|
||||||
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
|
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
|
||||||
@ -8215,7 +8161,8 @@ export function initMoves() {
|
|||||||
.triageMove(),
|
.triageMove(),
|
||||||
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
|
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
|
||||||
.target(MoveTarget.USER_SIDE)
|
.target(MoveTarget.USER_SIDE)
|
||||||
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true),
|
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
||||||
.target(MoveTarget.ALL)
|
.target(MoveTarget.ALL)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
|
.attr(StatStageChangeAttr, [ Stat.DEF ], 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
|
||||||
@ -8240,7 +8187,8 @@ export function initMoves() {
|
|||||||
.target(MoveTarget.BOTH_SIDES)
|
.target(MoveTarget.BOTH_SIDES)
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
|
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
|
||||||
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD),
|
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
|
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
||||||
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
|
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||||
@ -8263,7 +8211,8 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6)
|
new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
|
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1),
|
||||||
new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6)
|
new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6)
|
||||||
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD),
|
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6)
|
new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPDEF ], 1)
|
.attr(StatStageChangeAttr, [ Stat.SPDEF ], 1)
|
||||||
.target(MoveTarget.NEAR_ALLY),
|
.target(MoveTarget.NEAR_ALLY),
|
||||||
@ -8459,7 +8408,8 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.FIRST_IMPRESSION, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7)
|
new AttackMove(Moves.FIRST_IMPRESSION, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7)
|
||||||
.condition(new FirstMoveCondition()),
|
.condition(new FirstMoveCondition()),
|
||||||
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
|
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
|
||||||
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER),
|
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
|
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
|
||||||
.makesContact(false),
|
.makesContact(false),
|
||||||
@ -8702,6 +8652,7 @@ export function initMoves() {
|
|||||||
/* Unused */
|
/* Unused */
|
||||||
new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8)
|
new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8)
|
||||||
.attr(ProtectAttr)
|
.attr(ProtectAttr)
|
||||||
|
.condition(failIfLastCondition)
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
/* End Unused */
|
/* End Unused */
|
||||||
new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
|
new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
|
||||||
@ -8880,7 +8831,8 @@ export function initMoves() {
|
|||||||
.target(MoveTarget.USER_AND_ALLIES)
|
.target(MoveTarget.USER_AND_ALLIES)
|
||||||
.ignoresProtect(),
|
.ignoresProtect(),
|
||||||
new SelfStatusMove(Moves.OBSTRUCT, Type.DARK, 100, 10, -1, 4, 8)
|
new SelfStatusMove(Moves.OBSTRUCT, Type.DARK, 100, 10, -1, 4, 8)
|
||||||
.attr(ProtectAttr, BattlerTagType.OBSTRUCT),
|
.attr(ProtectAttr, BattlerTagType.OBSTRUCT)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new AttackMove(Moves.FALSE_SURRENDER, Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, 0, 8),
|
new AttackMove(Moves.FALSE_SURRENDER, Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, 0, 8),
|
||||||
new AttackMove(Moves.METEOR_ASSAULT, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8)
|
new AttackMove(Moves.METEOR_ASSAULT, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8)
|
||||||
.attr(RechargeAttr)
|
.attr(RechargeAttr)
|
||||||
@ -9168,10 +9120,10 @@ export function initMoves() {
|
|||||||
.attr(TeraBlastCategoryAttr)
|
.attr(TeraBlastCategoryAttr)
|
||||||
.attr(TeraBlastTypeAttr)
|
.attr(TeraBlastTypeAttr)
|
||||||
.attr(TeraBlastPowerAttr)
|
.attr(TeraBlastPowerAttr)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR))
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, true, (user, target, move) => user.isTerastallized() && user.isOfType(Type.STELLAR)),
|
||||||
.partial(),
|
|
||||||
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
|
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
|
||||||
.attr(ProtectAttr, BattlerTagType.SILK_TRAP),
|
.attr(ProtectAttr, BattlerTagType.SILK_TRAP)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9)
|
new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9)
|
||||||
.attr(MissEffectAttr, crashDamageFunc)
|
.attr(MissEffectAttr, crashDamageFunc)
|
||||||
.attr(NoEffectAttr, crashDamageFunc)
|
.attr(NoEffectAttr, crashDamageFunc)
|
||||||
@ -9363,7 +9315,8 @@ export function initMoves() {
|
|||||||
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
|
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
|
||||||
.attr(DoublePowerChanceAttr),
|
.attr(DoublePowerChanceAttr),
|
||||||
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
|
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
|
||||||
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK),
|
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK)
|
||||||
|
.condition(failIfLastCondition),
|
||||||
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
|
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
|
||||||
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct?
|
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS), // TODO: is this bang correct?
|
||||||
new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9)
|
new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9)
|
||||||
|
@ -64,6 +64,7 @@ export enum BattlerTagType {
|
|||||||
STOCKPILING = "STOCKPILING",
|
STOCKPILING = "STOCKPILING",
|
||||||
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
|
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
|
||||||
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
|
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
|
||||||
|
DISABLED = "DISABLED",
|
||||||
IGNORE_GHOST = "IGNORE_GHOST",
|
IGNORE_GHOST = "IGNORE_GHOST",
|
||||||
IGNORE_DARK = "IGNORE_DARK",
|
IGNORE_DARK = "IGNORE_DARK",
|
||||||
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
|
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
|
||||||
|
@ -17,7 +17,7 @@ import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
|
|||||||
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
|
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
|
||||||
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
|
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
|
||||||
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
||||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag } from "../data/battler-tags";
|
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag } from "../data/battler-tags";
|
||||||
import { WeatherType } from "../data/weather";
|
import { WeatherType } from "../data/weather";
|
||||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
||||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability";
|
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability";
|
||||||
@ -1720,7 +1720,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true);
|
this.fusionSpecies = this.scene.randomSpecies(this.scene.currentBattle?.waveIndex || 0, this.level, false, filter, true);
|
||||||
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? this.fusionSpecies.ability2 ? 2 : 1 : this.fusionSpecies.ability2 ? randAbilityIndex : 0);
|
this.fusionAbilityIndex = (this.fusionSpecies.abilityHidden && hasHiddenAbility ? 2 : this.fusionSpecies.ability2 !== this.fusionSpecies.ability1 ? randAbilityIndex : 0);
|
||||||
this.fusionShiny = this.shiny;
|
this.fusionShiny = this.shiny;
|
||||||
this.fusionVariant = this.variant;
|
this.fusionVariant = this.variant;
|
||||||
|
|
||||||
@ -2278,7 +2278,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
if (!isTypeImmune) {
|
if (!isTypeImmune) {
|
||||||
const levelMultiplier = (2 * source.level / 5 + 2);
|
const levelMultiplier = (2 * source.level / 5 + 2);
|
||||||
const randomMultiplier = ((this.scene.randBattleSeedInt(16) + 85) / 100);
|
const randomMultiplier = (this.randSeedIntRange(85, 100) / 100);
|
||||||
damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
|
damage.value = Utils.toDmgValue((((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2)
|
||||||
* stabMultiplier.value
|
* stabMultiplier.value
|
||||||
* typeMultiplier
|
* typeMultiplier
|
||||||
@ -2660,11 +2660,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const tag of source.summonData.tags) {
|
for (const tag of source.summonData.tags) {
|
||||||
|
if (!tag.isBatonPassable) {
|
||||||
// bypass those can not be passed via Baton Pass
|
|
||||||
const excludeTagTypes = new Set([BattlerTagType.DROWSY, BattlerTagType.INFATUATED, BattlerTagType.FIRE_BOOST]);
|
|
||||||
|
|
||||||
if (excludeTagTypes.has(tag.tagType)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2674,6 +2670,33 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.updateInfo();
|
this.updateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether the given move is currently disabled for this Pokemon.
|
||||||
|
*
|
||||||
|
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||||
|
* @returns {boolean} `true` if the move is disabled for this Pokemon, otherwise `false`
|
||||||
|
*
|
||||||
|
* @see {@linkcode MoveRestrictionBattlerTag}
|
||||||
|
*/
|
||||||
|
isMoveRestricted(moveId: Moves): boolean {
|
||||||
|
return this.getRestrictingTag(moveId) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
|
||||||
|
*
|
||||||
|
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||||
|
* @returns {MoveRestrictionBattlerTag | null} the first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
|
||||||
|
*/
|
||||||
|
getRestrictingTag(moveId: Moves): MoveRestrictionBattlerTag | null {
|
||||||
|
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
|
||||||
|
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) {
|
||||||
|
return tag as MoveRestrictionBattlerTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getMoveHistory(): TurnMove[] {
|
getMoveHistory(): TurnMove[] {
|
||||||
return this.battleSummonData.moveHistory;
|
return this.battleSummonData.moveHistory;
|
||||||
}
|
}
|
||||||
@ -3425,12 +3448,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
fusionCanvas.remove();
|
fusionCanvas.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
|
||||||
|
* <!-- @import "../battle".Battle -->
|
||||||
|
* This calls either {@linkcode BattleScene.randBattleSeedInt}({@linkcode range}, {@linkcode min}) in `src/battle-scene.ts`
|
||||||
|
* which calls {@linkcode Battle.randSeedInt}(`scene`, {@linkcode range}, {@linkcode min}) in `src/battle.ts`
|
||||||
|
* which calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts`,
|
||||||
|
* or it directly calls {@linkcode Utils.randSeedInt randSeedInt}({@linkcode range}, {@linkcode min}) in `src/utils.ts` if there is no current battle
|
||||||
|
*
|
||||||
|
* @param range How large of a range of random numbers to choose from. If {@linkcode range} <= 1, returns {@linkcode min}
|
||||||
|
* @param min The minimum integer to pick, default `0`
|
||||||
|
* @returns A random integer between {@linkcode min} and ({@linkcode min} + {@linkcode range} - 1)
|
||||||
|
*/
|
||||||
randSeedInt(range: integer, min: integer = 0): integer {
|
randSeedInt(range: integer, min: integer = 0): integer {
|
||||||
return this.scene.currentBattle
|
return this.scene.currentBattle
|
||||||
? this.scene.randBattleSeedInt(range, min)
|
? this.scene.randBattleSeedInt(range, min)
|
||||||
: Utils.randSeedInt(range, min);
|
: Utils.randSeedInt(range, min);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random number using the current battle's seed, or the global seed if `this.scene.currentBattle` is falsy
|
||||||
|
* @param min The minimum integer to generate
|
||||||
|
* @param max The maximum integer to generate
|
||||||
|
* @returns a random integer between {@linkcode min} and {@linkcode max} inclusive
|
||||||
|
*/
|
||||||
randSeedIntRange(min: integer, max: integer): integer {
|
randSeedIntRange(min: integer, max: integer): integer {
|
||||||
return this.randSeedInt((max - min) + 1, min);
|
return this.randSeedInt((max - min) + 1, min);
|
||||||
}
|
}
|
||||||
@ -4462,8 +4503,6 @@ export interface AttackMoveResult {
|
|||||||
export class PokemonSummonData {
|
export class PokemonSummonData {
|
||||||
public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ];
|
public statStages: number[] = [ 0, 0, 0, 0, 0, 0, 0 ];
|
||||||
public moveQueue: QueuedMove[] = [];
|
public moveQueue: QueuedMove[] = [];
|
||||||
public disabledMove: Moves = Moves.NONE;
|
|
||||||
public disabledTurns: number = 0;
|
|
||||||
public tags: BattlerTag[] = [];
|
public tags: BattlerTag[] = [];
|
||||||
public abilitySuppressed: boolean = false;
|
public abilitySuppressed: boolean = false;
|
||||||
public abilitiesApplied: Abilities[] = [];
|
public abilitiesApplied: Abilities[] = [];
|
||||||
@ -4544,7 +4583,7 @@ export type DamageResult = HitResult.EFFECTIVE | HitResult.SUPER_EFFECTIVE | Hit
|
|||||||
* It links to {@linkcode Move} class via the move ID.
|
* It links to {@linkcode Move} class via the move ID.
|
||||||
* Compared to {@linkcode Move}, this class also tracks if a move has received.
|
* Compared to {@linkcode Move}, this class also tracks if a move has received.
|
||||||
* PP Ups, amount of PP used, and things like that.
|
* PP Ups, amount of PP used, and things like that.
|
||||||
* @see {@linkcode isUsable} - checks if move is disabled, out of PP, or not implemented.
|
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
|
||||||
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
|
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
|
||||||
* @see {@linkcode usePp} - removes a point of PP from the move.
|
* @see {@linkcode usePp} - removes a point of PP from the move.
|
||||||
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
|
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
|
||||||
@ -4564,11 +4603,25 @@ export class PokemonMove {
|
|||||||
this.virtual = !!virtual;
|
this.virtual = !!virtual;
|
||||||
}
|
}
|
||||||
|
|
||||||
isUsable(pokemon: Pokemon, ignorePp?: boolean): boolean {
|
/**
|
||||||
if (this.moveId && pokemon.summonData?.disabledMove === this.moveId) {
|
* Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets.
|
||||||
|
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
|
||||||
|
*
|
||||||
|
* @param {Pokemon} pokemon {@linkcode Pokemon} that would be using this move
|
||||||
|
* @param {boolean} ignorePp If `true`, skips the PP check
|
||||||
|
* @param {boolean} ignoreRestrictionTags If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag})
|
||||||
|
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
|
||||||
|
*/
|
||||||
|
isUsable(pokemon: Pokemon, ignorePp?: boolean, ignoreRestrictionTags?: boolean): boolean {
|
||||||
|
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1) && !this.getMove().name.endsWith(" (N)");
|
|
||||||
|
if (this.getMove().name.endsWith(" (N)")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMove(): Move {
|
getMove(): Move {
|
||||||
|
@ -78,6 +78,7 @@ export class LoadingScene extends SceneBase {
|
|||||||
this.loadAtlas("overlay_hp_boss", "ui");
|
this.loadAtlas("overlay_hp_boss", "ui");
|
||||||
this.loadImage("overlay_exp", "ui");
|
this.loadImage("overlay_exp", "ui");
|
||||||
this.loadImage("icon_owned", "ui");
|
this.loadImage("icon_owned", "ui");
|
||||||
|
this.loadImage("icon_egg_move", "ui");
|
||||||
this.loadImage("ability_bar_left", "ui");
|
this.loadImage("ability_bar_left", "ui");
|
||||||
this.loadImage("bgm_bar", "ui");
|
this.loadImage("bgm_bar", "ui");
|
||||||
this.loadImage("party_exp_bar", "ui");
|
this.loadImage("party_exp_bar", "ui");
|
||||||
@ -164,6 +165,7 @@ export class LoadingScene extends SceneBase {
|
|||||||
this.loadImage("saving_icon", "ui");
|
this.loadImage("saving_icon", "ui");
|
||||||
this.loadImage("discord", "ui");
|
this.loadImage("discord", "ui");
|
||||||
this.loadImage("google", "ui");
|
this.loadImage("google", "ui");
|
||||||
|
this.loadImage("settings_icon", "ui");
|
||||||
|
|
||||||
this.loadImage("default_bg", "arenas");
|
this.loadImage("default_bg", "arenas");
|
||||||
// Load arena images
|
// Load arena images
|
||||||
@ -272,6 +274,7 @@ export class LoadingScene extends SceneBase {
|
|||||||
this.loadImage("gacha_knob", "egg");
|
this.loadImage("gacha_knob", "egg");
|
||||||
|
|
||||||
this.loadImage("egg_list_bg", "ui");
|
this.loadImage("egg_list_bg", "ui");
|
||||||
|
this.loadImage("egg_summary_bg", "ui");
|
||||||
|
|
||||||
this.loadImage("end_m", "cg");
|
this.loadImage("end_m", "cg");
|
||||||
this.loadImage("end_f", "cg");
|
this.loadImage("end_f", "cg");
|
||||||
|
@ -36,5 +36,6 @@
|
|||||||
"matBlock": "Tatami-Schild",
|
"matBlock": "Tatami-Schild",
|
||||||
"craftyShield": "Trickschutz",
|
"craftyShield": "Trickschutz",
|
||||||
"tailwind": "Rückenwind",
|
"tailwind": "Rückenwind",
|
||||||
"happyHour": "Goldene Zeiten"
|
"happyHour": "Goldene Zeiten",
|
||||||
|
"safeguard": "Bodyguard"
|
||||||
}
|
}
|
@ -52,6 +52,7 @@
|
|||||||
"postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!",
|
"postSummonTeravolt": "{{pokemonNameWithAffix}} is radiating a bursting aura!",
|
||||||
"postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!",
|
"postSummonDarkAura": "{{pokemonNameWithAffix}} is radiating a Dark Aura!",
|
||||||
"postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!",
|
"postSummonFairyAura": "{{pokemonNameWithAffix}} is radiating a Fairy Aura!",
|
||||||
|
"postSummonAuraBreak": "{{pokemonNameWithAffix}} reversed all other Pokémon's auras!",
|
||||||
"postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!",
|
"postSummonNeutralizingGas": "{{pokemonNameWithAffix}}'s Neutralizing Gas filled the area!",
|
||||||
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!",
|
"postSummonAsOneGlastrier": "{{pokemonNameWithAffix}} has two Abilities!",
|
||||||
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!",
|
"postSummonAsOneSpectrier": "{{pokemonNameWithAffix}} has two Abilities!",
|
||||||
|
@ -39,5 +39,6 @@
|
|||||||
"matBlock": "Mat Block",
|
"matBlock": "Mat Block",
|
||||||
"craftyShield": "Crafty Shield",
|
"craftyShield": "Crafty Shield",
|
||||||
"tailwind": "Tailwind",
|
"tailwind": "Tailwind",
|
||||||
"happyHour": "Happy Hour"
|
"happyHour": "Happy Hour",
|
||||||
|
"safeguard": "Safeguard"
|
||||||
}
|
}
|
@ -47,5 +47,11 @@
|
|||||||
"tailwindOnRemovePlayer": "Your team's Tailwind petered out!",
|
"tailwindOnRemovePlayer": "Your team's Tailwind petered out!",
|
||||||
"tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!",
|
"tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!",
|
||||||
"happyHourOnAdd": "Everyone is caught up in the happy atmosphere!",
|
"happyHourOnAdd": "Everyone is caught up in the happy atmosphere!",
|
||||||
"happyHourOnRemove": "The atmosphere returned to normal."
|
"happyHourOnRemove": "The atmosphere returned to normal.",
|
||||||
|
"safeguardOnAdd": "The whole field is cloaked in a mystical veil!",
|
||||||
|
"safeguardOnAddPlayer": "Your team cloaked itself in a mystical veil!",
|
||||||
|
"safeguardOnAddEnemy": "The opposing team cloaked itself in a mystical veil!",
|
||||||
|
"safeguardOnRemove": "The field is no longer protected by Safeguard!",
|
||||||
|
"safeguardOnRemovePlayer": "Your team is no longer protected by Safeguard!",
|
||||||
|
"safeguardOnRemoveEnemy": "The opposing team is no longer protected by Safeguard!"
|
||||||
}
|
}
|
@ -44,6 +44,7 @@
|
|||||||
"moveNotImplemented": "{{moveName}} is not yet implemented and cannot be selected.",
|
"moveNotImplemented": "{{moveName}} is not yet implemented and cannot be selected.",
|
||||||
"moveNoPP": "There's no PP left for\nthis move!",
|
"moveNoPP": "There's no PP left for\nthis move!",
|
||||||
"moveDisabled": "{{moveName}} is disabled!",
|
"moveDisabled": "{{moveName}} is disabled!",
|
||||||
|
"disableInterruptedMove": "{{pokemonNameWithAffix}}'s {{moveName}}\nis disabled!",
|
||||||
"noPokeballForce": "An unseen force\nprevents using Poké Balls.",
|
"noPokeballForce": "An unseen force\nprevents using Poké Balls.",
|
||||||
"noPokeballTrainer": "You can't catch\nanother trainer's Pokémon!",
|
"noPokeballTrainer": "You can't catch\nanother trainer's Pokémon!",
|
||||||
"noPokeballMulti": "You can only throw a Poké Ball\nwhen there is one Pokémon remaining!",
|
"noPokeballMulti": "You can only throw a Poké Ball\nwhen there is one Pokémon remaining!",
|
||||||
@ -61,6 +62,7 @@
|
|||||||
"skipItemQuestion": "Are you sure you want to skip taking an item?",
|
"skipItemQuestion": "Are you sure you want to skip taking an item?",
|
||||||
"itemStackFull": "The stack for {{fullItemName}} is full.\nYou will receive {{itemName}} instead.",
|
"itemStackFull": "The stack for {{fullItemName}} is full.\nYou will receive {{itemName}} instead.",
|
||||||
"eggHatching": "Oh?",
|
"eggHatching": "Oh?",
|
||||||
|
"eggSkipPrompt": "Skip to egg summary?",
|
||||||
"ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?",
|
"ivScannerUseQuestion": "Use IV Scanner on {{pokemonName}}?",
|
||||||
"wildPokemonWithAffix": "Wild {{pokemonName}}",
|
"wildPokemonWithAffix": "Wild {{pokemonName}}",
|
||||||
"foePokemonWithAffix": "Foe {{pokemonName}}",
|
"foePokemonWithAffix": "Foe {{pokemonName}}",
|
||||||
|
@ -67,5 +67,7 @@
|
|||||||
"saltCuredLapse": "{{pokemonNameWithAffix}} is hurt by {{moveName}}!",
|
"saltCuredLapse": "{{pokemonNameWithAffix}} is hurt by {{moveName}}!",
|
||||||
"cursedOnAdd": "{{pokemonNameWithAffix}} cut its own HP and put a curse on the {{pokemonName}}!",
|
"cursedOnAdd": "{{pokemonNameWithAffix}} cut its own HP and put a curse on the {{pokemonName}}!",
|
||||||
"cursedLapse": "{{pokemonNameWithAffix}} is afflicted by the Curse!",
|
"cursedLapse": "{{pokemonNameWithAffix}} is afflicted by the Curse!",
|
||||||
"stockpilingOnAdd": "{{pokemonNameWithAffix}} stockpiled {{stockpiledCount}}!"
|
"stockpilingOnAdd": "{{pokemonNameWithAffix}} stockpiled {{stockpiledCount}}!",
|
||||||
|
"disabledOnAdd": "{{pokemonNameWithAffix}}'s {{moveName}}\nwas disabled!",
|
||||||
|
"disabledLapse": "{{pokemonNameWithAffix}}'s {{moveName}}\nis no longer disabled."
|
||||||
}
|
}
|
@ -413,7 +413,7 @@
|
|||||||
},
|
},
|
||||||
"ariana": {
|
"ariana": {
|
||||||
"encounter": {
|
"encounter": {
|
||||||
"1": "Hold it right there! We can't someone on the loose.\n$It's harmful to Team Rocket's pride, you see.",
|
"1": "Hold it right there!\nWe can't have someone on the loose.\n$It's harmful to Team Rocket's pride, you see.",
|
||||||
"2": "I don't know or care if what I'm doing is right or wrong...\n$I just put my faith in Giovanni and do as I am told",
|
"2": "I don't know or care if what I'm doing is right or wrong...\n$I just put my faith in Giovanni and do as I am told",
|
||||||
"3": "Your trip ends here. I'm going to take you down!"
|
"3": "Your trip ends here. I'm going to take you down!"
|
||||||
},
|
},
|
||||||
|
@ -51,5 +51,7 @@
|
|||||||
"renamePokemon": "Rename Pokémon",
|
"renamePokemon": "Rename Pokémon",
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"nickname": "Nickname",
|
"nickname": "Nickname",
|
||||||
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect."
|
"errorServerDown": "Oops! There was an issue contacting the server.\n\nYou may leave this window open,\nthe game will automatically reconnect.",
|
||||||
|
"noSaves": "You don't have any save files on record!",
|
||||||
|
"tooManySaves": "You have too many save files on record!"
|
||||||
}
|
}
|
@ -47,10 +47,14 @@
|
|||||||
"description": "Changes a Pokémon's nature to {{natureName}} and permanently unlocks the nature for the starter."
|
"description": "Changes a Pokémon's nature to {{natureName}} and permanently unlocks the nature for the starter."
|
||||||
},
|
},
|
||||||
"DoubleBattleChanceBoosterModifierType": {
|
"DoubleBattleChanceBoosterModifierType": {
|
||||||
"description": "Doubles the chance of an encounter being a double battle for {{battleCount}} battles."
|
"description": "Quadruples the chance of an encounter being a double battle for up to {{battleCount}} battles."
|
||||||
},
|
},
|
||||||
"TempStatStageBoosterModifierType": {
|
"TempStatStageBoosterModifierType": {
|
||||||
"description": "Increases the {{stat}} of all party members by 1 stage for 5 battles."
|
"description": "Increases the {{stat}} of all party members by {{amount}} for up to 5 battles.",
|
||||||
|
"extra": {
|
||||||
|
"stage": "1 stage",
|
||||||
|
"percentage": "30%"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"AttackTypeBoosterModifierType": {
|
"AttackTypeBoosterModifierType": {
|
||||||
"description": "Increases the power of a Pokémon's {{moveType}}-type moves by 20%."
|
"description": "Increases the power of a Pokémon's {{moveType}}-type moves by 20%."
|
||||||
|
@ -66,5 +66,6 @@
|
|||||||
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
|
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
|
||||||
"revivalBlessing": "{{pokemonName}} was revived!",
|
"revivalBlessing": "{{pokemonName}} was revived!",
|
||||||
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
|
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
|
||||||
"exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!"
|
"exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!",
|
||||||
|
"safeguard": "{{targetName}} is protected by Safeguard!"
|
||||||
}
|
}
|
@ -36,5 +36,6 @@
|
|||||||
"matBlock": "Escudo Tatami",
|
"matBlock": "Escudo Tatami",
|
||||||
"craftyShield": "Truco Defensa",
|
"craftyShield": "Truco Defensa",
|
||||||
"tailwind": "Viento Afín",
|
"tailwind": "Viento Afín",
|
||||||
"happyHour": "Paga Extra"
|
"happyHour": "Paga Extra",
|
||||||
|
"safeguard": "Velo Sagrado"
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,14 @@
|
|||||||
"register": "Registrarse",
|
"register": "Registrarse",
|
||||||
"emptyUsername": "El usuario no puede estar vacío",
|
"emptyUsername": "El usuario no puede estar vacío",
|
||||||
"invalidLoginUsername": "El usuario no es válido",
|
"invalidLoginUsername": "El usuario no es válido",
|
||||||
"invalidRegisterUsername": "El usuario solo puede contener letras, números y guiones bajos",
|
"invalidRegisterUsername": "El usuario solo puede contener letras, números y guiones bajos.",
|
||||||
"invalidLoginPassword": "La contraseña no es válida",
|
"invalidLoginPassword": "La contraseña no es válida",
|
||||||
"invalidRegisterPassword": "La contraseña debe tener 6 o más caracteres.",
|
"invalidRegisterPassword": "La contraseña debe tener 6 o más caracteres.",
|
||||||
"usernameAlreadyUsed": "El usuario ya está en uso",
|
"usernameAlreadyUsed": "El usuario ya está en uso",
|
||||||
"accountNonExistent": "El usuario no existe",
|
"accountNonExistent": "El usuario no existe",
|
||||||
"unmatchingPassword": "La contraseña no coincide",
|
"unmatchingPassword": "La contraseña no coincide",
|
||||||
"passwordNotMatchingConfirmPassword": "Las contraseñas deben coincidir",
|
"passwordNotMatchingConfirmPassword": "Las contraseñas deben coincidir",
|
||||||
"confirmPassword": "Confirmar Contra.",
|
"confirmPassword": "Confirmar contraseña",
|
||||||
"registrationAgeWarning": "Al registrarte, confirmas tener 13 o más años de edad.",
|
"registrationAgeWarning": "Al registrarte, confirmas tener 13 o más años de edad.",
|
||||||
"backToLogin": "Volver al Login",
|
"backToLogin": "Volver al Login",
|
||||||
"failedToLoadSaveData": "No se han podido cargar los datos guardados. Por favor, recarga la página.\nSi el fallo continúa, por favor comprueba #announcements en nuestro Discord.",
|
"failedToLoadSaveData": "No se han podido cargar los datos guardados. Por favor, recarga la página.\nSi el fallo continúa, por favor comprueba #announcements en nuestro Discord.",
|
||||||
|
@ -36,5 +36,6 @@
|
|||||||
"matBlock": "Tatamigaeshi",
|
"matBlock": "Tatamigaeshi",
|
||||||
"craftyShield": "Vigilance",
|
"craftyShield": "Vigilance",
|
||||||
"tailwind": "Vent Arrière",
|
"tailwind": "Vent Arrière",
|
||||||
"happyHour": "Étrennes"
|
"happyHour": "Étrennes",
|
||||||
|
"safeguard": "Rune Protect"
|
||||||
}
|
}
|
@ -36,5 +36,6 @@
|
|||||||
"matBlock": "Ribaltappeto",
|
"matBlock": "Ribaltappeto",
|
||||||
"craftyShield": "Truccodifesa",
|
"craftyShield": "Truccodifesa",
|
||||||
"tailwind": "Ventoincoda",
|
"tailwind": "Ventoincoda",
|
||||||
"happyHour": "Cuccagna"
|
"happyHour": "Cuccagna",
|
||||||
|
"safeguard": "Salvaguardia"
|
||||||
}
|
}
|
@ -37,7 +37,7 @@
|
|||||||
"name_female": "ワンパンウーマン"
|
"name_female": "ワンパンウーマン"
|
||||||
},
|
},
|
||||||
"HealAchv": {
|
"HealAchv": {
|
||||||
"description": "一つの 技や 特性や 持っているアイテムで\n{{healAmount}}{{HP}}を 一気に 回復する"
|
"description": "一つの 技や 特性や 持たせたアイテムで\n{{HP}}{{healAmount}}を 一気に 回復する"
|
||||||
},
|
},
|
||||||
"250_HEAL": {
|
"250_HEAL": {
|
||||||
"name": "回復発見者"
|
"name": "回復発見者"
|
||||||
@ -52,7 +52,7 @@
|
|||||||
"name": "ジョーイさん"
|
"name": "ジョーイさん"
|
||||||
},
|
},
|
||||||
"LevelAchv": {
|
"LevelAchv": {
|
||||||
"description": "一つの ポケモンを Lv{{level}}まで レベルアップする"
|
"description": "一つの ポケモンを Lv.{{level}}まで 上げる"
|
||||||
},
|
},
|
||||||
"LV_100": {
|
"LV_100": {
|
||||||
"name": "まだまだだよ"
|
"name": "まだまだだよ"
|
||||||
@ -82,7 +82,7 @@
|
|||||||
"name": "マスターリーグチャンピオン"
|
"name": "マスターリーグチャンピオン"
|
||||||
},
|
},
|
||||||
"TRANSFER_MAX_STAT_STAGE": {
|
"TRANSFER_MAX_STAT_STAGE": {
|
||||||
"name": "同力",
|
"name": "連係プレー",
|
||||||
"description": "少なくとも 一つの 能力を 最大まで あげて\n他の 手持ちポケモンに バトンタッチする"
|
"description": "少なくとも 一つの 能力を 最大まで あげて\n他の 手持ちポケモンに バトンタッチする"
|
||||||
},
|
},
|
||||||
"MAX_FRIENDSHIP": {
|
"MAX_FRIENDSHIP": {
|
||||||
@ -94,7 +94,7 @@
|
|||||||
"description": "一つの 手持ちポケモンを メガシンカさせる"
|
"description": "一つの 手持ちポケモンを メガシンカさせる"
|
||||||
},
|
},
|
||||||
"GIGANTAMAX": {
|
"GIGANTAMAX": {
|
||||||
"name": "太―くて 堪らない",
|
"name": "太ーくて堪らない",
|
||||||
"description": "一つの 手持ちポケモンを キョダイマックスさせる"
|
"description": "一つの 手持ちポケモンを キョダイマックスさせる"
|
||||||
},
|
},
|
||||||
"TERASTALLIZE": {
|
"TERASTALLIZE": {
|
||||||
@ -106,7 +106,7 @@
|
|||||||
"description": "一つの 手持ちポケモンを ステラ・テラスタルさせる"
|
"description": "一つの 手持ちポケモンを ステラ・テラスタルさせる"
|
||||||
},
|
},
|
||||||
"SPLICE": {
|
"SPLICE": {
|
||||||
"name": "インフィニット・フュジョン",
|
"name": "インフィニット・フュージョン",
|
||||||
"description": "遺伝子のくさびで 二つの ポケモンを 吸収合体させる"
|
"description": "遺伝子のくさびで 二つの ポケモンを 吸収合体させる"
|
||||||
},
|
},
|
||||||
"MINI_BLACK_HOLE": {
|
"MINI_BLACK_HOLE": {
|
||||||
@ -205,7 +205,7 @@
|
|||||||
"description": "{{type}}タイプの 単一タイプチャレンジを クリアする"
|
"description": "{{type}}タイプの 単一タイプチャレンジを クリアする"
|
||||||
},
|
},
|
||||||
"MONO_NORMAL": {
|
"MONO_NORMAL": {
|
||||||
"name": "凡人"
|
"name": "超凡人"
|
||||||
},
|
},
|
||||||
"MONO_FIGHTING": {
|
"MONO_FIGHTING": {
|
||||||
"name": "八千以上だ!!"
|
"name": "八千以上だ!!"
|
||||||
@ -223,7 +223,7 @@
|
|||||||
"name": "タケシの挑戦状"
|
"name": "タケシの挑戦状"
|
||||||
},
|
},
|
||||||
"MONO_BUG": {
|
"MONO_BUG": {
|
||||||
"name": "チョウチョウせん者"
|
"name": "チョウチョウ戦者"
|
||||||
},
|
},
|
||||||
"MONO_GHOST": {
|
"MONO_GHOST": {
|
||||||
"name": "貞子ちゃん"
|
"name": "貞子ちゃん"
|
||||||
|
@ -1 +1,43 @@
|
|||||||
{}
|
{
|
||||||
|
"activeBattleEffects": "場の効果",
|
||||||
|
"player": "味方",
|
||||||
|
"neutral": "場の全員",
|
||||||
|
"enemy": "相手",
|
||||||
|
|
||||||
|
"sunny": "晴れ",
|
||||||
|
"rain": "雨",
|
||||||
|
"sandstorm": "砂あらし",
|
||||||
|
"hail": "あられ",
|
||||||
|
"snow": "雪",
|
||||||
|
"fog": "きり",
|
||||||
|
"heavyRain": "強い雨",
|
||||||
|
"harshSun": "大日照り",
|
||||||
|
"strongWinds": "乱気流",
|
||||||
|
|
||||||
|
"misty": "ミストフィールド",
|
||||||
|
"electric": "エレキフィールド",
|
||||||
|
"grassy": "グラスフィールド",
|
||||||
|
"psychic": "サイコフィールド",
|
||||||
|
|
||||||
|
"mudSport": "どろあそび",
|
||||||
|
"waterSport": "みずあそび",
|
||||||
|
"spikes": "まきびし",
|
||||||
|
"toxicSpikes": "どくびし",
|
||||||
|
"mist": "しろいきり",
|
||||||
|
"futureSight": "みらいよち",
|
||||||
|
"doomDesire": "はめつのねがい",
|
||||||
|
"wish": "ねがいごと",
|
||||||
|
"stealthRock": "ステルスロック",
|
||||||
|
"stickyWeb": "ねばねばネット",
|
||||||
|
"trickRoom": "トリックルーム",
|
||||||
|
"gravity": "じゅうりょく",
|
||||||
|
"reflect": "リフレクター",
|
||||||
|
"lightScreen": "ひかりのかべ",
|
||||||
|
"auroraVeil": "オーロラベール",
|
||||||
|
"quickGuard": "ファストガード",
|
||||||
|
"wideGuard": "ワイドガード",
|
||||||
|
"matBlock": "たたみがえし",
|
||||||
|
"craftyShield": "トリックガード",
|
||||||
|
"tailwind": "おいかぜ",
|
||||||
|
"happyHour": "ハッピータイム"
|
||||||
|
}
|
||||||
|
@ -1 +1,150 @@
|
|||||||
{}
|
{
|
||||||
|
"music": "BGM: ",
|
||||||
|
"missing_entries": "{{name}}",
|
||||||
|
"battle_kanto_champion": "B2W2 戦闘!チャンピオン(カントー)",
|
||||||
|
"battle_johto_champion": "B2W2 戦闘!チャンピオン(ジョウト)",
|
||||||
|
"battle_hoenn_champion_g5": "B2W2 戦闘!チャンピオン(ホウエン)",
|
||||||
|
"battle_hoenn_champion_g6": "ORAS 決戦!ダイゴ",
|
||||||
|
"battle_sinnoh_champion": "B2W2 戦闘!チャンピオン(シンオウ)",
|
||||||
|
"battle_champion_alder": "BW チャンピオン アデク",
|
||||||
|
"battle_champion_iris": "B2W2 戦闘!チャンピオンアイリス",
|
||||||
|
"battle_kalos_champion": "XY 戦闘!チャンピオン",
|
||||||
|
"battle_alola_champion": "USUM 頂上決戦!ハウ",
|
||||||
|
"battle_galar_champion": "SWSH 決戦!チャンピオンダンデ",
|
||||||
|
"battle_champion_geeta": "SV 戦闘!トップチャンピオン",
|
||||||
|
"battle_champion_nemona": "SV 戦闘!チャンピオンネモ",
|
||||||
|
"battle_champion_kieran": "SV 戦闘!チャンピオンスグリ",
|
||||||
|
"battle_hoenn_elite": "ORAS 戦闘!四天王",
|
||||||
|
"battle_unova_elite": "BW 戦闘!四天王",
|
||||||
|
"battle_kalos_elite": "XY 戦闘!四天王",
|
||||||
|
"battle_alola_elite": "SM 戦闘!四天王",
|
||||||
|
"battle_galar_elite": "SWSH 戦闘!ファイナルトーナメント!",
|
||||||
|
"battle_paldea_elite": "SV 戦闘!四天王",
|
||||||
|
"battle_bb_elite": "SV 戦闘!ブルベリーグ四天王",
|
||||||
|
"battle_final_encounter": "ポケダンDX レックウザ登場",
|
||||||
|
"battle_final": "BW 戦闘!ゲーチス",
|
||||||
|
"battle_kanto_gym": "B2W2 戦闘!ジムリーダー(カントー)",
|
||||||
|
"battle_johto_gym": "B2W2 戦闘!ジムリーダー(ジョウト)",
|
||||||
|
"battle_hoenn_gym": "B2W2 戦闘!ジムリーダー(ホウエン)",
|
||||||
|
"battle_sinnoh_gym": "B2W2 戦闘!ジムリーダー(シンオウ)",
|
||||||
|
"battle_unova_gym": "BW 戦闘!ジムリーダー",
|
||||||
|
"battle_kalos_gym": "XY 戦闘!ジムリーダー",
|
||||||
|
"battle_galar_gym": "SWSH 戦闘!ジムリーダー",
|
||||||
|
"battle_paldea_gym": "SV 戦闘!ジムリーダー",
|
||||||
|
"battle_legendary_kanto": "XY 戦闘!ミュウツー",
|
||||||
|
"battle_legendary_raikou": "HGSS 戦闘!ライコウ",
|
||||||
|
"battle_legendary_entei": "HGSS 戦闘!エンテイ",
|
||||||
|
"battle_legendary_suicune": "HGSS 戦闘!スイクン",
|
||||||
|
"battle_legendary_lugia": "HGSS 戦闘!ルギア",
|
||||||
|
"battle_legendary_ho_oh": "HGSS 戦闘!ホウオウ",
|
||||||
|
"battle_legendary_regis_g5": "B2W2 戦闘!レジロック・レジアイス・レジスチル",
|
||||||
|
"battle_legendary_regis_g6": "ORAS 戦闘!レジロック・レジアイス・レジスチル",
|
||||||
|
"battle_legendary_gro_kyo": "ORAS 戦闘!ゲンシカイキ",
|
||||||
|
"battle_legendary_rayquaza": "ORAS 戦闘!超古代ポケモン",
|
||||||
|
"battle_legendary_deoxys": "ORAS 戦闘!デオキシス",
|
||||||
|
"battle_legendary_lake_trio": "ORAS 戦闘!ユクシー・エムリット・アグノム",
|
||||||
|
"battle_legendary_sinnoh": "ORAS 戦闘!伝説のポケモン(シンオウ)",
|
||||||
|
"battle_legendary_dia_pal": "ORAS 戦闘!ディアルガ・パルキア",
|
||||||
|
"battle_legendary_origin_forme": "LA 戦い:ディアルガ・パルキア(オリジンフォルム)",
|
||||||
|
"battle_legendary_giratina": "ORAS 戦闘!ギラティナ",
|
||||||
|
"battle_legendary_arceus": "HGSS アルセウス",
|
||||||
|
"battle_legendary_unova": "BW 戦闘!伝説のポケモン",
|
||||||
|
"battle_legendary_kyurem": "BW 戦闘!キュレム",
|
||||||
|
"battle_legendary_res_zek": "BW 戦闘!ゼクロム・レシラム",
|
||||||
|
"battle_legendary_xern_yvel": "XY 戦闘!ゼルネアス・イベルタル・ジガルデ",
|
||||||
|
"battle_legendary_tapu": "SM 戦闘!カプ",
|
||||||
|
"battle_legendary_sol_lun": "SM 戦闘!ソルガレオ・ルナアーラ",
|
||||||
|
"battle_legendary_ub": "SM 戦闘!ウルトラビースト",
|
||||||
|
"battle_legendary_dusk_dawn": "USUM 戦闘!日食・月食ネクロズマ",
|
||||||
|
"battle_legendary_ultra_nec": "USUM 戦闘!ウルトラネクロズマ",
|
||||||
|
"battle_legendary_zac_zam": "SWSH 戦闘!ザシアン・ザマゼンタ",
|
||||||
|
"battle_legendary_glas_spec": "SWSH 戦闘!ブリザポス・レイスポス",
|
||||||
|
"battle_legendary_calyrex": "SWSH 戦闘!バドレックス",
|
||||||
|
"battle_legendary_riders": "SWSH 戦闘!豊穣の王",
|
||||||
|
"battle_legendary_birds_galar": "SWSH 戦闘!伝説のとりポケモン",
|
||||||
|
"battle_legendary_ruinous": "SV 戦闘!災厄ポケモン",
|
||||||
|
"battle_legendary_kor_mir": "SV 戦闘!エリアゼロのポケモン",
|
||||||
|
"battle_legendary_loyal_three": "SV 戦闘!ともっこ",
|
||||||
|
"battle_legendary_ogerpon": "SV 戦闘!オーガポン",
|
||||||
|
"battle_legendary_terapagos": "SV 戦闘!テラパゴス",
|
||||||
|
"battle_legendary_pecharunt": "SV 戦闘!モモワロウ",
|
||||||
|
"battle_rival": "BW 戦闘!チェレン・ベル",
|
||||||
|
"battle_rival_2": "BW 戦闘!N",
|
||||||
|
"battle_rival_3": "BW 決戦!N",
|
||||||
|
"battle_trainer": "BW 戦闘!トレーナー",
|
||||||
|
"battle_wild": "BW 戦闘!野生ポケモン",
|
||||||
|
"battle_wild_strong": "BW 戦闘!強い野生ポケモン",
|
||||||
|
"end_summit": "ポケダンDX 天空の塔 最上階",
|
||||||
|
"battle_rocket_grunt": "HGSS 戦闘!ロケット団",
|
||||||
|
"battle_aqua_magma_grunt": "ORAS 戦闘!アクア団・マグマ団",
|
||||||
|
"battle_galactic_grunt": "BDSP 戦闘!ギンガ団",
|
||||||
|
"battle_plasma_grunt": "BW 戦闘!プラズマ団",
|
||||||
|
"battle_flare_grunt": "XY 戦闘!フレア団",
|
||||||
|
"battle_aether_grunt": "SM 戦闘!エーテル財団トレーナー",
|
||||||
|
"battle_skull_grunt": "SM 戦闘!スカル団",
|
||||||
|
"battle_macro_grunt": "SWSH 戦闘!トレーナー",
|
||||||
|
"battle_galactic_admin": "BDSP 戦闘!ギンガ団幹部",
|
||||||
|
"battle_skull_admin": "SM 戦闘!スカル団幹部",
|
||||||
|
"battle_oleana": "SWSH 戦闘!オリーヴ",
|
||||||
|
"battle_rocket_boss": "USUM 戦闘!レインボーロケット団ボス",
|
||||||
|
"battle_aqua_magma_boss": "ORAS 戦闘!アクア団・マグマ団のリーダー",
|
||||||
|
"battle_galactic_boss": "BDSP 戦闘!ギンガ団ボス",
|
||||||
|
"battle_plasma_boss": "B2W2 戦闘!ゲーチス",
|
||||||
|
"battle_flare_boss": "XY 戦闘!フラダリ",
|
||||||
|
"battle_aether_boss": "SM 戦闘!ルザミーネ",
|
||||||
|
"battle_skull_boss": "SM 戦闘!スカル団ボス",
|
||||||
|
"battle_macro_boss": "SWSH 戦闘!ローズ",
|
||||||
|
|
||||||
|
"abyss": "ポケダン空 やみのかこう",
|
||||||
|
"badlands": "ポケダン空 こかつのたに",
|
||||||
|
"beach": "ポケダン空 しめったいわば",
|
||||||
|
"cave": "ポケダン空 そらのいただき(どうくつ)",
|
||||||
|
"construction_site": "ポケダン空 きょだいがんせきぐん",
|
||||||
|
"desert": "ポケダン空 きたのさばく",
|
||||||
|
"dojo": "ポケダン空 ガラガラどうじょう",
|
||||||
|
"end": "ポケダンDX 天空の塔",
|
||||||
|
"factory": "ポケダン空 かくされたいせき",
|
||||||
|
"fairy_cave": "ポケダン空 ほしのどうくつ",
|
||||||
|
"forest": "ポケダン空 くろのもり",
|
||||||
|
"grass": "ポケダン空 リンゴのもり",
|
||||||
|
"graveyard": "ポケダン空 しんぴのもり",
|
||||||
|
"ice_cave": "ポケダン空 だいひょうざん",
|
||||||
|
"island": "ポケダン空 えんがんのいわば",
|
||||||
|
"jungle": "Lmz - Jungle(ジャングル)",
|
||||||
|
"laboratory": "Firel - Laboratory(ラボラトリー)",
|
||||||
|
"lake": "ポケダン空 すいしょうのどうくつ",
|
||||||
|
"meadow": "ポケダン空 そらのいただき(もり)",
|
||||||
|
"metropolis": "Firel - Metropolis(大都市)",
|
||||||
|
"mountain": "ポケダン空 ツノやま",
|
||||||
|
"plains": "ポケダン空 そらのいただき(そうげん)",
|
||||||
|
"power_plant": "ポケダン空 エレキへいげん",
|
||||||
|
"ruins": "ポケダン空 ふういんのいわば",
|
||||||
|
"sea": "Andr06 - Marine Mystique(海の神秘性)",
|
||||||
|
"seabed": "Firel - Seabed(海底)",
|
||||||
|
"slum": "Andr06 - Sneaky Snom(ずるいユキハミ)",
|
||||||
|
"snowy_forest": "ポケダン空 そらのいただき(ゆきやま)",
|
||||||
|
"space": "Firel - Aether(エーテル)",
|
||||||
|
"swamp": "ポケダン空 とざされたうみ",
|
||||||
|
"tall_grass": "ポケダン空 のうむのもり",
|
||||||
|
"temple": "ポケダン空 ばんにんのどうくつ",
|
||||||
|
"town": "ポケダン空 ランダムダンジョン3",
|
||||||
|
"volcano": "ポケダン空 ねっすいのどうくつ",
|
||||||
|
"wasteland": "ポケダン空 まぼろしのだいち",
|
||||||
|
"encounter_ace_trainer": "BW 視線!エリートトレーナー",
|
||||||
|
"encounter_backpacker": "BW 視線!バックパッカー",
|
||||||
|
"encounter_clerk": "BW 視線!ビジネスマン",
|
||||||
|
"encounter_cyclist": "BW 視線!サイクリング",
|
||||||
|
"encounter_lass": "BW 視線!ミニスカート",
|
||||||
|
"encounter_parasol_lady": "BW 視線!パラソルおねえさん",
|
||||||
|
"encounter_pokefan": "BW 視線!だいすきクラブ",
|
||||||
|
"encounter_psychic": "BW 視線!サイキッカー",
|
||||||
|
"encounter_rich": "BW 視線!ジェントルマン",
|
||||||
|
"encounter_rival": "BW チェレンのテーマ",
|
||||||
|
"encounter_roughneck": "BW 視線!スキンヘッズ",
|
||||||
|
"encounter_scientist": "BW 視線!けんきゅういん",
|
||||||
|
"encounter_twins": "BW 視線!ふたごちゃん",
|
||||||
|
"encounter_youngster": "BW 視線!たんぱんこぞう",
|
||||||
|
"heal": "BW 回復",
|
||||||
|
"menu": "ポケダン空 ようこそ! ポケモンたちのせかいへ!",
|
||||||
|
"title": "ポケダン空 トップメニュー"
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"title": "チャレンジを 設定",
|
"title": "チャレンジの設定",
|
||||||
"illegalEvolution": "{{pokemon}}は このチャレンジで\n対象外の ポケモンに なってしまった!",
|
"illegalEvolution": "{{pokemon}}は このチャレンジで\n対象外の ポケモンに なってしまった!",
|
||||||
"singleGeneration": {
|
"singleGeneration": {
|
||||||
"name": "単一世代",
|
"name": "単一世代",
|
||||||
|
@ -1 +1,8 @@
|
|||||||
{}
|
{
|
||||||
|
"start": "スタート",
|
||||||
|
"luckIndicator": "運:",
|
||||||
|
"shinyOnHover": "色違い",
|
||||||
|
"commonShiny": "ふつう",
|
||||||
|
"rareShiny": "レア",
|
||||||
|
"epicShiny": "超レア"
|
||||||
|
}
|
||||||
|
@ -1 +1,84 @@
|
|||||||
{}
|
{
|
||||||
|
"blue_red_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Blue: Hey Red, let's show them what we're made of!\n$Red: ...\n$Blue: This is Pallet Town Power!"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Blue: That was a great battle!\n$Red: ..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"red_blue_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Red: ...!\n$Blue: He never talks much.\n$Blue: But dont let that fool you! He is a champ after all!"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Red: ...!\n$Blue: Next time we will beat you!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tate_liza_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Tate: Are you surprised?\n$Liza: We are two gym leaders at once!\n$Tate: We are twins!\n$Liza: We dont need to talk to understand each other!\n$Tate: Twice the power...\n$Liza: Can you handle it?"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Tate: What? Our combination was perfect!\n$Liza: Looks like we need to train more..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"liza_tate_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Liza: Hihihi... Are you surprised?\n$Tate: Yes, we are really two gym leaders at once!\n$Liza: This is my twin brother Tate!\n$Tate: And this is my twin sister Liza!\n$Liza: Don't you think we are a perfect combination?"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Liza: Are we...\n$Tate: ...not as strong as we thought?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wallace_steven_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Steven: Wallace, let's show them the power of the champions!\n$Wallace: We will show you the power of Hoenn!\n$Steven: Let's go!"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Steven: That was a great battle!\n$Wallace: We will win next time!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steven_wallace_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Steven: Do you have any rare Pokémon?\n$Wallace: Steven... We are here for a battle, not to show off our Pokémon.\n$Steven: Oh... I see... Let's go then!"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Steven: Now that we are done with the battle, let's show off our Pokémon!\n$Wallace: Steven..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"alder_iris_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Alder: We are the strongest trainers in Unova!\n$Iris: Fights against strong trainers are the best!"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Alder: Wow! You are super strong!\n$Iris: We will win next time!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"iris_alder_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Iris: Welcome Challenger! I am THE Unova Champion!\n$Alder: Iris, aren't you a bit too excited?",
|
||||||
|
"1_female": "Iris: Welcome Challenger! I am THE Unova Champion!\n$Alder: Iris, aren't you a bit too excited?"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Iris: A loss like this is not easy to take...\n$Alder: But we will only get stronger with every loss!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"piers_marnie_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Marnie: Brother, let's show them the power of Spikemuth!\n$Piers: We bring darkness!"
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Marnie: You brought light to our darkness!\n$Piers: Its too bright..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"marnie_piers_double": {
|
||||||
|
"encounter": {
|
||||||
|
"1": "Piers: Ready for a concert?\n$Marnie: Brother... They are here to fight, not to sing...",
|
||||||
|
"1_female": "Piers: Ready for a concert?\n$Marnie: Brother... They are here to fight, not to sing..."
|
||||||
|
},
|
||||||
|
"victory": {
|
||||||
|
"1": "Piers: Now that was a great concert!\n$Marnie: Brother..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1 +1,10 @@
|
|||||||
{}
|
{
|
||||||
|
"encounter": "It appears the time has finally come once again.\nYou know why you have come here, do you not?\n$You were drawn here, because you have been here before.\nCountless times.\n$Though, perhaps it can be counted.\nTo be precise, this is in fact your {{cycleCount}} cycle.\n$Each cycle your mind reverts to its former state.\nEven so, somehow, remnants of your former selves remain.\n$Until now you have yet to succeed, but I sense a different presence in you this time.\n\n$You are the only one here, though it is as if there is… another.\n$Will you finally prove a formidable challenge to me?\nThe challenge I have longed after for millennia?\n$We begin.",
|
||||||
|
"encounter_female": "It appears the time has finally come once again.\nYou know why you have come here, do you not?\n$You were drawn here, because you have been here before.\nCountless times.\n$Though, perhaps it can be counted.\nTo be precise, this is in fact your {{cycleCount}} cycle.\n$Each cycle your mind reverts to its former state.\nEven so, somehow, remnants of your former selves remain.\n$Until now you have yet to succeed, but I sense a different presence in you this time.\n\n$You are the only one here, though it is as if there is… another.\n$Will you finally prove a formidable challenge to me?\nThe challenge I have longed after for millennia?\n$We begin.",
|
||||||
|
"firstStageWin": "I see. The presence I felt was indeed real.\nIt appears I no longer need to hold back.\n$Do not disappoint me.",
|
||||||
|
"secondStageWin": "…Magnificent.",
|
||||||
|
"key_ordinal_one": "st",
|
||||||
|
"key_ordinal_two": "nd",
|
||||||
|
"key_ordinal_few": "rd",
|
||||||
|
"key_ordinal_other": "th"
|
||||||
|
}
|
||||||
|
@ -1 +1,6 @@
|
|||||||
{}
|
{
|
||||||
|
"ending": "@c{shock}You're back?@d{32} Does that mean…@d{96} you won?!\n@c{smile_ehalf}I should have known you had it in you.\n$@c{smile_eclosed}Of course… I always had that feeling.\n@c{smile}It's over now, right? You ended the loop.\n$@c{smile_ehalf}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$I'll be the only one to remember what you did.\n@c{angry_mopen}I'll try not to forget!\n$@c{smile_wave_wink}Just kidding!@d{64} @c{smile}I'd never forget.@d{32}\nYour legend will live on in our hearts.\n$@c{smile_wave}Anyway,@d{64} it's getting late…@d{96} I think?\nIt's hard to tell in this place.\n$Let's go home. @c{smile_wave_wink}Maybe tomorrow, we can have another battle, for old time's sake?",
|
||||||
|
"ending_female": "@c{smile}Oh? You won?@d{96} @c{smile_eclosed}I guess I should've known.\nBut, you're back now.\n$@c{smile}It's over.@d{64} You ended the loop.\n$@c{serious_smile_fists}You fulfilled your dream too, didn't you?\nYou didn't lose even once.\n$@c{neutral}I'm the only one who'll remember what you did.@d{96}\nI guess that's okay, isn't it?\n$@c{serious_smile_fists}Your legend will always live on in our hearts.\n$@c{smile_eclosed}Anyway, I've had about enough of this place, haven't you? Let's head home.\n$@c{serious_smile_fists}Maybe when we get back, we can have another battle?\nIf you're up to it.",
|
||||||
|
"ending_endless": "Congratulations on reaching the current end!\nMore content is coming soon.",
|
||||||
|
"ending_name": "Devs"
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pp": "PP",
|
"pp": "PP",
|
||||||
"power": "いりょく",
|
"power": "威力",
|
||||||
"accuracy": "めいちゅう",
|
"accuracy": "命中",
|
||||||
"abilityFlyInText": " {{pokemonName}}の {{passive}}{{abilityName}}",
|
"abilityFlyInText": " {{pokemonName}}の\n{{passive}}:{{abilityName}}",
|
||||||
"passive": "Passive "
|
"passive": "パッシブ "
|
||||||
}
|
}
|
@ -32,7 +32,7 @@
|
|||||||
"noPokerus": "ポケルス - なし",
|
"noPokerus": "ポケルス - なし",
|
||||||
"sortByNumber": "No.",
|
"sortByNumber": "No.",
|
||||||
"sortByCost": "ポイント",
|
"sortByCost": "ポイント",
|
||||||
"sortByCandies": "飴の数",
|
"sortByCandies": "アメの数",
|
||||||
"sortByIVs": "個体値",
|
"sortByIVs": "個体値",
|
||||||
"sortByName": "名前"
|
"sortByName": "名前"
|
||||||
}
|
}
|
@ -12,26 +12,26 @@
|
|||||||
"dailyRunAttempts": "デイリーラン",
|
"dailyRunAttempts": "デイリーラン",
|
||||||
"dailyRunWins": "デイリーラン勝利",
|
"dailyRunWins": "デイリーラン勝利",
|
||||||
"endlessRuns": "エンドレスラン",
|
"endlessRuns": "エンドレスラン",
|
||||||
"highestWaveEndless": "エンドレス最高波",
|
"highestWaveEndless": "エンドレス最高ラウンド",
|
||||||
"highestMoney": "最大貯金",
|
"highestMoney": "最大貯金",
|
||||||
"highestDamage": "最大ダメージ",
|
"highestDamage": "最大ダメージ",
|
||||||
"highestHPHealed": "最大HP回復",
|
"highestHPHealed": "最大HP回復",
|
||||||
"pokemonEncountered": "遭遇したポケモン",
|
"pokemonEncountered": "遭遇したポケモン",
|
||||||
"pokemonDefeated": "倒したポケモン",
|
"pokemonDefeated": "倒したポケモン",
|
||||||
"pokemonCaught": "捕まえたポケモン",
|
"pokemonCaught": "捕まえたポケモン",
|
||||||
"eggsHatched": "孵化したタマゴ",
|
"eggsHatched": "ふかしたタマゴ",
|
||||||
"subLegendsSeen": "見つけた順伝説ポケモン",
|
"subLegendsSeen": "見つけた順伝説ポケモン",
|
||||||
"subLegendsCaught": "捕まえた準伝説ポケモン",
|
"subLegendsCaught": "捕まえた準伝説ポケモン",
|
||||||
"subLegendsHatched": "孵化した準伝説ポケモン",
|
"subLegendsHatched": "ふかした準伝説ポケモン",
|
||||||
"legendsSeen": "見つけた伝説ポケモン",
|
"legendsSeen": "見つけた伝説ポケモン",
|
||||||
"legendsCaught": "捕まえた伝説ポケモン",
|
"legendsCaught": "捕まえた伝説ポケモン",
|
||||||
"legendsHatched": "孵化した伝説ポケモン",
|
"legendsHatched": "ふかした伝説ポケモン",
|
||||||
"mythicalsSeen": "見つけた幻ポケモン",
|
"mythicalsSeen": "見つけた幻ポケモン",
|
||||||
"mythicalsCaught": "捕まえた幻ポケモン",
|
"mythicalsCaught": "捕まえた幻ポケモン",
|
||||||
"mythicalsHatched": "孵化した幻ポケモン",
|
"mythicalsHatched": "ふかした幻ポケモン",
|
||||||
"shiniesSeen": "見つけた色違いポケモン",
|
"shiniesSeen": "見つけた色違いポケモン",
|
||||||
"shiniesCaught": "捕まえた色違いポケモン",
|
"shiniesCaught": "捕まえた色違いポケモン",
|
||||||
"shiniesHatched": "孵化した色違いポケモン",
|
"shiniesHatched": "ふかした色違いポケモン",
|
||||||
"pokemonFused": "吸収合体したポケモン",
|
"pokemonFused": "吸収合体したポケモン",
|
||||||
"trainersDefeated": "倒したトレーナー",
|
"trainersDefeated": "倒したトレーナー",
|
||||||
"eggsPulled": "引いたタマゴ",
|
"eggsPulled": "引いたタマゴ",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"Erratic": "60まんタイプ",
|
"Erratic": "60万タイプ",
|
||||||
"Fast": "80まんタイプ",
|
"Fast": "80万タイプ",
|
||||||
"Medium_Fast": "100まんタイプ",
|
"Medium_Fast": "100万タイプ",
|
||||||
"Medium_Slow": "105まんタイプ",
|
"Medium_Slow": "105万タイプ",
|
||||||
"Slow": "125まんタイプ",
|
"Slow": "125万タイプ",
|
||||||
"Fluctuating": "164まんタイプ"
|
"Fluctuating": "164万タイプ"
|
||||||
}
|
}
|
@ -24,6 +24,6 @@
|
|||||||
"linkGoogle": "Google連携",
|
"linkGoogle": "Google連携",
|
||||||
"unlinkGoogle": "Google連携解除",
|
"unlinkGoogle": "Google連携解除",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"losingProgressionWarning": "戦闘開始からの データが 保存されません。\nよろしいですか?",
|
"losingProgressionWarning": "戦闘開始からの データが セーブされません。\nよろしいですか?",
|
||||||
"noEggs": "現在は タマゴを 孵化していません!"
|
"noEggs": "現在は タマゴを ふかしていません!"
|
||||||
}
|
}
|
||||||
|
@ -353,7 +353,7 @@
|
|||||||
"description": "やせいのポケモンがかくれとくせいをもつかくりつをおおきくふやす"
|
"description": "やせいのポケモンがかくれとくせいをもつかくりつをおおきくふやす"
|
||||||
},
|
},
|
||||||
"IV_SCANNER": {
|
"IV_SCANNER": {
|
||||||
"name": "こたいち たんちき",
|
"name": "こたいちスキャナー",
|
||||||
"description": "やせいのポケモンのこたいちをスキャンできる。スタックごとに2つのこたいちがあきらかになる。もっともたかいこたいちがさいしょにひょうじされる"
|
"description": "やせいのポケモンのこたいちをスキャンできる。スタックごとに2つのこたいちがあきらかになる。もっともたかいこたいちがさいしょにひょうじされる"
|
||||||
},
|
},
|
||||||
"DNA_SPLICERS": {
|
"DNA_SPLICERS": {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"surviveDamageApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で もちこたえた!",
|
"surviveDamageApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で もちこたえた!",
|
||||||
"turnHealApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 少し 回復!",
|
"turnHealApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 少し 体力を 回復した!",
|
||||||
"hitHealApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 少し 回復!",
|
"hitHealApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 少し 体力を 回復した!",
|
||||||
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 復活した!",
|
"pokemonInstantReviveApply": "{{pokemonNameWithAffix}}は\n{{typeName}}で 復活した!",
|
||||||
"resetNegativeStatStageApply": "{{pokemonNameWithAffix}}は {{typeName}}で\n下がった能力が 元に戻った!",
|
"resetNegativeStatStageApply": "{{pokemonNameWithAffix}}は {{typeName}}で\n下がった能力が 元に戻った!",
|
||||||
"moneyInterestApply": "{{typeName}}から {{moneyAmount}}円 取得した!",
|
"moneyInterestApply": "{{typeName}}から {{moneyAmount}}円 取得した!",
|
||||||
"turnHeldItemTransferApply": "{{pokemonName}}の {{typeName}}が\n{{pokemonNameWithAffix}}の {{itemName}}を 吸い取った!",
|
"turnHeldItemTransferApply": "{{pokemonName}}の {{typeName}}が\n{{pokemonNameWithAffix}}の {{itemName}}を 吸い取った!",
|
||||||
"contactHeldItemTransferApply": "{{pokemonName}}の {{typeName}}が\n{{pokemonNameWithAffix}}の {{itemName}}を うばい取った!",
|
"contactHeldItemTransferApply": "{{pokemonName}}の {{typeName}}が\n{{pokemonNameWithAffix}}の {{itemName}}を 奪い取った!",
|
||||||
"enemyTurnHealApply": "{{pokemonNameWithAffix}}は\n体力を 回復!",
|
"enemyTurnHealApply": "{{pokemonNameWithAffix}}は\n体力を 回復!",
|
||||||
"bypassSpeedChanceApply": "{{pokemonName}}は {{itemName}}で\n行動が はやくなった!"
|
"bypassSpeedChanceApply": "{{pokemonName}}は {{itemName}}で\n行動が はやくなった!"
|
||||||
}
|
}
|
@ -1,64 +1,69 @@
|
|||||||
{
|
{
|
||||||
"hitWithRecoil": "{{pokemonName}}は\nはんどうによる ダメージを うけた!",
|
"hitWithRecoil": "{{pokemonName}}は\n反動による ダメージを 受けた!",
|
||||||
"cutHpPowerUpMove": "{{pokemonName}}は\nたいりょくを けずって パワーぜんかい!",
|
"cutHpPowerUpMove": "{{pokemonName}}は\n体力を 削って 技の 威力を 上がった!",
|
||||||
"absorbedElectricity": "{{pokemonName}}は\n でんきを きゅうしゅうした!",
|
"absorbedElectricity": "{{pokemonName}}は\n 電気を 吸収した!",
|
||||||
"switchedStatChanges": "{{pokemonName}}は あいてと じぶんの\nのうりょくへんかを いれかえた!",
|
"switchedStatChanges": "{{pokemonName}}は 相手と 自分の\n能力変化を 入れ替えた!",
|
||||||
"sharedGuard": "{{pokemonName}}は\nおたがいのガードを シェアした!",
|
"switchedTwoStatChanges": "{{pokemonName}}は 相手と 自分の {{firstStat}}と\n{{secondStat}}の 能力変化を 入れ替えた!",
|
||||||
"sharedPower": "{{pokemonName}}は\nおたがいのパワーを シェアした!",
|
"switchedStat": "{{pokemonName}}は 相手と {{stat}}を 入れ替えた!",
|
||||||
"goingAllOutForAttack": "{{pokemonName}}は\nほんきを だした!",
|
"sharedGuard": "{{pokemonName}}は\nお互いのガードを シェアした!",
|
||||||
"regainedHealth": "{{pokemonName}}は\nたいりょくを かいふくした!",
|
"sharedPower": "{{pokemonName}}は\nお互いのパワーを シェアした!",
|
||||||
"keptGoingAndCrashed": "いきおいあまって {{pokemonName}}は\nじめんに ぶつかった!",
|
"goingAllOutForAttack": "{{pokemonName}}は\n本気を 出した!",
|
||||||
"fled": "{{pokemonName}}は にげだした!",
|
"regainedHealth": "{{pokemonName}}は\n体力を 回復した!",
|
||||||
"cannotBeSwitchedOut": "{{pokemonName}}を\nもどすことが できない!",
|
"keptGoingAndCrashed": "勢い余って {{pokemonName}}は\n地面に ぶつかった!",
|
||||||
"swappedAbilitiesWithTarget": "{{pokemonName}}は\nおたがいの とくせいを いれかえた!",
|
"fled": "{{pokemonName}}は 逃げ出した!",
|
||||||
"coinsScatteredEverywhere": "こばんが あたりに ちらばった!",
|
"cannotBeSwitchedOut": "{{pokemonName}}を\n戻すことが できない!",
|
||||||
|
"swappedAbilitiesWithTarget": "{{pokemonName}}は\nお互いの 特性を 入れ替えた!",
|
||||||
|
"coinsScatteredEverywhere": "小判が 辺りに 散らばった!",
|
||||||
"attackedByItem": "{{pokemonName}}に\n{{itemName}}が おそいかかる!",
|
"attackedByItem": "{{pokemonName}}に\n{{itemName}}が おそいかかる!",
|
||||||
"whippedUpAWhirlwind": "{{pokemonName}}の まわりで\nくうきが うずをまく!",
|
"whippedUpAWhirlwind": "{{pokemonName}}の 周りで\n空気が 渦を巻く!",
|
||||||
"flewUpHigh": "{{pokemonName}}は\nそらたかく とびあがった!",
|
"flewUpHigh": "{{pokemonName}}は\n空高く 飛び上がった!",
|
||||||
"tookInSunlight": "{{pokemonName}}は\nひかりを きゅうしゅうした!",
|
"tookInSunlight": "{{pokemonName}}は\n光を 吸収した!",
|
||||||
"dugAHole": "{{pokemonName}}は\nじめんに もぐった!",
|
"dugAHole": "{{pokemonName}}は\n地面に 潜った!",
|
||||||
"loweredItsHead": "{{pokemonName}}は\nくびを ひっこめた!",
|
"loweredItsHead": "{{pokemonName}}は\n首を 引っ込めた!",
|
||||||
"isGlowing": "{{pokemonName}}を\nはげしいひかりが つつむ!",
|
"isGlowing": "{{pokemonName}}を\n激しい光が 包む!",
|
||||||
"bellChimed": "すずのおとが ひびきわたった!",
|
"bellChimed": "鈴の音が 響き渡った!",
|
||||||
"foresawAnAttack": "{{pokemonName}}は\nみらいに こうげきを よちした!",
|
"foresawAnAttack": "{{pokemonName}}は\n未来に 攻撃を 予知した!",
|
||||||
"hidUnderwater": "{{pokemonName}}は\nすいちゅうに みをひそめた!",
|
"isTighteningFocus": "{{pokemonName}}は\n集中力を 高めている!",
|
||||||
"soothingAromaWaftedThroughArea": "ここちよい かおりが ひろがった!",
|
"hidUnderwater": "{{pokemonName}}は\n水中に 身を潜めた!",
|
||||||
"sprangUp": "{{pokemonName}}は\nたかく とびはねた!",
|
"soothingAromaWaftedThroughArea": "心地よい 香りが 広がった!",
|
||||||
"choseDoomDesireAsDestiny": "{{pokemonName}}は\nはめつのねがいを みらいに たくした!",
|
"sprangUp": "{{pokemonName}}は\n高く 飛び跳ねた!",
|
||||||
"vanishedInstantly": "{{pokemonName}}の すがたが\nいっしゅんにして きえた!",
|
"choseDoomDesireAsDestiny": "{{pokemonName}}は\nはめつのねがいを 未来に 託した!",
|
||||||
"tookTargetIntoSky": "{{pokemonName}}は {{targetName}}を\nじょうくうに つれさった!",
|
"vanishedInstantly": "{{pokemonName}}の 姿が\n一瞬にして 消えた!",
|
||||||
"becameCloakedInFreezingLight": "{{pokemonName}}は\nつめたいひかりに つつまれた!",
|
"tookTargetIntoSky": "{{pokemonName}}は {{targetName}}を\n上空に 連れ去った!",
|
||||||
"becameCloakedInFreezingAir": "{{pokemonName}}は\nこごえるくうきに つつまれた!",
|
"becameCloakedInFreezingLight": "{{pokemonName}}は\n冷たい光に 包まれた!",
|
||||||
"isChargingPower": "{{pokemonName}}は\nパワーを ためこんでいる!",
|
"becameCloakedInFreezingAir": "{{pokemonName}}は\n凍える空気に 包まれた!",
|
||||||
"burnedItselfOut": "{{pokemonName}}の ほのうは\nもえつきた!",
|
"isChargingPower": "{{pokemonName}}は\nパワーを 溜め込んでいる!",
|
||||||
"startedHeatingUpBeak": "{{pokemonName}}は\nクチバシを かねつしはじめた!",
|
"burnedItselfOut": "{{pokemonName}}の 炎は 燃え尽きた!",
|
||||||
|
"startedHeatingUpBeak": "{{pokemonName}}は\nクチバシを 加熱し始めた!",
|
||||||
"setUpShellTrap": "{{pokemonName}}は\nトラップシェルを 仕掛けた!",
|
"setUpShellTrap": "{{pokemonName}}は\nトラップシェルを 仕掛けた!",
|
||||||
"isOverflowingWithSpacePower": "{{pokemonName}}に\nうちゅうの ちからが あふれだす!",
|
"isOverflowingWithSpacePower": "{{pokemonName}}に\n宇宙の 力が 溢れ出す!",
|
||||||
"usedUpAllElectricity": "{{pokemonName}}は\nでんきを つかいきった!",
|
"usedUpAllElectricity": "{{pokemonName}}は\n電気を 使い切った!",
|
||||||
"stoleItem": "{{pokemonName}}は\n{{targetName}}の {{itemName}}を ぬすんだ!",
|
"stoleItem": "{{pokemonName}}は\n{{targetName}}の {{itemName}}を 盗んだ!",
|
||||||
"incineratedItem": "{{pokemonName}}は\n{{targetName}}の {{itemName}}を もやした!",
|
"incineratedItem": "{{pokemonName}}は\n{{targetName}}の {{itemName}}を 燃やした!",
|
||||||
"knockedOffItem": "{{pokemonName}}は\n{{targetName}}の {{itemName}}を はたきおとした!",
|
"knockedOffItem": "{{pokemonName}}は\n{{targetName}}の {{itemName}}を はたき落とした!",
|
||||||
"tookMoveAttack": "{{pokemonName}}は\n{{moveName}}の こうげきを うけた!",
|
"tookMoveAttack": "{{pokemonName}}は\n{{moveName}}の 攻撃を 受けた!",
|
||||||
"cutOwnHpAndMaximizedStat": "{{pokemonName}}は\nたいりょくを けずって {{statName}}ぜんかい!",
|
"cutOwnHpAndMaximizedStat": "{{pokemonName}}は\n体力を 削って {{statName}}全開!",
|
||||||
"copiedStatChanges": "{{pokemonName}}は {{targetName}}の\nのうりょくへんかを コピーした!",
|
"copiedStatChanges": "{{pokemonName}}は {{targetName}}の\n能力変化を コピーした!",
|
||||||
"magnitudeMessage": "マグニチュード{{magnitude}}!",
|
"magnitudeMessage": "マグニチュード{{magnitude}}!",
|
||||||
"tookAimAtTarget": "{{pokemonName}}は {{targetName}}に\nねらいを さだめた!",
|
"tookAimAtTarget": "{{pokemonName}}は {{targetName}}に\n狙いを 定めた!",
|
||||||
"transformedIntoType": "{{pokemonName}}は\n{{typeName}}タイプに なった!",
|
"transformedIntoType": "{{pokemonName}}は\n{{typeName}}タイプに なった!",
|
||||||
"copiedMove": "{{pokemonName}}は\n{{moveName}}を コピーした!",
|
"copiedMove": "{{pokemonName}}は\n{{moveName}}を コピーした!",
|
||||||
"sketchedMove": "{{pokemonName}}は\n{{moveName}}を スケッチした!",
|
"sketchedMove": "{{pokemonName}}は\n{{moveName}}を スケッチした!",
|
||||||
"acquiredAbility": "{{pokemonName}}の とくせいが\n{{abilityName}}に なった!",
|
"acquiredAbility": "{{pokemonName}}の 特性が\n{{abilityName}}に なった!",
|
||||||
"copiedTargetAbility": "{{pokemonName}}は\n{{targetName}}の {{abilityName}}を コピーした!",
|
"copiedTargetAbility": "{{pokemonName}}は\n{{targetName}}の {{abilityName}}を コピーした!",
|
||||||
"transformedIntoTarget": "{{pokemonName}}は\n{{targetName}}に へんしんした!",
|
"transformedIntoTarget": "{{pokemonName}}は\n{{targetName}}に 変身した!",
|
||||||
"tryingToTakeFoeDown": "{{pokemonName}}は あいてを\nみちづれに しようとしている!",
|
"tryingToTakeFoeDown": "{{pokemonName}}は 相手を\nみちづれに しようとしている!",
|
||||||
"addType": "{{pokemonName}}に\n{{typeName}}タイプが ついかされた!",
|
"addType": "{{pokemonName}}に\n{{typeName}}タイプが 追加された!",
|
||||||
"cannotUseMove": "{{pokemonName}}は\n{{moveName}}を つかえなかった!",
|
"cannotUseMove": "{{pokemonName}}は\n{{moveName}}を 使えなかった!",
|
||||||
"healHp": "{{pokemonName}}の\nたいりょくが かいふくした!",
|
"healHp": "{{pokemonName}}の\n体力が 回復した!",
|
||||||
"sacrificialFullRestore": "{{pokemonName}}の\nねがいごとが かなった!",
|
"sacrificialFullRestore": "{{pokemonName}}の\nいやしのねがいが 叶った!",
|
||||||
"invertStats": "{{pokemonName}}の\nのうりょくへんかが ぎゃくてんした!",
|
"invertStats": "{{pokemonName}}は\n能力変化が ひっくり返った!",
|
||||||
"resetStats": "{{pokemonName}}の\nのうりょくへんかが もとにもどった!",
|
"resetStats": "{{pokemonName}}の\n能力変化が 元に戻った!",
|
||||||
"faintCountdown": "{{pokemonName}}は\n{{turnCount}}ターンごに ほろびてしまう!",
|
"statEliminated": "全ての 能力変化が 元に戻った!",
|
||||||
|
"faintCountdown": "{{pokemonName}}は\n{{turnCount}}ターン後に 滅びてしまう!",
|
||||||
"copyType": "{{pokemonName}}は {{targetPokemonName}}と\n同じタイプに なった!",
|
"copyType": "{{pokemonName}}は {{targetPokemonName}}と\n同じタイプに なった!",
|
||||||
"suppressAbilities": "{{pokemonName}}の とくせいが きかなくなった!",
|
"suppressAbilities": "{{pokemonName}}の 特性が 効かなくなった!",
|
||||||
"revivalBlessing": "{{pokemonName}}は\n復活して 戦えるようになった!",
|
"revivalBlessing": "{{pokemonName}}は\n復活して 戦えるようになった!",
|
||||||
"swapArenaTags": "{{pokemonName}}は\nおたがいの ばのこうかを いれかえた!"
|
"swapArenaTags": "{{pokemonName}}は\nお互いの 場の 効果を 入れ替えた!",
|
||||||
|
"exposedMove": "{{pokemonName}}は {{targetPokemonName}}の\n正体を 見破った!"
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"SEND_OUT": "いれかえる",
|
"SEND_OUT": "入れ替える",
|
||||||
"SUMMARY": "つよさをみる",
|
"SUMMARY": "強さを見る",
|
||||||
"CANCEL": "やめる",
|
"CANCEL": "やめる",
|
||||||
"RELEASE": "にがす",
|
"RELEASE": "逃がす",
|
||||||
"APPLY": "つかう",
|
"APPLY": "使う",
|
||||||
"TEACH": "おしえる"
|
"TEACH": "教える"
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"moveset": "わざ",
|
"moveset": "技",
|
||||||
"gender": "せいべつ:",
|
"gender": "性別:",
|
||||||
"ability": "とくせい:",
|
"ability": "特性:",
|
||||||
"nature": "せいかく:",
|
"nature": "性格:",
|
||||||
"form": "すがた:"
|
"form": "姿:"
|
||||||
}
|
}
|
@ -1 +1,44 @@
|
|||||||
{}
|
{
|
||||||
|
"pokemonInfo": "ポケモン情報",
|
||||||
|
"status": "ステータス",
|
||||||
|
"powerAccuracyCategory": "威力\n命中\n分類",
|
||||||
|
"type": "タイプ",
|
||||||
|
"unknownTrainer": "???",
|
||||||
|
"ot": "親",
|
||||||
|
"nature": "性格",
|
||||||
|
"expPoints": "経験値",
|
||||||
|
"nextLv": "次のレベルまで",
|
||||||
|
"cancel": "キャンセル",
|
||||||
|
"memoString": "{{natureFragment}}な性格。\n{{metFragment}}",
|
||||||
|
"metFragment": {
|
||||||
|
"normal": "{{biome}}で\nLv.{{level}}の時に出会った。",
|
||||||
|
"apparently": "{{biome}}で\nLv.{{level}}の時に出会ったようだ。"
|
||||||
|
},
|
||||||
|
"natureFragment": {
|
||||||
|
"Hardy": "{{nature}}",
|
||||||
|
"Lonely": "{{nature}}",
|
||||||
|
"Brave": "{{nature}}",
|
||||||
|
"Adamant": "{{nature}}",
|
||||||
|
"Naughty": "{{nature}}",
|
||||||
|
"Bold": "{{nature}}",
|
||||||
|
"Docile": "{{nature}}",
|
||||||
|
"Relaxed": "{{nature}}",
|
||||||
|
"Impish": "{{nature}}",
|
||||||
|
"Lax": "{{nature}}",
|
||||||
|
"Timid": "{{nature}}",
|
||||||
|
"Hasty": "{{nature}}",
|
||||||
|
"Serious": "{{nature}}",
|
||||||
|
"Jolly": "{{nature}}",
|
||||||
|
"Naive": "{{nature}}",
|
||||||
|
"Modest": "{{nature}}",
|
||||||
|
"Mild": "{{nature}}",
|
||||||
|
"Quiet": "{{nature}}",
|
||||||
|
"Bashful": "{{nature}}",
|
||||||
|
"Rash": "{{nature}}",
|
||||||
|
"Calm": "{{nature}}",
|
||||||
|
"Gentle": "{{nature}}",
|
||||||
|
"Sassy": "{{nature}}",
|
||||||
|
"Careful": "{{nature}}",
|
||||||
|
"Quirky": "{{nature}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,10 +28,10 @@
|
|||||||
"SPDshortened": "速さ",
|
"SPDshortened": "速さ",
|
||||||
"runInfo": "ラン情報",
|
"runInfo": "ラン情報",
|
||||||
"money": "お金",
|
"money": "お金",
|
||||||
"runLength": "ラン最高ウェーブ",
|
"runLength": "時間",
|
||||||
"viewHeldItems": "手持ちアイテム",
|
"viewHeldItems": "持たせたアイテム",
|
||||||
"hallofFameText": "殿堂へようこそ!",
|
"hallofFameText": "殿堂入り おめでとう!",
|
||||||
"hallofFameText_female": "殿堂へようこそ!",
|
"hallofFameText_female": "殿堂入り おめでとう!",
|
||||||
"viewHallOfFame": "殿堂登録を見る!",
|
"viewHallOfFame": "殿堂登録を見る!",
|
||||||
"viewEndingSplash": "クリア後のアートを見る!"
|
"viewEndingSplash": "クリア後のアートを見る!"
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"overwriteData": "選択した スロットに データを 上書きします?",
|
"overwriteData": "選択した スロットに データを 上書きします?",
|
||||||
"loading": "読込中…",
|
"loading": "読込中…",
|
||||||
"wave": "波",
|
"wave": "ラウンド",
|
||||||
"lv": "Lv",
|
"lv": "Lv",
|
||||||
"empty": "なし"
|
"empty": "なし"
|
||||||
}
|
}
|
@ -1 +1,36 @@
|
|||||||
{}
|
{
|
||||||
|
"battlesWon": "Battles Won!",
|
||||||
|
"joinTheDiscord": "Join the Discord!",
|
||||||
|
"infiniteLevels": "Infinite Levels!",
|
||||||
|
"everythingStacks": "Everything Stacks!",
|
||||||
|
"optionalSaveScumming": "Optional Save Scumming!",
|
||||||
|
"biomes": "35 Biomes!",
|
||||||
|
"openSource": "Open Source!",
|
||||||
|
"playWithSpeed": "Play with 5x Speed!",
|
||||||
|
"liveBugTesting": "Live Bug Testing!",
|
||||||
|
"heavyInfluence": "Heavy RoR2 Influence!",
|
||||||
|
"pokemonRiskAndPokemonRain": "Pokémon Risk and Pokémon Rain!",
|
||||||
|
"nowWithMoreSalt": "Now with 33% More Salt!",
|
||||||
|
"infiniteFusionAtHome": "Infinite Fusion at Home!",
|
||||||
|
"brokenEggMoves": "Broken Egg Moves!",
|
||||||
|
"magnificent": "Magnificent!",
|
||||||
|
"mubstitute": "Mubstitute!",
|
||||||
|
"thatsCrazy": "That's Crazy!",
|
||||||
|
"oranceJuice": "Orance Juice!",
|
||||||
|
"questionableBalancing": "Questionable Balancing!",
|
||||||
|
"coolShaders": "Cool Shaders!",
|
||||||
|
"aiFree": "AI-Free!",
|
||||||
|
"suddenDifficultySpikes": "Sudden Difficulty Spikes!",
|
||||||
|
"basedOnAnUnfinishedFlashGame": "Based on an Unfinished Flash Game!",
|
||||||
|
"moreAddictiveThanIntended": "More Addictive than Intended!",
|
||||||
|
"mostlyConsistentSeeds": "Mostly Consistent Seeds!",
|
||||||
|
"achievementPointsDontDoAnything": "Achievement Points Don't Do Anything!",
|
||||||
|
"youDoNotStartAtLevel": "You Do Not Start at Level 2000!",
|
||||||
|
"dontTalkAboutTheManaphyEggIncident": "Don't Talk About the Manaphy Egg Incident!",
|
||||||
|
"alsoTryPokengine": "Also Try Pokéngine!",
|
||||||
|
"alsoTryEmeraldRogue": "Also Try Emerald Rogue!",
|
||||||
|
"alsoTryRadicalRed": "Also Try Radical Red!",
|
||||||
|
"eeveeExpo": "Eevee Expo!",
|
||||||
|
"ynoproject": "YNOproject!",
|
||||||
|
"breedersInSpace": "Breeders in space!"
|
||||||
|
}
|
||||||
|
@ -16,17 +16,17 @@
|
|||||||
"snowStartMessage": "雪が 降り始めた!",
|
"snowStartMessage": "雪が 降り始めた!",
|
||||||
"snowLapseMessage": "雪が 降っている!",
|
"snowLapseMessage": "雪が 降っている!",
|
||||||
"snowClearMessage": "雪が 止んだ!",
|
"snowClearMessage": "雪が 止んだ!",
|
||||||
"fogStartMessage": "足下に 霧(きり)が立ち込めた!",
|
"fogStartMessage": "足下に 霧が 立ち込めた!",
|
||||||
"fogLapseMessage": "足下に 霧(きり)が 立ち込めている!",
|
"fogLapseMessage": "足下に 霧が 立ち込めている!",
|
||||||
"fogClearMessage": "足下の 霧(きり)が消え去った!",
|
"fogClearMessage": "足下の 霧が 消え去った!",
|
||||||
"heavyRainStartMessage": "強い雨が 降り始めた!",
|
"heavyRainStartMessage": "強い雨が 降り始めた!",
|
||||||
"heavyRainLapseMessage": "強い雨が 降っている!",
|
"heavyRainLapseMessage": "強い雨が 降っている!",
|
||||||
"heavyRainClearMessage": "強い雨が あがった!",
|
"heavyRainClearMessage": "強い雨が あがった!",
|
||||||
"harshSunStartMessage": "日差しが とても強くなった!",
|
"harshSunStartMessage": "日差しが とても強くなった!",
|
||||||
"harshSunLapseMessage": "日差しが とても強い!",
|
"harshSunLapseMessage": "日差しが とても強い!",
|
||||||
"harshSunClearMessage": "日差しが 元に戻った!",
|
"harshSunClearMessage": "日差しが 元に戻った!",
|
||||||
"strongWindsStartMessage": "謎(なぞ)の 乱気流(らんきりゅう)が\nひこうポケモンを 護(まも)る!",
|
"strongWindsStartMessage": "謎の 乱気流が\nひこうポケモンを 護る!",
|
||||||
"strongWindsLapseMessage": "謎(なぞ)の 乱気流(らんきりゅう)の 勢(いきお)いは 止まらない!",
|
"strongWindsLapseMessage": "謎の 乱気流の 勢いは 止まらない!",
|
||||||
"strongWindsEffectMessage": "謎(なぞ)の 乱気流(らんきりゅう)が 攻撃(こうげき)を 弱(よわ)めた!",
|
"strongWindsEffectMessage": "謎の 乱気流が 攻撃を 弱めた!",
|
||||||
"strongWindsClearMessage": "謎(なぞ)の 乱気流(らんきりゅう)が おさまった!"
|
"strongWindsClearMessage": "謎の 乱気流が おさまった!"
|
||||||
}
|
}
|
@ -36,5 +36,6 @@
|
|||||||
"matBlock": "마룻바닥세워막기",
|
"matBlock": "마룻바닥세워막기",
|
||||||
"craftyShield": "트릭가드",
|
"craftyShield": "트릭가드",
|
||||||
"tailwind": "순풍",
|
"tailwind": "순풍",
|
||||||
"happyHour": "해피타임"
|
"happyHour": "해피타임",
|
||||||
|
"safeguard": "신비의부적"
|
||||||
}
|
}
|
@ -36,5 +36,6 @@
|
|||||||
"matBlock": "Mat Block",
|
"matBlock": "Mat Block",
|
||||||
"craftyShield": "Crafty Shield",
|
"craftyShield": "Crafty Shield",
|
||||||
"tailwind": "Tailwind",
|
"tailwind": "Tailwind",
|
||||||
"happyHour": "Happy Hour"
|
"happyHour": "Happy Hour",
|
||||||
|
"safeguard": "Safeguard"
|
||||||
}
|
}
|
||||||
|
@ -36,5 +36,6 @@
|
|||||||
"matBlock": "掀榻榻米",
|
"matBlock": "掀榻榻米",
|
||||||
"craftyShield": "戏法防守",
|
"craftyShield": "戏法防守",
|
||||||
"tailwind": "顺风",
|
"tailwind": "顺风",
|
||||||
"happyHour": "快乐时光"
|
"happyHour": "快乐时光",
|
||||||
|
"safeguard": "神秘守护"
|
||||||
}
|
}
|
@ -433,37 +433,44 @@ export class RememberMoveModifierType extends PokemonModifierType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DoubleBattleChanceBoosterModifierType extends ModifierType {
|
export class DoubleBattleChanceBoosterModifierType extends ModifierType {
|
||||||
public battleCount: integer;
|
private maxBattles: number;
|
||||||
|
|
||||||
constructor(localeKey: string, iconImage: string, battleCount: integer) {
|
constructor(localeKey: string, iconImage: string, maxBattles: number) {
|
||||||
super(localeKey, iconImage, (_type, _args) => new Modifiers.DoubleBattleChanceBoosterModifier(this, this.battleCount), "lure");
|
super(localeKey, iconImage, (_type, _args) => new Modifiers.DoubleBattleChanceBoosterModifier(this, maxBattles), "lure");
|
||||||
|
|
||||||
this.battleCount = battleCount;
|
this.maxBattles = maxBattles;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescription(scene: BattleScene): string {
|
getDescription(_scene: BattleScene): string {
|
||||||
return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { battleCount: this.battleCount });
|
return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", {
|
||||||
|
battleCount: this.maxBattles
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TempStatStageBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType {
|
export class TempStatStageBoosterModifierType extends ModifierType implements GeneratedPersistentModifierType {
|
||||||
private stat: TempBattleStat;
|
private stat: TempBattleStat;
|
||||||
private key: string;
|
private nameKey: string;
|
||||||
|
private quantityKey: string;
|
||||||
|
|
||||||
constructor(stat: TempBattleStat) {
|
constructor(stat: TempBattleStat) {
|
||||||
const key = TempStatStageBoosterModifierTypeGenerator.items[stat];
|
const nameKey = TempStatStageBoosterModifierTypeGenerator.items[stat];
|
||||||
super("", key, (_type, _args) => new Modifiers.TempStatStageBoosterModifier(this, this.stat));
|
super("", nameKey, (_type, _args) => new Modifiers.TempStatStageBoosterModifier(this, this.stat, 5));
|
||||||
|
|
||||||
this.stat = stat;
|
this.stat = stat;
|
||||||
this.key = key;
|
this.nameKey = nameKey;
|
||||||
|
this.quantityKey = (stat !== Stat.ACC) ? "percentage" : "stage";
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return i18next.t(`modifierType:TempStatStageBoosterItem.${this.key}`);
|
return i18next.t(`modifierType:TempStatStageBoosterItem.${this.nameKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescription(_scene: BattleScene): string {
|
getDescription(_scene: BattleScene): string {
|
||||||
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)) });
|
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", {
|
||||||
|
stat: i18next.t(getStatKey(this.stat)),
|
||||||
|
amount: i18next.t(`modifierType:ModifierType.TempStatStageBoosterModifierType.extra.${this.quantityKey}`)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getPregenArgs(): any[] {
|
getPregenArgs(): any[] {
|
||||||
@ -1348,9 +1355,9 @@ export const modifierTypes = {
|
|||||||
SUPER_REPEL: () => new DoubleBattleChanceBoosterModifierType('Super Repel', 10),
|
SUPER_REPEL: () => new DoubleBattleChanceBoosterModifierType('Super Repel', 10),
|
||||||
MAX_REPEL: () => new DoubleBattleChanceBoosterModifierType('Max Repel', 25),*/
|
MAX_REPEL: () => new DoubleBattleChanceBoosterModifierType('Max Repel', 25),*/
|
||||||
|
|
||||||
LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.LURE", "lure", 5),
|
LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.LURE", "lure", 10),
|
||||||
SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 10),
|
SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 15),
|
||||||
MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 25),
|
MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 30),
|
||||||
|
|
||||||
SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(),
|
SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(),
|
||||||
|
|
||||||
@ -1358,9 +1365,12 @@ export const modifierTypes = {
|
|||||||
|
|
||||||
DIRE_HIT: () => new class extends ModifierType {
|
DIRE_HIT: () => new class extends ModifierType {
|
||||||
getDescription(_scene: BattleScene): string {
|
getDescription(_scene: BattleScene): string {
|
||||||
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises") });
|
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", {
|
||||||
|
stat: i18next.t("modifierType:ModifierType.DIRE_HIT.extra.raises"),
|
||||||
|
amount: i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.extra.stage")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new Modifiers.TempCritBoosterModifier(type)),
|
}("modifierType:ModifierType.DIRE_HIT", "dire_hit", (type, _args) => new Modifiers.TempCritBoosterModifier(type, 5)),
|
||||||
|
|
||||||
BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(),
|
BASE_STAT_BOOSTER: () => new BaseStatBoosterModifierTypeGenerator(),
|
||||||
|
|
||||||
|
@ -292,70 +292,131 @@ export class AddVoucherModifier extends ConsumableModifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifier used for party-wide or passive items that start an initial
|
||||||
|
* {@linkcode battleCount} equal to {@linkcode maxBattles} that, for every
|
||||||
|
* battle, decrements. Typically, when {@linkcode battleCount} reaches 0, the
|
||||||
|
* modifier will be removed. If a modifier of the same type is to be added, it
|
||||||
|
* will reset {@linkcode battleCount} back to {@linkcode maxBattles} of the
|
||||||
|
* existing modifier instead of adding that modifier directly.
|
||||||
|
* @extends PersistentModifier
|
||||||
|
* @abstract
|
||||||
|
* @see {@linkcode add}
|
||||||
|
*/
|
||||||
export abstract class LapsingPersistentModifier extends PersistentModifier {
|
export abstract class LapsingPersistentModifier extends PersistentModifier {
|
||||||
protected battlesLeft: integer;
|
/** The maximum amount of battles the modifier will exist for */
|
||||||
|
private maxBattles: number;
|
||||||
|
/** The current amount of battles the modifier will exist for */
|
||||||
|
private battleCount: number;
|
||||||
|
|
||||||
constructor(type: ModifierTypes.ModifierType, battlesLeft?: integer, stackCount?: integer) {
|
constructor(type: ModifierTypes.ModifierType, maxBattles: number, battleCount?: number, stackCount?: integer) {
|
||||||
super(type, stackCount);
|
super(type, stackCount);
|
||||||
|
|
||||||
this.battlesLeft = battlesLeft!; // TODO: is this bang correct?
|
this.maxBattles = maxBattles;
|
||||||
|
this.battleCount = battleCount ?? this.maxBattles;
|
||||||
}
|
}
|
||||||
|
|
||||||
lapse(args: any[]): boolean {
|
/**
|
||||||
return !!--this.battlesLeft;
|
* Goes through existing modifiers for any that match the selected modifier,
|
||||||
|
* which will then either add it to the existing modifiers if none were found
|
||||||
|
* or, if one was found, it will refresh {@linkcode battleCount}.
|
||||||
|
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
|
||||||
|
* @param _virtual N/A
|
||||||
|
* @param _scene N/A
|
||||||
|
* @returns true if the modifier was successfully added or applied, false otherwise
|
||||||
|
*/
|
||||||
|
add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean {
|
||||||
|
for (const modifier of modifiers) {
|
||||||
|
if (this.match(modifier)) {
|
||||||
|
const modifierInstance = modifier as LapsingPersistentModifier;
|
||||||
|
if (modifierInstance.getBattleCount() < modifierInstance.getMaxBattles()) {
|
||||||
|
modifierInstance.resetBattleCount();
|
||||||
|
scene.playSound("se/restore");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// should never get here
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiers.push(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lapse(_args: any[]): boolean {
|
||||||
|
this.battleCount--;
|
||||||
|
return this.battleCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getIcon(scene: BattleScene): Phaser.GameObjects.Container {
|
getIcon(scene: BattleScene): Phaser.GameObjects.Container {
|
||||||
const container = super.getIcon(scene);
|
const container = super.getIcon(scene);
|
||||||
|
|
||||||
const battleCountText = addTextObject(scene, 27, 0, this.battlesLeft.toString(), TextStyle.PARTY, { fontSize: "66px", color: "#f89890" });
|
// Linear interpolation on hue
|
||||||
|
const hue = Math.floor(120 * (this.battleCount / this.maxBattles) + 5);
|
||||||
|
|
||||||
|
// Generates the color hex code with a constant saturation and lightness but varying hue
|
||||||
|
const typeHex = Utils.hslToHex(hue, 0.50, 0.90);
|
||||||
|
const strokeHex = Utils.hslToHex(hue, 0.70, 0.30);
|
||||||
|
|
||||||
|
const battleCountText = addTextObject(scene, 27, 0, this.battleCount.toString(), TextStyle.PARTY, { fontSize: "66px", color: typeHex });
|
||||||
battleCountText.setShadow(0, 0);
|
battleCountText.setShadow(0, 0);
|
||||||
battleCountText.setStroke("#984038", 16);
|
battleCountText.setStroke(strokeHex, 16);
|
||||||
battleCountText.setOrigin(1, 0);
|
battleCountText.setOrigin(1, 0);
|
||||||
container.add(battleCountText);
|
container.add(battleCountText);
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBattlesLeft(): integer {
|
getBattleCount(): number {
|
||||||
return this.battlesLeft;
|
return this.battleCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number {
|
resetBattleCount(): void {
|
||||||
return 99;
|
this.battleCount = this.maxBattles;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
|
|
||||||
constructor(type: ModifierTypes.DoubleBattleChanceBoosterModifierType, battlesLeft: integer, stackCount?: integer) {
|
|
||||||
super(type, battlesLeft, stackCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match(modifier: Modifier): boolean {
|
getMaxBattles(): number {
|
||||||
if (modifier instanceof DoubleBattleChanceBoosterModifier) {
|
return this.maxBattles;
|
||||||
// Check type id to not match different tiers of lures
|
|
||||||
return modifier.type.id === this.type.id && modifier.battlesLeft === this.battlesLeft;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
clone(): DoubleBattleChanceBoosterModifier {
|
|
||||||
return new DoubleBattleChanceBoosterModifier(this.type as ModifierTypes.DoubleBattleChanceBoosterModifierType, this.battlesLeft, this.stackCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getArgs(): any[] {
|
getArgs(): any[] {
|
||||||
return [ this.battlesLeft ];
|
return [ this.maxBattles, this.battleCount ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifier used for passive items, specifically lures, that
|
||||||
|
* temporarily increases the chance of a double battle.
|
||||||
|
* @extends LapsingPersistentModifier
|
||||||
|
* @see {@linkcode apply}
|
||||||
|
*/
|
||||||
|
export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier {
|
||||||
|
constructor(type: ModifierType, maxBattles:number, battleCount?: number, stackCount?: integer) {
|
||||||
|
super(type, maxBattles, battleCount, stackCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
match(modifier: Modifier): boolean {
|
||||||
|
return (modifier instanceof DoubleBattleChanceBoosterModifier) && (modifier.getMaxBattles() === this.getMaxBattles());
|
||||||
|
}
|
||||||
|
|
||||||
|
clone(): DoubleBattleChanceBoosterModifier {
|
||||||
|
return new DoubleBattleChanceBoosterModifier(this.type as ModifierTypes.DoubleBattleChanceBoosterModifierType, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies the chance of a double battle occurring
|
* Modifies the chance of a double battle occurring
|
||||||
* @param args A single element array containing the double battle chance as a NumberHolder
|
* @param args [0] {@linkcode Utils.NumberHolder} for double battle chance
|
||||||
* @returns {boolean} Returns true if the modifier was applied
|
* @returns true if the modifier was applied
|
||||||
*/
|
*/
|
||||||
apply(args: any[]): boolean {
|
apply(args: any[]): boolean {
|
||||||
const doubleBattleChance = args[0] as Utils.NumberHolder;
|
const doubleBattleChance = args[0] as Utils.NumberHolder;
|
||||||
// This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt
|
// This is divided because the chance is generated as a number from 0 to doubleBattleChance.value using Utils.randSeedInt
|
||||||
// A double battle will initiate if the generated number is 0
|
// A double battle will initiate if the generated number is 0
|
||||||
doubleBattleChance.value = Math.ceil(doubleBattleChance.value / 2);
|
doubleBattleChance.value = Math.ceil(doubleBattleChance.value / 4);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -369,16 +430,18 @@ export class DoubleBattleChanceBoosterModifier extends LapsingPersistentModifier
|
|||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
*/
|
*/
|
||||||
export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
|
export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
|
||||||
|
/** The stat whose stat stage multiplier will be temporarily increased */
|
||||||
private stat: TempBattleStat;
|
private stat: TempBattleStat;
|
||||||
private multiplierBoost: number;
|
/** The amount by which the stat stage itself or its multiplier will be increased by */
|
||||||
|
private boost: number;
|
||||||
|
|
||||||
constructor(type: ModifierType, stat: TempBattleStat, battlesLeft?: number, stackCount?: number) {
|
constructor(type: ModifierType, stat: TempBattleStat, maxBattles: number, battleCount?: number, stackCount?: number) {
|
||||||
super(type, battlesLeft ?? 5, stackCount);
|
super(type, maxBattles, battleCount, stackCount);
|
||||||
|
|
||||||
this.stat = stat;
|
this.stat = stat;
|
||||||
// Note that, because we want X Accuracy to maintain its original behavior,
|
// Note that, because we want X Accuracy to maintain its original behavior,
|
||||||
// it will increment as it did previously, directly to the stat stage.
|
// it will increment as it did previously, directly to the stat stage.
|
||||||
this.multiplierBoost = stat !== Stat.ACC ? 0.3 : 1;
|
this.boost = (stat !== Stat.ACC) ? 0.3 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
match(modifier: Modifier): boolean {
|
match(modifier: Modifier): boolean {
|
||||||
@ -390,11 +453,11 @@ export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
return new TempStatStageBoosterModifier(this.type, this.stat, this.battlesLeft, this.stackCount);
|
return new TempStatStageBoosterModifier(this.type, this.stat, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
getArgs(): any[] {
|
getArgs(): any[] {
|
||||||
return [ this.stat, this.battlesLeft ];
|
return [ this.stat, ...super.getArgs() ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -409,44 +472,14 @@ export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases the incoming stat stage matching {@linkcode stat} by {@linkcode multiplierBoost}.
|
* Increases the incoming stat stage matching {@linkcode stat} by {@linkcode boost}.
|
||||||
* @param args [0] {@linkcode TempBattleStat} N/A
|
* @param args [0] {@linkcode TempBattleStat} N/A
|
||||||
* [1] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat stage multiplier
|
* [1] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat stage multiplier
|
||||||
*/
|
*/
|
||||||
apply(args: any[]): boolean {
|
apply(args: any[]): boolean {
|
||||||
(args[1] as Utils.NumberHolder).value += this.multiplierBoost;
|
(args[1] as Utils.NumberHolder).value += this.boost;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Goes through existing modifiers for any that match the selected modifier,
|
|
||||||
* which will then either add it to the existing modifiers if none were found
|
|
||||||
* or, if one was found, it will refresh {@linkcode battlesLeft}.
|
|
||||||
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
|
|
||||||
* @param _virtual N/A
|
|
||||||
* @param _scene N/A
|
|
||||||
* @returns true if the modifier was successfully added or applied, false otherwise
|
|
||||||
*/
|
|
||||||
add(modifiers: PersistentModifier[], _virtual: boolean, _scene: BattleScene): boolean {
|
|
||||||
for (const modifier of modifiers) {
|
|
||||||
if (this.match(modifier)) {
|
|
||||||
const modifierInstance = modifier as TempStatStageBoosterModifier;
|
|
||||||
if (modifierInstance.getBattlesLeft() < 5) {
|
|
||||||
modifierInstance.battlesLeft = 5;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// should never get here
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiers.push(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -456,12 +489,12 @@ export class TempStatStageBoosterModifier extends LapsingPersistentModifier {
|
|||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
*/
|
*/
|
||||||
export class TempCritBoosterModifier extends LapsingPersistentModifier {
|
export class TempCritBoosterModifier extends LapsingPersistentModifier {
|
||||||
constructor(type: ModifierType, battlesLeft?: integer, stackCount?: number) {
|
constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) {
|
||||||
super(type, battlesLeft || 5, stackCount);
|
super(type, maxBattles, battleCount, stackCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
return new TempCritBoosterModifier(this.type, this.stackCount);
|
return new TempCritBoosterModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
match(modifier: Modifier): boolean {
|
match(modifier: Modifier): boolean {
|
||||||
@ -486,36 +519,6 @@ export class TempCritBoosterModifier extends LapsingPersistentModifier {
|
|||||||
(args[0] as Utils.NumberHolder).value++;
|
(args[0] as Utils.NumberHolder).value++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Goes through existing modifiers for any that match the selected modifier,
|
|
||||||
* which will then either add it to the existing modifiers if none were found
|
|
||||||
* or, if one was found, it will refresh {@linkcode battlesLeft}.
|
|
||||||
* @param modifiers {@linkcode PersistentModifier} array of the player's modifiers
|
|
||||||
* @param _virtual N/A
|
|
||||||
* @param _scene N/A
|
|
||||||
* @returns true if the modifier was successfully added or applied, false otherwise
|
|
||||||
*/
|
|
||||||
add(modifiers: PersistentModifier[], _virtual: boolean, _scene: BattleScene): boolean {
|
|
||||||
for (const modifier of modifiers) {
|
|
||||||
if (this.match(modifier)) {
|
|
||||||
const modifierInstance = modifier as TempCritBoosterModifier;
|
|
||||||
if (modifierInstance.getBattlesLeft() < 5) {
|
|
||||||
modifierInstance.battlesLeft = 5;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// should never get here
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiers.push(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
getMaxStackCount(_scene: BattleScene, _forThreshold?: boolean): number {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MapModifier extends PersistentModifier {
|
export class MapModifier extends PersistentModifier {
|
||||||
@ -2488,7 +2491,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string {
|
getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string {
|
||||||
return i18next.t("modifier:turnHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name });
|
return i18next.t("modifier:turnHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.getNameToRender(), typeName: this.type.name });
|
||||||
}
|
}
|
||||||
|
|
||||||
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||||
|
@ -1,31 +1,34 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { applyAbAttrs, RunSuccessAbAttr } from "#app/data/ability.js";
|
import { applyAbAttrs, RunSuccessAbAttr } from "#app/data/ability";
|
||||||
import { Stat } from "#app/enums/stat.js";
|
import { Stat } from "#app/enums/stat";
|
||||||
import { StatusEffect } from "#app/enums/status-effect.js";
|
import { StatusEffect } from "#app/enums/status-effect";
|
||||||
import Pokemon from "#app/field/pokemon.js";
|
import Pokemon, { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import * as Utils from "#app/utils.js";
|
import * as Utils from "#app/utils";
|
||||||
import { BattleEndPhase } from "./battle-end-phase";
|
import { BattleEndPhase } from "./battle-end-phase";
|
||||||
import { NewBattlePhase } from "./new-battle-phase";
|
import { NewBattlePhase } from "./new-battle-phase";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
|
|
||||||
export class AttemptRunPhase extends PokemonPhase {
|
export class AttemptRunPhase extends PokemonPhase {
|
||||||
constructor(scene: BattleScene, fieldIndex: integer) {
|
constructor(scene: BattleScene, fieldIndex: number) {
|
||||||
super(scene, fieldIndex);
|
super(scene, fieldIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
const playerPokemon = this.getPokemon();
|
const playerField = this.scene.getPlayerField();
|
||||||
const enemyField = this.scene.getEnemyField();
|
const enemyField = this.scene.getEnemyField();
|
||||||
|
|
||||||
const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length;
|
const playerPokemon = this.getPokemon();
|
||||||
|
|
||||||
|
const escapeChance = new Utils.NumberHolder(0);
|
||||||
|
|
||||||
|
this.attemptRunAway(playerField, enemyField, escapeChance);
|
||||||
|
|
||||||
const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256);
|
|
||||||
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
|
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
|
||||||
|
|
||||||
if (playerPokemon.randSeedInt(256) < escapeChance.value) {
|
if (Utils.randSeedInt(100) < escapeChance.value) {
|
||||||
this.scene.playSound("se/flee");
|
this.scene.playSound("se/flee");
|
||||||
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||||
|
|
||||||
@ -53,4 +56,48 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: Utils.NumberHolder) {
|
||||||
|
/** Sum of the speed of all enemy pokemon on the field */
|
||||||
|
const enemySpeed = enemyField.reduce((total: number, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0);
|
||||||
|
/** Sum of the speed of all player pokemon on the field */
|
||||||
|
const playerSpeed = playerField.reduce((total: number, playerPokemon: Pokemon) => total + playerPokemon.getStat(Stat.SPD), 0);
|
||||||
|
|
||||||
|
/* The way the escape chance works is by looking at the difference between your speed and the enemy field's average speed as a ratio. The higher this ratio, the higher your chance of success.
|
||||||
|
* However, there is a cap for the ratio of your speed vs enemy speed which beyond that point, you won't gain any advantage. It also looks at how many times you've tried to escape.
|
||||||
|
* Again, the more times you've tried to escape, the higher your odds of escaping. Bosses and non-bosses are calculated differently - bosses are harder to escape from vs non-bosses
|
||||||
|
* Finally, there's a minimum and maximum escape chance as well so that escapes aren't guaranteed, yet they are never 0 either.
|
||||||
|
* The percentage chance to escape from a pokemon for both bosses and non bosses is linear and based on the minimum and maximum chances, and the speed ratio cap.
|
||||||
|
*
|
||||||
|
* At the time of writing, these conditions should be met:
|
||||||
|
* - The minimum escape chance should be 5% for bosses and non bosses
|
||||||
|
* - Bosses should have a maximum escape chance of 25%, whereas non-bosses should be 95%
|
||||||
|
* - The bonus per previous escape attempt should be 2% for bosses and 10% for non-bosses
|
||||||
|
* - The speed ratio cap should be 6x for bosses and 4x for non-bosses
|
||||||
|
* - The "default" escape chance when your speed equals the enemy speed should be 8.33% for bosses and 27.5% for non-bosses
|
||||||
|
*
|
||||||
|
* From the above, we can calculate the below values
|
||||||
|
*/
|
||||||
|
|
||||||
|
let isBoss = false;
|
||||||
|
for (let e = 0; e < enemyField.length; e++) {
|
||||||
|
isBoss = isBoss || enemyField[e].isBoss(); // this line checks if any of the enemy pokemon on the field are bosses; if so, the calculation for escaping is different
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The ratio between the speed of your active pokemon and the speed of the enemy field */
|
||||||
|
const speedRatio = playerSpeed / enemySpeed;
|
||||||
|
/** The max ratio before escape chance stops increasing. Increased if there is a boss on the field */
|
||||||
|
const speedCap = isBoss ? 6 : 4;
|
||||||
|
/** Minimum percent chance to escape */
|
||||||
|
const minChance = 5;
|
||||||
|
/** Maximum percent chance to escape. Decreased if a boss is on the field */
|
||||||
|
const maxChance = isBoss ? 25 : 95;
|
||||||
|
/** How much each escape attempt increases the chance of the next attempt. Decreased if a boss is on the field */
|
||||||
|
const escapeBonus = isBoss ? 2 : 10;
|
||||||
|
/** Slope of the escape chance curve */
|
||||||
|
const escapeSlope = (maxChance - minChance) / speedCap;
|
||||||
|
|
||||||
|
// This will calculate the escape chance given all of the above and clamp it to the range of [`minChance`, `maxChance`]
|
||||||
|
escapeChance.value = Phaser.Math.Clamp(Math.round((escapeSlope * speedRatio) + minChance + (escapeBonus * this.scene.currentBattle.escapeAttempts++)), minChance, maxChance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,8 +107,9 @@ export class CommandPhase extends FieldPhase {
|
|||||||
|
|
||||||
// Decides between a Disabled, Not Implemented, or No PP translation message
|
// Decides between a Disabled, Not Implemented, or No PP translation message
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
playerPokemon.summonData.disabledMove === move.moveId ? "battle:moveDisabled" :
|
playerPokemon.isMoveRestricted(move.moveId)
|
||||||
move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP";
|
? playerPokemon.getRestrictingTag(move.moveId)!.selectionDeniedText(playerPokemon, move.moveId)
|
||||||
|
: move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP";
|
||||||
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
|
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator
|
||||||
|
|
||||||
this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => {
|
this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => {
|
||||||
|
@ -1,23 +1,29 @@
|
|||||||
import BattleScene, { AnySound } from "#app/battle-scene.js";
|
import BattleScene, { AnySound } from "#app/battle-scene";
|
||||||
import { Egg, EGG_SEED } from "#app/data/egg.js";
|
import { Egg } from "#app/data/egg";
|
||||||
import { EggCountChangedEvent } from "#app/events/egg.js";
|
import { EggCountChangedEvent } from "#app/events/egg";
|
||||||
import { PlayerPokemon } from "#app/field/pokemon.js";
|
import { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages.js";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { Phase } from "#app/phase.js";
|
import { Phase } from "#app/phase";
|
||||||
import { achvs } from "#app/system/achv.js";
|
import { achvs } from "#app/system/achv";
|
||||||
import EggCounterContainer from "#app/ui/egg-counter-container.js";
|
import EggCounterContainer from "#app/ui/egg-counter-container";
|
||||||
import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler.js";
|
import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler";
|
||||||
import PokemonInfoContainer from "#app/ui/pokemon-info-container.js";
|
import PokemonInfoContainer from "#app/ui/pokemon-info-container";
|
||||||
import { Mode } from "#app/ui/ui.js";
|
import { Mode } from "#app/ui/ui";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||||
import * as Utils from "#app/utils.js";
|
import * as Utils from "#app/utils";
|
||||||
|
import { EggLapsePhase } from "./egg-lapse-phase";
|
||||||
|
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that represents egg hatching
|
* Class that represents egg hatching
|
||||||
*/
|
*/
|
||||||
export class EggHatchPhase extends Phase {
|
export class EggHatchPhase extends Phase {
|
||||||
/** The egg that is hatching */
|
/** The egg that is hatching */
|
||||||
private egg: Egg;
|
private egg: Egg;
|
||||||
|
/** The new EggHatchData for the egg/pokemon that hatches */
|
||||||
|
private eggHatchData: EggHatchData;
|
||||||
|
|
||||||
/** The number of eggs that are hatching */
|
/** The number of eggs that are hatching */
|
||||||
private eggsToHatchCount: integer;
|
private eggsToHatchCount: integer;
|
||||||
@ -58,10 +64,11 @@ export class EggHatchPhase extends Phase {
|
|||||||
private skipped: boolean;
|
private skipped: boolean;
|
||||||
/** The sound effect being played when the egg is hatched */
|
/** The sound effect being played when the egg is hatched */
|
||||||
private evolutionBgm: AnySound;
|
private evolutionBgm: AnySound;
|
||||||
|
private eggLapsePhase: EggLapsePhase;
|
||||||
|
|
||||||
constructor(scene: BattleScene, egg: Egg, eggsToHatchCount: integer) {
|
constructor(scene: BattleScene, hatchScene: EggLapsePhase, egg: Egg, eggsToHatchCount: integer) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
this.eggLapsePhase = hatchScene;
|
||||||
this.egg = egg;
|
this.egg = egg;
|
||||||
this.eggsToHatchCount = eggsToHatchCount;
|
this.eggsToHatchCount = eggsToHatchCount;
|
||||||
}
|
}
|
||||||
@ -307,6 +314,7 @@ export class EggHatchPhase extends Phase {
|
|||||||
* Function to do the logic and animation of completing a hatch and revealing the Pokemon
|
* Function to do the logic and animation of completing a hatch and revealing the Pokemon
|
||||||
*/
|
*/
|
||||||
doReveal(): void {
|
doReveal(): void {
|
||||||
|
// set the previous dex data so info container can show new unlocks in egg summary
|
||||||
const isShiny = this.pokemon.isShiny();
|
const isShiny = this.pokemon.isShiny();
|
||||||
if (this.pokemon.species.subLegendary) {
|
if (this.pokemon.species.subLegendary) {
|
||||||
this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY);
|
this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY);
|
||||||
@ -345,13 +353,13 @@ export class EggHatchPhase extends Phase {
|
|||||||
this.scene.ui.showText(i18next.t("egg:hatchFromTheEgg", { pokemonName: getPokemonNameWithAffix(this.pokemon) }), null, () => {
|
this.scene.ui.showText(i18next.t("egg:hatchFromTheEgg", { pokemonName: getPokemonNameWithAffix(this.pokemon) }), null, () => {
|
||||||
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
|
this.scene.gameData.updateSpeciesDexIvs(this.pokemon.species.speciesId, this.pokemon.ivs);
|
||||||
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
|
this.scene.gameData.setPokemonCaught(this.pokemon, true, true).then(() => {
|
||||||
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then(() => {
|
this.scene.gameData.setEggMoveUnlocked(this.pokemon.species, this.eggMoveIndex).then((value) => {
|
||||||
|
this.eggHatchData.setEggMoveUnlocked(value);
|
||||||
this.scene.ui.showText("", 0);
|
this.scene.ui.showText("", 0);
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, null, true, 3000);
|
}, null, true, 3000);
|
||||||
//this.scene.time.delayedCall(Utils.fixedInt(4250), () => this.scene.playBgm());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
@ -435,17 +443,11 @@ export class EggHatchPhase extends Phase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a Pokemon to be hatched by the egg
|
* Generates a Pokemon to be hatched by the egg
|
||||||
|
* Also stores the generated pokemon in this.eggHatchData
|
||||||
* @returns the hatched PlayerPokemon
|
* @returns the hatched PlayerPokemon
|
||||||
*/
|
*/
|
||||||
generatePokemon(): PlayerPokemon {
|
generatePokemon(): PlayerPokemon {
|
||||||
let ret: PlayerPokemon;
|
this.eggHatchData = this.eggLapsePhase.generatePokemon(this.egg);
|
||||||
|
return this.eggHatchData.pokemon;
|
||||||
this.scene.executeWithSeedOffset(() => {
|
|
||||||
ret = this.egg.generatePlayerPokemon(this.scene);
|
|
||||||
this.eggMoveIndex = this.egg.eggMoveIndex;
|
|
||||||
|
|
||||||
}, this.egg.id, EGG_SEED.toString());
|
|
||||||
|
|
||||||
return ret!;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,23 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { Egg } from "#app/data/egg.js";
|
import { Egg, EGG_SEED } from "#app/data/egg";
|
||||||
import { Phase } from "#app/phase.js";
|
import { Phase } from "#app/phase";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { EggHatchPhase } from "./egg-hatch-phase";
|
import { EggHatchPhase } from "./egg-hatch-phase";
|
||||||
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import { achvs } from "#app/system/achv";
|
||||||
|
import { PlayerPokemon } from "#app/field/pokemon";
|
||||||
|
import { EggSummaryPhase } from "./egg-summary-phase";
|
||||||
|
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phase that handles updating eggs, and hatching any ready eggs
|
||||||
|
* Also handles prompts for skipping animation, and calling the egg summary phase
|
||||||
|
*/
|
||||||
export class EggLapsePhase extends Phase {
|
export class EggLapsePhase extends Phase {
|
||||||
|
|
||||||
|
private eggHatchData: EggHatchData[] = [];
|
||||||
|
private readonly minEggsToPromptSkip: number = 5;
|
||||||
constructor(scene: BattleScene) {
|
constructor(scene: BattleScene) {
|
||||||
super(scene);
|
super(scene);
|
||||||
}
|
}
|
||||||
@ -16,20 +28,111 @@ export class EggLapsePhase extends Phase {
|
|||||||
const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => {
|
const eggsToHatch: Egg[] = this.scene.gameData.eggs.filter((egg: Egg) => {
|
||||||
return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1;
|
return Overrides.EGG_IMMEDIATE_HATCH_OVERRIDE ? true : --egg.hatchWaves < 1;
|
||||||
});
|
});
|
||||||
|
const eggsToHatchCount: number = eggsToHatch.length;
|
||||||
|
this.eggHatchData= [];
|
||||||
|
|
||||||
let eggCount: integer = eggsToHatch.length;
|
if (eggsToHatchCount > 0) {
|
||||||
|
|
||||||
if (eggCount) {
|
if (eggsToHatchCount >= this.minEggsToPromptSkip) {
|
||||||
|
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
|
||||||
|
// show prompt for skip
|
||||||
|
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0);
|
||||||
|
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
|
||||||
|
this.hatchEggsSkipped(eggsToHatch);
|
||||||
|
this.showSummary();
|
||||||
|
}, () => {
|
||||||
|
this.hatchEggsRegular(eggsToHatch);
|
||||||
|
this.showSummary();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, 100, true);
|
||||||
|
} else {
|
||||||
|
// regular hatches, no summary
|
||||||
this.scene.queueMessage(i18next.t("battle:eggHatching"));
|
this.scene.queueMessage(i18next.t("battle:eggHatching"));
|
||||||
|
this.hatchEggsRegular(eggsToHatch);
|
||||||
for (const egg of eggsToHatch) {
|
|
||||||
this.scene.unshiftPhase(new EggHatchPhase(this.scene, egg, eggCount));
|
|
||||||
if (eggCount > 0) {
|
|
||||||
eggCount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hatches eggs normally one by one, showing animations
|
||||||
|
* @param eggsToHatch list of eggs to hatch
|
||||||
|
*/
|
||||||
|
hatchEggsRegular(eggsToHatch: Egg[]) {
|
||||||
|
let eggsToHatchCount: number = eggsToHatch.length;
|
||||||
|
for (const egg of eggsToHatch) {
|
||||||
|
this.scene.unshiftPhase(new EggHatchPhase(this.scene, this, egg, eggsToHatchCount));
|
||||||
|
eggsToHatchCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hatches eggs with no animations
|
||||||
|
* @param eggsToHatch list of eggs to hatch
|
||||||
|
*/
|
||||||
|
hatchEggsSkipped(eggsToHatch: Egg[]) {
|
||||||
|
for (const egg of eggsToHatch) {
|
||||||
|
this.hatchEggSilently(egg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSummary() {
|
||||||
|
this.scene.unshiftPhase(new EggSummaryPhase(this.scene, this.eggHatchData));
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hatches an egg and stores it in the local EggHatchData array without animations
|
||||||
|
* Also validates the achievements for the hatched pokemon and removes the egg
|
||||||
|
* @param egg egg to hatch
|
||||||
|
*/
|
||||||
|
hatchEggSilently(egg: Egg) {
|
||||||
|
const eggIndex = this.scene.gameData.eggs.findIndex(e => e.id === egg.id);
|
||||||
|
if (eggIndex === -1) {
|
||||||
|
return this.end();
|
||||||
|
}
|
||||||
|
this.scene.gameData.eggs.splice(eggIndex, 1);
|
||||||
|
|
||||||
|
const data = this.generatePokemon(egg);
|
||||||
|
const pokemon = data.pokemon;
|
||||||
|
if (pokemon.fusionSpecies) {
|
||||||
|
pokemon.clearFusionSpecies();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pokemon.species.subLegendary) {
|
||||||
|
this.scene.validateAchv(achvs.HATCH_SUB_LEGENDARY);
|
||||||
|
}
|
||||||
|
if (pokemon.species.legendary) {
|
||||||
|
this.scene.validateAchv(achvs.HATCH_LEGENDARY);
|
||||||
|
}
|
||||||
|
if (pokemon.species.mythical) {
|
||||||
|
this.scene.validateAchv(achvs.HATCH_MYTHICAL);
|
||||||
|
}
|
||||||
|
if (pokemon.isShiny()) {
|
||||||
|
this.scene.validateAchv(achvs.HATCH_SHINY);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a Pokemon and creates a new EggHatchData instance for the given egg
|
||||||
|
* @param egg the egg to hatch
|
||||||
|
* @returns the hatched PlayerPokemon
|
||||||
|
*/
|
||||||
|
generatePokemon(egg: Egg): EggHatchData {
|
||||||
|
let ret: PlayerPokemon;
|
||||||
|
let newHatchData: EggHatchData;
|
||||||
|
this.scene.executeWithSeedOffset(() => {
|
||||||
|
ret = egg.generatePlayerPokemon(this.scene);
|
||||||
|
newHatchData = new EggHatchData(this.scene, ret, egg.eggMoveIndex);
|
||||||
|
newHatchData.setDex();
|
||||||
|
this.eggHatchData.push(newHatchData);
|
||||||
|
|
||||||
|
}, egg.id, EGG_SEED.toString());
|
||||||
|
return newHatchData!;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
50
src/phases/egg-summary-phase.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import { Phase } from "#app/phase";
|
||||||
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler";
|
||||||
|
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that represents the egg summary phase
|
||||||
|
* It does some of the function for updating egg data
|
||||||
|
* Phase is handled mostly by the egg-hatch-scene-handler UI
|
||||||
|
*/
|
||||||
|
export class EggSummaryPhase extends Phase {
|
||||||
|
private eggHatchData: EggHatchData[];
|
||||||
|
private eggHatchHandler: EggHatchSceneHandler;
|
||||||
|
|
||||||
|
constructor(scene: BattleScene, eggHatchData: EggHatchData[]) {
|
||||||
|
super(scene);
|
||||||
|
this.eggHatchData = eggHatchData;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
super.start();
|
||||||
|
|
||||||
|
// updates next pokemon once the current update has been completed
|
||||||
|
const updateNextPokemon = (i: number) => {
|
||||||
|
if (i >= this.eggHatchData.length) {
|
||||||
|
this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => {
|
||||||
|
this.scene.fadeOutBgm(undefined, false);
|
||||||
|
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.eggHatchData[i].setDex();
|
||||||
|
this.eggHatchData[i].updatePokemon().then(() => {
|
||||||
|
if (i < this.eggHatchData.length) {
|
||||||
|
updateNextPokemon(i + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateNextPokemon(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
this.eggHatchHandler.clear();
|
||||||
|
this.scene.ui.setModeForceTransition(Mode.MESSAGE).then(() => {});
|
||||||
|
super.end();
|
||||||
|
}
|
||||||
|
}
|
@ -77,4 +77,8 @@ export class EnemyCommandPhase extends FieldPhase {
|
|||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFieldIndex(): number {
|
||||||
|
return this.fieldIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import { ToggleDoublePositionPhase } from "./toggle-double-position-phase";
|
|||||||
import { GameOverPhase } from "./game-over-phase";
|
import { GameOverPhase } from "./game-over-phase";
|
||||||
import { SwitchPhase } from "./switch-phase";
|
import { SwitchPhase } from "./switch-phase";
|
||||||
import { VictoryPhase } from "./victory-phase";
|
import { VictoryPhase } from "./victory-phase";
|
||||||
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||||
|
|
||||||
export class FaintPhase extends PokemonPhase {
|
export class FaintPhase extends PokemonPhase {
|
||||||
private preventEndure: boolean;
|
private preventEndure: boolean;
|
||||||
@ -59,6 +60,7 @@ export class FaintPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true);
|
this.scene.queueMessage(i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), null, true);
|
||||||
|
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
|
||||||
|
|
||||||
if (pokemon.turnData?.attacksReceived?.length) {
|
if (pokemon.turnData?.attacksReceived?.length) {
|
||||||
const lastAttack = pokemon.turnData.attacksReceived[0];
|
const lastAttack = pokemon.turnData.attacksReceived[0];
|
||||||
|
@ -49,7 +49,9 @@ export class GameOverPhase extends BattlePhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.victory && this.scene.gameMode.isEndless) {
|
if (this.victory && this.scene.gameMode.isEndless) {
|
||||||
this.scene.ui.showDialogue(i18next.t("PGMmiscDialogue:ending_endless"), i18next.t("PGMmiscDialogue:ending_name"), 0, () => this.handleGameOver());
|
const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET;
|
||||||
|
const genderStr = PlayerGender[genderIndex].toLowerCase();
|
||||||
|
this.scene.ui.showDialogue(i18next.t("miscDialogue:ending_endless", { context: genderStr }), i18next.t("miscDialogue:ending_name"), 0, () => this.handleGameOver());
|
||||||
} else if (this.victory || !this.scene.enableRetries) {
|
} else if (this.victory || !this.scene.enableRetries) {
|
||||||
this.handleGameOver();
|
this.handleGameOver();
|
||||||
} else {
|
} else {
|
||||||
|
@ -377,16 +377,16 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user!, target); // TODO: is the bang correct here?
|
const moveAccuracy = this.move.getMove().calculateBattleAccuracy(user, target);
|
||||||
|
|
||||||
if (moveAccuracy === -1) {
|
if (moveAccuracy === -1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove());
|
const accuracyMultiplier = user.getAccuracyMultiplier(target, this.move.getMove());
|
||||||
const rand = user.randSeedInt(100, 1);
|
const rand = user.randSeedInt(100);
|
||||||
|
|
||||||
return rand <= moveAccuracy * (accuracyMultiplier!); // TODO: is this bang correct?
|
return rand < (moveAccuracy * accuracyMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the {@linkcode Pokemon} using this phase's invoked move */
|
/** Returns the {@linkcode Pokemon} using this phase's invoked move */
|
||||||
|
@ -44,8 +44,8 @@ export class MovePhase extends BattlePhase {
|
|||||||
this.cancelled = false;
|
this.cancelled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
canMove(): boolean {
|
canMove(ignoreDisableTags?: boolean): boolean {
|
||||||
return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp) && !!this.targets.length;
|
return this.pokemon.isActive(true) && this.move.isUsable(this.pokemon, this.ignorePp, ignoreDisableTags) && !!this.targets.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Signifies the current move should fail but still use PP */
|
/**Signifies the current move should fail but still use PP */
|
||||||
@ -63,10 +63,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
|
|
||||||
console.log(Moves[this.move.moveId]);
|
console.log(Moves[this.move.moveId]);
|
||||||
|
|
||||||
if (!this.canMove()) {
|
if (!this.canMove(true)) {
|
||||||
if (this.move.moveId && this.pokemon.summonData?.disabledMove === this.move.moveId) {
|
|
||||||
this.scene.queueMessage(i18next.t("battle:moveDisabled", { moveName: this.move.getName() }));
|
|
||||||
}
|
|
||||||
if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { // if the move PP was reduced from Spite or otherwise, the move fails
|
if (this.pokemon.isActive(true) && this.move.ppUsed >= this.move.getMovePp()) { // if the move PP was reduced from Spite or otherwise, the move fails
|
||||||
this.fail();
|
this.fail();
|
||||||
this.showMoveText();
|
this.showMoveText();
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import BattleScene from "#app/battle-scene.js";
|
||||||
import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/ability.js";
|
import { applyPostTurnAbAttrs, PostTurnAbAttr } from "#app/data/ability.js";
|
||||||
import { BattlerTagLapseType } from "#app/data/battler-tags.js";
|
import { BattlerTagLapseType } from "#app/data/battler-tags.js";
|
||||||
import { allMoves } from "#app/data/move.js";
|
|
||||||
import { TerrainType } from "#app/data/terrain.js";
|
import { TerrainType } from "#app/data/terrain.js";
|
||||||
import { Moves } from "#app/enums/moves.js";
|
|
||||||
import { WeatherType } from "#app/enums/weather-type.js";
|
import { WeatherType } from "#app/enums/weather-type.js";
|
||||||
import { TurnEndEvent } from "#app/events/battle-scene.js";
|
import { TurnEndEvent } from "#app/events/battle-scene.js";
|
||||||
import Pokemon from "#app/field/pokemon.js";
|
import Pokemon from "#app/field/pokemon.js";
|
||||||
@ -11,7 +9,6 @@ import { getPokemonNameWithAffix } from "#app/messages.js";
|
|||||||
import { TurnHealModifier, EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier, TurnStatusEffectModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier.js";
|
import { TurnHealModifier, EnemyTurnHealModifier, EnemyStatusEffectHealChanceModifier, TurnStatusEffectModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier.js";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { FieldPhase } from "./field-phase";
|
import { FieldPhase } from "./field-phase";
|
||||||
import { MessagePhase } from "./message-phase";
|
|
||||||
import { PokemonHealPhase } from "./pokemon-heal-phase";
|
import { PokemonHealPhase } from "./pokemon-heal-phase";
|
||||||
|
|
||||||
export class TurnEndPhase extends FieldPhase {
|
export class TurnEndPhase extends FieldPhase {
|
||||||
@ -28,11 +25,6 @@ export class TurnEndPhase extends FieldPhase {
|
|||||||
const handlePokemon = (pokemon: Pokemon) => {
|
const handlePokemon = (pokemon: Pokemon) => {
|
||||||
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
|
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
|
||||||
|
|
||||||
if (pokemon.summonData.disabledMove && !--pokemon.summonData.disabledTurns) {
|
|
||||||
this.scene.pushPhase(new MessagePhase(this.scene, i18next.t("battle:notDisabled", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: allMoves[pokemon.summonData.disabledMove].name })));
|
|
||||||
pokemon.summonData.disabledMove = Moves.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
|
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
|
||||||
|
|
||||||
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
|
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import BattleScene from "#app/battle-scene.js";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability.js";
|
import { applyPreWeatherEffectAbAttrs, SuppressWeatherEffectAbAttr, PreWeatherDamageAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPostWeatherLapseAbAttrs, PostWeatherLapseAbAttr } from "#app/data/ability.js";
|
||||||
import { CommonAnim } from "#app/data/battle-anims.js";
|
import { CommonAnim } from "#app/data/battle-anims";
|
||||||
import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather.js";
|
import { Weather, getWeatherDamageMessage, getWeatherLapseMessage } from "#app/data/weather";
|
||||||
import { WeatherType } from "#app/enums/weather-type.js";
|
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
||||||
import Pokemon, { HitResult } from "#app/field/pokemon.js";
|
import { WeatherType } from "#app/enums/weather-type";
|
||||||
import * as Utils from "#app/utils.js";
|
import Pokemon, { HitResult } from "#app/field/pokemon";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
import { CommonAnimPhase } from "./common-anim-phase";
|
import { CommonAnimPhase } from "./common-anim-phase";
|
||||||
|
|
||||||
export class WeatherEffectPhase extends CommonAnimPhase {
|
export class WeatherEffectPhase extends CommonAnimPhase {
|
||||||
@ -39,7 +40,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
|||||||
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
|
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
|
||||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||||
|
|
||||||
if (cancelled.value) {
|
if (cancelled.value || pokemon.getTag(BattlerTagType.UNDERGROUND) || pokemon.getTag(BattlerTagType.UNDERWATER)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,6 +857,14 @@ export class GameData {
|
|||||||
|
|
||||||
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
|
const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct?
|
||||||
|
|
||||||
|
// TODO: Remove this block after save migration is implemented
|
||||||
|
if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) {
|
||||||
|
settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"];
|
||||||
|
delete settings["REROLL_TARGET"];
|
||||||
|
localStorage.setItem("settings", JSON.stringify(settings));
|
||||||
|
}
|
||||||
|
// End of block to remove
|
||||||
|
|
||||||
for (const setting of Object.keys(settings)) {
|
for (const setting of Object.keys(settings)) {
|
||||||
setSetting(this.scene, setting, settings[setting]);
|
setSetting(this.scene, setting, settings[setting]);
|
||||||
}
|
}
|
||||||
@ -1553,11 +1561,11 @@ export class GameData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> {
|
setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false, showMessage: boolean = true): Promise<void> {
|
||||||
return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg);
|
return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg, showMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> {
|
setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false, showMessage: boolean = true): Promise<void> {
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>(resolve => {
|
||||||
const dexEntry = this.dexData[species.speciesId];
|
const dexEntry = this.dexData[species.speciesId];
|
||||||
const caughtAttr = dexEntry.caughtAttr;
|
const caughtAttr = dexEntry.caughtAttr;
|
||||||
@ -1616,13 +1624,17 @@ export class GameData {
|
|||||||
const checkPrevolution = () => {
|
const checkPrevolution = () => {
|
||||||
if (hasPrevolution) {
|
if (hasPrevolution) {
|
||||||
const prevolutionSpecies = pokemonPrevolutions[species.speciesId];
|
const prevolutionSpecies = pokemonPrevolutions[species.speciesId];
|
||||||
return this.setPokemonSpeciesCaught(pokemon, getPokemonSpecies(prevolutionSpecies), incrementCount, fromEgg).then(() => resolve());
|
this.setPokemonSpeciesCaught(pokemon, getPokemonSpecies(prevolutionSpecies), incrementCount, fromEgg, showMessage).then(() => resolve());
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (newCatch && speciesStarters.hasOwnProperty(species.speciesId)) {
|
if (newCatch && speciesStarters.hasOwnProperty(species.speciesId)) {
|
||||||
|
if (!showMessage) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.scene.playSound("level_up_fanfare");
|
this.scene.playSound("level_up_fanfare");
|
||||||
this.scene.ui.showText(i18next.t("battle:addedAsAStarter", { pokemonName: species.name }), null, () => checkPrevolution(), null, true);
|
this.scene.ui.showText(i18next.t("battle:addedAsAStarter", { pokemonName: species.name }), null, () => checkPrevolution(), null, true);
|
||||||
} else {
|
} else {
|
||||||
@ -1668,7 +1680,7 @@ export class GameData {
|
|||||||
this.starterData[species.speciesId].candyCount += count;
|
this.starterData[species.speciesId].candyCount += count;
|
||||||
}
|
}
|
||||||
|
|
||||||
setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer): Promise<boolean> {
|
setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer, showMessage: boolean = true): Promise<boolean> {
|
||||||
return new Promise<boolean>(resolve => {
|
return new Promise<boolean>(resolve => {
|
||||||
const speciesId = species.speciesId;
|
const speciesId = species.speciesId;
|
||||||
if (!speciesEggMoves.hasOwnProperty(speciesId) || !speciesEggMoves[speciesId][eggMoveIndex]) {
|
if (!speciesEggMoves.hasOwnProperty(speciesId) || !speciesEggMoves[speciesId][eggMoveIndex]) {
|
||||||
@ -1688,11 +1700,15 @@ export class GameData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.starterData[speciesId].eggMoves |= value;
|
this.starterData[speciesId].eggMoves |= value;
|
||||||
|
if (!showMessage) {
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.scene.playSound("level_up_fanfare");
|
this.scene.playSound("level_up_fanfare");
|
||||||
|
|
||||||
const moveName = allMoves[speciesEggMoves[speciesId][eggMoveIndex]].name;
|
const moveName = allMoves[speciesEggMoves[speciesId][eggMoveIndex]].name;
|
||||||
this.scene.ui.showText(eggMoveIndex === 3 ? i18next.t("egg:rareEggMoveUnlock", { moveName: moveName }) : i18next.t("egg:eggMoveUnlock", { moveName: moveName }), null, () => resolve(true), null, true);
|
this.scene.ui.showText(eggMoveIndex === 3 ? i18next.t("egg:rareEggMoveUnlock", { moveName: moveName }) : i18next.t("egg:eggMoveUnlock", { moveName: moveName }), null, (() => {
|
||||||
|
resolve(true);
|
||||||
|
}), null, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +127,6 @@ export default class PokemonData {
|
|||||||
this.summonData.stats = source.summonData.stats;
|
this.summonData.stats = source.summonData.stats;
|
||||||
this.summonData.statStages = source.summonData.statStages;
|
this.summonData.statStages = source.summonData.statStages;
|
||||||
this.summonData.moveQueue = source.summonData.moveQueue;
|
this.summonData.moveQueue = source.summonData.moveQueue;
|
||||||
this.summonData.disabledMove = source.summonData.disabledMove;
|
|
||||||
this.summonData.disabledTurns = source.summonData.disabledTurns;
|
|
||||||
this.summonData.abilitySuppressed = source.summonData.abilitySuppressed;
|
this.summonData.abilitySuppressed = source.summonData.abilitySuppressed;
|
||||||
this.summonData.abilitiesApplied = source.summonData.abilitiesApplied;
|
this.summonData.abilitiesApplied = source.summonData.abilitiesApplied;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
||||||
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";
|
||||||
@ -33,31 +32,45 @@ describe("Abilities - Aura Break", () => {
|
|||||||
game.override.enemySpecies(Species.SHUCKLE);
|
game.override.enemySpecies(Species.SHUCKLE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reverses the effect of fairy aura", async () => {
|
it("reverses the effect of Fairy Aura", async () => {
|
||||||
const moveToCheck = allMoves[Moves.MOONBLAST];
|
const moveToCheck = allMoves[Moves.MOONBLAST];
|
||||||
const basePower = moveToCheck.power;
|
const basePower = moveToCheck.power;
|
||||||
|
|
||||||
game.override.ability(Abilities.FAIRY_AURA);
|
game.override.ability(Abilities.FAIRY_AURA);
|
||||||
vi.spyOn(moveToCheck, "calculateBattlePower");
|
vi.spyOn(moveToCheck, "calculateBattlePower");
|
||||||
|
|
||||||
await game.startBattle([Species.PIKACHU]);
|
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||||
game.move.select(Moves.MOONBLAST);
|
game.move.select(Moves.MOONBLAST);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
|
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reverses the effect of dark aura", async () => {
|
it("reverses the effect of Dark Aura", async () => {
|
||||||
const moveToCheck = allMoves[Moves.DARK_PULSE];
|
const moveToCheck = allMoves[Moves.DARK_PULSE];
|
||||||
const basePower = moveToCheck.power;
|
const basePower = moveToCheck.power;
|
||||||
|
|
||||||
game.override.ability(Abilities.DARK_AURA);
|
game.override.ability(Abilities.DARK_AURA);
|
||||||
vi.spyOn(moveToCheck, "calculateBattlePower");
|
vi.spyOn(moveToCheck, "calculateBattlePower");
|
||||||
|
|
||||||
await game.startBattle([Species.PIKACHU]);
|
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||||
game.move.select(Moves.DARK_PULSE);
|
game.move.select(Moves.DARK_PULSE);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
|
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(expect.closeTo(basePower * auraBreakMultiplier));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("has no effect if neither Fairy Aura nor Dark Aura are present", async () => {
|
||||||
|
const moveToCheck = allMoves[Moves.MOONBLAST];
|
||||||
|
const basePower = moveToCheck.power;
|
||||||
|
|
||||||
|
game.override.ability(Abilities.BALL_FETCH);
|
||||||
|
vi.spyOn(moveToCheck, "calculateBattlePower");
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||||
|
game.move.select(Moves.MOONBLAST);
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { toDmgValue } from "#app/utils";
|
import { toDmgValue } from "#app/utils";
|
||||||
|
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";
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
@ -205,4 +206,22 @@ describe("Abilities - Disguise", () => {
|
|||||||
expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase");
|
expect(game.scene.getCurrentPhase()?.constructor.name).toBe("CommandPhase");
|
||||||
expect(game.scene.currentBattle.waveIndex).toBe(2);
|
expect(game.scene.currentBattle.waveIndex).toBe(2);
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("activates when Aerilate circumvents immunity to the move's base type", async () => {
|
||||||
|
game.override.ability(Abilities.AERILATE);
|
||||||
|
game.override.moveset([Moves.TACKLE]);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const mimikyu = game.scene.getEnemyPokemon()!;
|
||||||
|
const maxHp = mimikyu.getMaxHp();
|
||||||
|
const disguiseDamage = toDmgValue(maxHp / 8);
|
||||||
|
|
||||||
|
game.move.select(Moves.TACKLE);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
|
||||||
|
expect(mimikyu.formIndex).toBe(bustedForm);
|
||||||
|
expect(mimikyu.hp).toBe(maxHp - disguiseDamage);
|
||||||
|
}, TIMEOUT);
|
||||||
});
|
});
|
||||||
|
@ -2,12 +2,6 @@ import { Stat } from "#enums/stat";
|
|||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import { Type } from "#app/data/type";
|
import { Type } from "#app/data/type";
|
||||||
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
import { BattlerTagType } from "#app/enums/battler-tag-type";
|
||||||
import { BerryPhase } from "#app/phases/berry-phase";
|
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
|
||||||
import { DamagePhase } from "#app/phases/damage-phase";
|
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
import { toDmgValue } from "#app/utils";
|
import { toDmgValue } from "#app/utils";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -15,7 +9,7 @@ import { Species } from "#enums/species";
|
|||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
const TIMEOUT = 20 * 1000;
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
@ -39,36 +33,31 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
game.override.disableCrits();
|
game.override.disableCrits();
|
||||||
game.override.ability(Abilities.PARENTAL_BOND);
|
game.override.ability(Abilities.PARENTAL_BOND);
|
||||||
game.override.enemySpecies(Species.SNORLAX);
|
game.override.enemySpecies(Species.SNORLAX);
|
||||||
game.override.enemyAbility(Abilities.INSOMNIA);
|
game.override.enemyAbility(Abilities.FUR_COAT);
|
||||||
game.override.enemyMoveset(SPLASH_ONLY);
|
game.override.enemyMoveset(SPLASH_ONLY);
|
||||||
game.override.startingLevel(100);
|
game.override.startingLevel(100);
|
||||||
game.override.enemyLevel(100);
|
game.override.enemyLevel(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should add second strike to attack move",
|
"should add second strike to attack move",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.TACKLE]);
|
game.override.moveset([Moves.TACKLE]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
let enemyStartingHp = enemyPokemon.hp;
|
let enemyStartingHp = enemyPokemon.hp;
|
||||||
|
|
||||||
game.move.select(Moves.TACKLE);
|
game.move.select(Moves.TACKLE);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
|
||||||
const firstStrikeDamage = enemyStartingHp - enemyPokemon.hp;
|
const firstStrikeDamage = enemyStartingHp - enemyPokemon.hp;
|
||||||
enemyStartingHp = enemyPokemon.hp;
|
enemyStartingHp = enemyPokemon.hp;
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
const secondStrikeDamage = enemyStartingHp - enemyPokemon.hp;
|
const secondStrikeDamage = enemyStartingHp - enemyPokemon.hp;
|
||||||
|
|
||||||
@ -77,556 +66,460 @@ describe("Abilities - Parental Bond", () => {
|
|||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should apply secondary effects to both strikes",
|
"should apply secondary effects to both strikes",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.POWER_UP_PUNCH]);
|
game.override.moveset([Moves.POWER_UP_PUNCH]);
|
||||||
game.override.enemySpecies(Species.AMOONGUSS);
|
game.override.enemySpecies(Species.AMOONGUSS);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.POWER_UP_PUNCH);
|
game.move.select(Moves.POWER_UP_PUNCH);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
|
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply to Status moves",
|
"should not apply to Status moves",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.BABY_DOLL_EYES]);
|
game.override.moveset([Moves.BABY_DOLL_EYES]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.BABY_DOLL_EYES);
|
game.move.select(Moves.BABY_DOLL_EYES);
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply to multi-hit moves",
|
"should not apply to multi-hit moves",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.DOUBLE_HIT]);
|
game.override.moveset([Moves.DOUBLE_HIT]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.DOUBLE_HIT);
|
game.move.select(Moves.DOUBLE_HIT);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply to self-sacrifice moves",
|
"should not apply to self-sacrifice moves",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.SELF_DESTRUCT]);
|
game.override.moveset([Moves.SELF_DESTRUCT]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.SELF_DESTRUCT);
|
game.move.select(Moves.SELF_DESTRUCT);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to("DamagePhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(1);
|
expect(leadPokemon.turnData.hitCount).toBe(1);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply to Rollout",
|
"should not apply to Rollout",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.ROLLOUT]);
|
game.override.moveset([Moves.ROLLOUT]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.ROLLOUT);
|
game.move.select(Moves.ROLLOUT);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to("DamagePhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(1);
|
expect(leadPokemon.turnData.hitCount).toBe(1);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply multiplier to fixed-damage moves",
|
"should not apply multiplier to fixed-damage moves",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.DRAGON_RAGE]);
|
game.override.moveset([Moves.DRAGON_RAGE]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyStartingHp = enemyPokemon.hp;
|
|
||||||
|
|
||||||
game.move.select(Moves.DRAGON_RAGE);
|
game.move.select(Moves.DRAGON_RAGE);
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.hp).toBe(enemyStartingHp - 80);
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - 80);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply multiplier to counter moves",
|
"should not apply multiplier to counter moves",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.COUNTER]);
|
game.override.moveset([Moves.COUNTER]);
|
||||||
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.SHUCKLE]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const playerStartingHp = leadPokemon.hp;
|
|
||||||
const enemyStartingHp = enemyPokemon.hp;
|
|
||||||
|
|
||||||
game.move.select(Moves.COUNTER);
|
game.move.select(Moves.COUNTER);
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
const playerDamage = playerStartingHp - leadPokemon.hp;
|
const playerDamage = leadPokemon.getMaxHp() - leadPokemon.hp;
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.hp).toBe(enemyStartingHp - 4 * playerDamage);
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp() - 4 * playerDamage);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply to multi-target moves",
|
"should not apply to multi-target moves",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
game.override.moveset([Moves.EARTHQUAKE]);
|
game.override.moveset([Moves.EARTHQUAKE]);
|
||||||
|
game.override.passiveAbility(Abilities.LEVITATE);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD, Species.PIDGEOT]);
|
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerField();
|
const playerPokemon = game.scene.getPlayerField();
|
||||||
expect(playerPokemon.length).toBe(2);
|
|
||||||
playerPokemon.forEach(p => expect(p).not.toBe(undefined));
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyField();
|
|
||||||
expect(enemyPokemon.length).toBe(2);
|
|
||||||
enemyPokemon.forEach(p => expect(p).not.toBe(undefined));
|
|
||||||
|
|
||||||
game.move.select(Moves.EARTHQUAKE);
|
game.move.select(Moves.EARTHQUAKE);
|
||||||
await game.phaseInterceptor.to(CommandPhase);
|
|
||||||
|
|
||||||
game.move.select(Moves.EARTHQUAKE, 1);
|
game.move.select(Moves.EARTHQUAKE, 1);
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
playerPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1));
|
playerPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1));
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should apply to multi-target moves when hitting only one target",
|
"should apply to multi-target moves when hitting only one target",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.EARTHQUAKE]);
|
game.override.moveset([Moves.EARTHQUAKE]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.EARTHQUAKE);
|
game.move.select(Moves.EARTHQUAKE);
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to("DamagePhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should only trigger post-target move effects once",
|
"should only trigger post-target move effects once",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.MIND_BLOWN]);
|
game.override.moveset([Moves.MIND_BLOWN]);
|
||||||
|
|
||||||
await game.startBattle([Species.PIDGEOT]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.MIND_BLOWN);
|
game.move.select(Moves.MIND_BLOWN);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase, false);
|
await game.phaseInterceptor.to("DamagePhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
|
|
||||||
// This test will time out if the user faints
|
// This test will time out if the user faints
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.hp).toBe(toDmgValue(leadPokemon.getMaxHp() / 2));
|
expect(leadPokemon.hp).toBe(Math.ceil(leadPokemon.getMaxHp() / 2));
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"Burn Up only removes type after second strike with this ability",
|
"Burn Up only removes type after the second strike",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.BURN_UP]);
|
game.override.moveset([Moves.BURN_UP]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.CHARIZARD]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.BURN_UP);
|
game.move.select(Moves.BURN_UP);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(enemyPokemon.hp).toBeGreaterThan(0);
|
expect(enemyPokemon.hp).toBeGreaterThan(0);
|
||||||
expect(leadPokemon.isOfType(Type.FIRE)).toBe(true);
|
expect(leadPokemon.isOfType(Type.FIRE)).toBe(true);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.isOfType(Type.FIRE)).toBe(false);
|
expect(leadPokemon.isOfType(Type.FIRE)).toBe(false);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"Moves boosted by this ability and Multi-Lens should strike 4 times",
|
"Moves boosted by this ability and Multi-Lens should strike 4 times",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.TACKLE]);
|
game.override.moveset([Moves.TACKLE]);
|
||||||
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.TACKLE);
|
game.move.select(Moves.TACKLE);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(4);
|
expect(leadPokemon.turnData.hitCount).toBe(4);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"Super Fang boosted by this ability and Multi-Lens should strike twice",
|
"Super Fang boosted by this ability and Multi-Lens should strike twice",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.SUPER_FANG]);
|
game.override.moveset([Moves.SUPER_FANG]);
|
||||||
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyStartingHp = enemyPokemon.hp;
|
|
||||||
|
|
||||||
game.move.select(Moves.SUPER_FANG);
|
game.move.select(Moves.SUPER_FANG);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase, false);
|
await game.phaseInterceptor.to("MoveEndPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.hp).toBe(Math.ceil(enemyStartingHp * 0.25));
|
expect(enemyPokemon.hp).toBe(Math.ceil(enemyPokemon.getMaxHp() * 0.25));
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"Seismic Toss boosted by this ability and Multi-Lens should strike twice",
|
"Seismic Toss boosted by this ability and Multi-Lens should strike twice",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.SEISMIC_TOSS]);
|
game.override.moveset([Moves.SEISMIC_TOSS]);
|
||||||
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyStartingHp = enemyPokemon.hp;
|
const enemyStartingHp = enemyPokemon.hp;
|
||||||
|
|
||||||
game.move.select(Moves.SEISMIC_TOSS);
|
game.move.select(Moves.SEISMIC_TOSS);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase, false);
|
await game.phaseInterceptor.to("MoveEndPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.hp).toBe(enemyStartingHp - 200);
|
expect(enemyPokemon.hp).toBe(enemyStartingHp - 200);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"Hyper Beam boosted by this ability should strike twice, then recharge",
|
"Hyper Beam boosted by this ability should strike twice, then recharge",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.HYPER_BEAM]);
|
game.override.moveset([Moves.HYPER_BEAM]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.HYPER_BEAM);
|
game.move.select(Moves.HYPER_BEAM);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeUndefined();
|
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeUndefined();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeDefined();
|
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeDefined();
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
/** TODO: Fix TRAPPED tag lapsing incorrectly, then run this test */
|
it(
|
||||||
test(
|
|
||||||
"Anchor Shot boosted by this ability should only trap the target after the second hit",
|
"Anchor Shot boosted by this ability should only trap the target after the second hit",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.ANCHOR_SHOT]);
|
game.override.moveset([Moves.ANCHOR_SHOT]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.ANCHOR_SHOT);
|
game.move.select(Moves.ANCHOR_SHOT);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); // Passes
|
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); // Passes
|
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); // Fails :(
|
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined();
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"Smack Down boosted by this ability should only ground the target after the second hit",
|
"Smack Down boosted by this ability should only ground the target after the second hit",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.SMACK_DOWN]);
|
game.override.moveset([Moves.SMACK_DOWN]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.SMACK_DOWN);
|
game.move.select(Moves.SMACK_DOWN);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
|
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
|
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"U-turn boosted by this ability should strike twice before forcing a switch",
|
"U-turn boosted by this ability should strike twice before forcing a switch",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.U_TURN]);
|
game.override.moveset([Moves.U_TURN]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
|
await game.classicMode.startBattle([Species.MAGIKARP, Species.BLASTOISE]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.U_TURN);
|
game.move.select(Moves.U_TURN);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
|
|
||||||
// This will cause this test to time out if the switch was forced on the first hit.
|
// This will cause this test to time out if the switch was forced on the first hit.
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to("MoveEffectPhase", false);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"Wake-Up Slap boosted by this ability should only wake up the target after the second hit",
|
"Wake-Up Slap boosted by this ability should only wake up the target after the second hit",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.WAKE_UP_SLAP]).enemyStatusEffect(StatusEffect.SLEEP);
|
game.override.moveset([Moves.WAKE_UP_SLAP]).enemyStatusEffect(StatusEffect.SLEEP);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.WAKE_UP_SLAP);
|
game.move.select(Moves.WAKE_UP_SLAP);
|
||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
expect(leadPokemon.turnData.hitCount).toBe(2);
|
expect(leadPokemon.turnData.hitCount).toBe(2);
|
||||||
expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP);
|
expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.status?.effect).toBeUndefined();
|
expect(enemyPokemon.status?.effect).toBeUndefined();
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not cause user to hit into King's Shield more than once",
|
"should not cause user to hit into King's Shield more than once",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.TACKLE]);
|
game.override.moveset([Moves.TACKLE]);
|
||||||
game.override.enemyMoveset([Moves.KINGS_SHIELD, Moves.KINGS_SHIELD, Moves.KINGS_SHIELD, Moves.KINGS_SHIELD]);
|
game.override.enemyMoveset(Array(4).fill(Moves.KINGS_SHIELD));
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
const leadPokemon = game.scene.getPlayerPokemon()!;
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.TACKLE);
|
game.move.select(Moves.TACKLE);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not cause user to hit into Storm Drain more than once",
|
"should not cause user to hit into Storm Drain more than once",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.moveset([Moves.WATER_GUN]);
|
game.override.moveset([Moves.WATER_GUN]);
|
||||||
game.override.enemyAbility(Abilities.STORM_DRAIN);
|
game.override.enemyAbility(Abilities.STORM_DRAIN);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD]);
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
const leadPokemon = game.scene.getPlayerPokemon()!;
|
|
||||||
expect(leadPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
expect(enemyPokemon).not.toBe(undefined);
|
|
||||||
|
|
||||||
game.move.select(Moves.WATER_GUN);
|
game.move.select(Moves.WATER_GUN);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1);
|
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1);
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
|
|
||||||
test(
|
it(
|
||||||
"ability should not apply to multi-target moves with Multi-Lens",
|
"should not apply to multi-target moves with Multi-Lens",
|
||||||
async () => {
|
async () => {
|
||||||
game.override.battleType("double");
|
game.override.battleType("double");
|
||||||
game.override.moveset([Moves.EARTHQUAKE, Moves.SPLASH]);
|
game.override.moveset([Moves.EARTHQUAKE, Moves.SPLASH]);
|
||||||
|
game.override.passiveAbility(Abilities.LEVITATE);
|
||||||
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
|
||||||
|
|
||||||
await game.startBattle([Species.CHARIZARD, Species.PIDGEOT]);
|
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerField();
|
|
||||||
expect(playerPokemon.length).toBe(2);
|
|
||||||
playerPokemon.forEach(p => expect(p).not.toBe(undefined));
|
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyField();
|
const enemyPokemon = game.scene.getEnemyField();
|
||||||
expect(enemyPokemon.length).toBe(2);
|
|
||||||
enemyPokemon.forEach(p => expect(p).not.toBe(undefined));
|
|
||||||
|
|
||||||
const enemyStartingHp = enemyPokemon.map(p => p.hp);
|
const enemyStartingHp = enemyPokemon.map(p => p.hp);
|
||||||
|
|
||||||
game.move.select(Moves.EARTHQUAKE);
|
game.move.select(Moves.EARTHQUAKE);
|
||||||
await game.phaseInterceptor.to(CommandPhase);
|
|
||||||
|
|
||||||
game.move.select(Moves.SPLASH, 1);
|
game.move.select(Moves.SPLASH, 1);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.phaseInterceptor.to("DamagePhase");
|
||||||
|
|
||||||
await game.phaseInterceptor.to(DamagePhase);
|
|
||||||
const enemyFirstHitDamage = enemyStartingHp.map((hp, i) => hp - enemyPokemon[i].hp);
|
const enemyFirstHitDamage = enemyStartingHp.map((hp, i) => hp - enemyPokemon[i].hp);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BerryPhase, false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
enemyPokemon.forEach((p, i) => expect(enemyStartingHp[i] - p.hp).toBe(2 * enemyFirstHitDamage[i]));
|
enemyPokemon.forEach((p, i) => expect(enemyStartingHp[i] - p.hp).toBe(2 * enemyFirstHitDamage[i]));
|
||||||
|
|
||||||
}, TIMEOUT
|
}, TIMEOUT
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { allAbilities } from "#app/data/ability";
|
import { allAbilities } from "#app/data/ability";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
@ -37,7 +36,7 @@ describe("Abilities - Steely Spirit", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("increases Steel-type moves' power used by the user and its allies by 50%", async () => {
|
it("increases Steel-type moves' power used by the user and its allies by 50%", async () => {
|
||||||
await game.startBattle([Species.PIKACHU, Species.SHUCKLE]);
|
await game.classicMode.startBattle([Species.PIKACHU, Species.SHUCKLE]);
|
||||||
const boostSource = game.scene.getPlayerField()[1];
|
const boostSource = game.scene.getPlayerField()[1];
|
||||||
const enemyToCheck = game.scene.getEnemyPokemon()!;
|
const enemyToCheck = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
@ -47,13 +46,13 @@ describe("Abilities - Steely Spirit", () => {
|
|||||||
|
|
||||||
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
|
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
|
||||||
game.move.select(Moves.SPLASH, 1);
|
game.move.select(Moves.SPLASH, 1);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * steelySpiritMultiplier);
|
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * steelySpiritMultiplier);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stacks if multiple users with this ability are on the field.", async () => {
|
it("stacks if multiple users with this ability are on the field.", async () => {
|
||||||
await game.startBattle([Species.PIKACHU, Species.PIKACHU]);
|
await game.classicMode.startBattle([Species.PIKACHU, Species.PIKACHU]);
|
||||||
const enemyToCheck = game.scene.getEnemyPokemon()!;
|
const enemyToCheck = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
game.scene.getPlayerField().forEach(p => {
|
game.scene.getPlayerField().forEach(p => {
|
||||||
@ -64,13 +63,13 @@ describe("Abilities - Steely Spirit", () => {
|
|||||||
|
|
||||||
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
|
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
|
||||||
game.move.select(moveToCheck, 1, enemyToCheck.getBattlerIndex());
|
game.move.select(moveToCheck, 1, enemyToCheck.getBattlerIndex());
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * Math.pow(steelySpiritMultiplier, 2));
|
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower * Math.pow(steelySpiritMultiplier, 2));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not take effect when suppressed", async () => {
|
it("does not take effect when suppressed", async () => {
|
||||||
await game.startBattle([Species.PIKACHU, Species.SHUCKLE]);
|
await game.classicMode.startBattle([Species.PIKACHU, Species.SHUCKLE]);
|
||||||
const boostSource = game.scene.getPlayerField()[1];
|
const boostSource = game.scene.getPlayerField()[1];
|
||||||
const enemyToCheck = game.scene.getEnemyPokemon()!;
|
const enemyToCheck = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
@ -84,8 +83,25 @@ describe("Abilities - Steely Spirit", () => {
|
|||||||
|
|
||||||
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
|
game.move.select(moveToCheck, 0, enemyToCheck.getBattlerIndex());
|
||||||
game.move.select(Moves.SPLASH, 1);
|
game.move.select(Moves.SPLASH, 1);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower);
|
expect(allMoves[moveToCheck].calculateBattlePower).toHaveReturnedWith(ironHeadPower);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("affects variable-type moves if their resolved type is Steel", async () => {
|
||||||
|
game.override
|
||||||
|
.ability(Abilities.STEELY_SPIRIT)
|
||||||
|
.moveset([Moves.REVELATION_DANCE]);
|
||||||
|
|
||||||
|
const revelationDance = allMoves[Moves.REVELATION_DANCE];
|
||||||
|
vi.spyOn(revelationDance, "calculateBattlePower");
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.KLINKLANG]);
|
||||||
|
|
||||||
|
game.move.select(Moves.REVELATION_DANCE);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(revelationDance.calculateBattlePower).toHaveReturnedWith(revelationDance.power * 1.5);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
62
src/test/arena/weather_hail.test.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { WeatherType } from "#app/data/weather";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
|
||||||
|
describe("Weather - Hail", () => {
|
||||||
|
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
|
||||||
|
.weather(WeatherType.HAIL)
|
||||||
|
.battleType("single")
|
||||||
|
.moveset(SPLASH_ONLY)
|
||||||
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.enemySpecies(Species.MAGIKARP);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("inflicts damage equal to 1/16 of Pokemon's max HP at turn end", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
game.scene.getField(true).forEach(pokemon => {
|
||||||
|
expect(pokemon.hp).toBeLessThan(pokemon.getMaxHp() - Math.floor(pokemon.getMaxHp() / 16));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not inflict damage to a Pokemon that is underwater (Dive) or underground (Dig)", async () => {
|
||||||
|
game.override.moveset([Moves.DIG]);
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
|
game.move.select(Moves.DIG);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp());
|
||||||
|
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp() - Math.floor(enemyPokemon.getMaxHp() / 16));
|
||||||
|
});
|
||||||
|
});
|
59
src/test/arena/weather_sandstorm.test.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { WeatherType } from "#app/data/weather";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
|
|
||||||
|
describe("Weather - Sandstorm", () => {
|
||||||
|
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
|
||||||
|
.weather(WeatherType.SANDSTORM)
|
||||||
|
.battleType("single")
|
||||||
|
.moveset(SPLASH_ONLY)
|
||||||
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.enemySpecies(Species.MAGIKARP);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("inflicts damage equal to 1/16 of Pokemon's max HP at turn end", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
game.scene.getField(true).forEach(pokemon => {
|
||||||
|
expect(pokemon.hp).toBeLessThan(pokemon.getMaxHp() - Math.floor(pokemon.getMaxHp() / 16));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not inflict damage to a Pokemon that is underwater (Dive) or underground (Dig)", async () => {
|
||||||
|
game.override.moveset([Moves.DIVE]);
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||||
|
|
||||||
|
game.move.select(Moves.DIVE);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
|
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp());
|
||||||
|
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp() - Math.floor(enemyPokemon.getMaxHp() / 16));
|
||||||
|
});
|
||||||
|
});
|
303
src/test/escape-calculations.test.ts
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||||
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
|
import { Command } from "#app/ui/command-ui-handler";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Escape chance calculations", () => {
|
||||||
|
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
|
||||||
|
.battleType("single")
|
||||||
|
.enemySpecies(Species.BULBASAUR)
|
||||||
|
.enemyAbility(Abilities.INSOMNIA)
|
||||||
|
.ability(Abilities.INSOMNIA);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("single non-boss opponent", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.BULBASAUR]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerField();
|
||||||
|
const enemyField = game.scene.getEnemyField();
|
||||||
|
const enemySpeed = 100;
|
||||||
|
// set enemyPokemon's speed to 100
|
||||||
|
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
|
||||||
|
|
||||||
|
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||||
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
|
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
|
||||||
|
const escapePercentage = new Utils.NumberHolder(0);
|
||||||
|
|
||||||
|
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
|
||||||
|
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
|
||||||
|
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
|
||||||
|
{ pokemonSpeedRatio: 0.1, escapeAttempts: 0, expectedEscapeChance: 7 },
|
||||||
|
{ pokemonSpeedRatio: 0.25, escapeAttempts: 0, expectedEscapeChance: 11 },
|
||||||
|
{ pokemonSpeedRatio: 0.5, escapeAttempts: 0, expectedEscapeChance: 16 },
|
||||||
|
{ pokemonSpeedRatio: 0.8, escapeAttempts: 0, expectedEscapeChance: 23 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 28 },
|
||||||
|
{ pokemonSpeedRatio: 1.2, escapeAttempts: 0, expectedEscapeChance: 32 },
|
||||||
|
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 39 },
|
||||||
|
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 73 },
|
||||||
|
{ pokemonSpeedRatio: 3.8, escapeAttempts: 0, expectedEscapeChance: 91 },
|
||||||
|
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 4.2, escapeAttempts: 0, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 95 },
|
||||||
|
|
||||||
|
// retries section
|
||||||
|
{ pokemonSpeedRatio: 0.4, escapeAttempts: 1, expectedEscapeChance: 24 },
|
||||||
|
{ pokemonSpeedRatio: 1.6, escapeAttempts: 2, expectedEscapeChance: 61 },
|
||||||
|
{ pokemonSpeedRatio: 3.7, escapeAttempts: 5, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 0.2, escapeAttempts: 2, expectedEscapeChance: 30 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 3, expectedEscapeChance: 58 },
|
||||||
|
{ pokemonSpeedRatio: 2.9, escapeAttempts: 0, expectedEscapeChance: 70 },
|
||||||
|
{ pokemonSpeedRatio: 0.01, escapeAttempts: 7, expectedEscapeChance: 75 },
|
||||||
|
{ pokemonSpeedRatio: 16.2, escapeAttempts: 4, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 80 },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < escapeChances.length; i++) {
|
||||||
|
// sets the number of escape attempts to the required amount
|
||||||
|
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
|
||||||
|
// set playerPokemon's speed to a multiple of the enemySpeed
|
||||||
|
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * enemySpeed]);
|
||||||
|
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
||||||
|
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("double non-boss opponent", async () => {
|
||||||
|
game.override.battleType("double");
|
||||||
|
await game.classicMode.startBattle([Species.BULBASAUR, Species.ABOMASNOW]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerField();
|
||||||
|
const enemyField = game.scene.getEnemyField();
|
||||||
|
const enemyASpeed = 70;
|
||||||
|
const enemyBSpeed = 30;
|
||||||
|
// gets the sum of the speed of the two pokemon
|
||||||
|
const totalEnemySpeed = enemyASpeed + enemyBSpeed;
|
||||||
|
// this is used to find the ratio of the player's first pokemon
|
||||||
|
const playerASpeedPercentage = 0.4;
|
||||||
|
// set enemyAPokemon's speed to 70
|
||||||
|
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
|
||||||
|
// set enemyBPokemon's speed to 30
|
||||||
|
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
|
||||||
|
|
||||||
|
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||||
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
|
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
|
||||||
|
const escapePercentage = new Utils.NumberHolder(0);
|
||||||
|
|
||||||
|
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
|
||||||
|
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
|
||||||
|
{ pokemonSpeedRatio: 0.3, escapeAttempts: 0, expectedEscapeChance: 12 },
|
||||||
|
{ pokemonSpeedRatio: 0.7, escapeAttempts: 0, expectedEscapeChance: 21 },
|
||||||
|
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 39 },
|
||||||
|
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 73 },
|
||||||
|
{ pokemonSpeedRatio: 9, escapeAttempts: 0, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 28 },
|
||||||
|
{ pokemonSpeedRatio: 4.3, escapeAttempts: 0, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 2.7, escapeAttempts: 0, expectedEscapeChance: 66 },
|
||||||
|
{ pokemonSpeedRatio: 2.1, escapeAttempts: 0, expectedEscapeChance: 52 },
|
||||||
|
{ pokemonSpeedRatio: 1.8, escapeAttempts: 0, expectedEscapeChance: 46 },
|
||||||
|
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 95 },
|
||||||
|
|
||||||
|
// retries section
|
||||||
|
{ pokemonSpeedRatio: 0.9, escapeAttempts: 1, expectedEscapeChance: 35 },
|
||||||
|
{ pokemonSpeedRatio: 3.6, escapeAttempts: 2, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 0.03, escapeAttempts: 7, expectedEscapeChance: 76 },
|
||||||
|
{ pokemonSpeedRatio: 0.02, escapeAttempts: 7, expectedEscapeChance: 75 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 5, expectedEscapeChance: 78 },
|
||||||
|
{ pokemonSpeedRatio: 0.7, escapeAttempts: 3, expectedEscapeChance: 51 },
|
||||||
|
{ pokemonSpeedRatio: 2.4, escapeAttempts: 9, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 1.8, escapeAttempts: 7, expectedEscapeChance: 95 },
|
||||||
|
{ pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 95 },
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < escapeChances.length; i++) {
|
||||||
|
// sets the number of escape attempts to the required amount
|
||||||
|
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
|
||||||
|
// set the first playerPokemon's speed to a multiple of the enemySpeed
|
||||||
|
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage)]);
|
||||||
|
// set the second playerPokemon's speed to the remaining value of speed
|
||||||
|
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5]]);
|
||||||
|
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
||||||
|
// checks to make sure the escape values are the same
|
||||||
|
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
||||||
|
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
|
||||||
|
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed);
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("single boss opponent", async () => {
|
||||||
|
game.override.startingWave(10);
|
||||||
|
await game.classicMode.startBattle([Species.BULBASAUR]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerField()!;
|
||||||
|
const enemyField = game.scene.getEnemyField()!;
|
||||||
|
const enemySpeed = 100;
|
||||||
|
// set enemyPokemon's speed to 100
|
||||||
|
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
|
||||||
|
|
||||||
|
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||||
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
|
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
|
||||||
|
const escapePercentage = new Utils.NumberHolder(0);
|
||||||
|
|
||||||
|
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
|
||||||
|
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
|
||||||
|
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
|
||||||
|
{ pokemonSpeedRatio: 0.1, escapeAttempts: 0, expectedEscapeChance: 5 },
|
||||||
|
{ pokemonSpeedRatio: 0.25, escapeAttempts: 0, expectedEscapeChance: 6 },
|
||||||
|
{ pokemonSpeedRatio: 0.5, escapeAttempts: 0, expectedEscapeChance: 7 },
|
||||||
|
{ pokemonSpeedRatio: 0.8, escapeAttempts: 0, expectedEscapeChance: 8 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 8 },
|
||||||
|
{ pokemonSpeedRatio: 1.2, escapeAttempts: 0, expectedEscapeChance: 9 },
|
||||||
|
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 10 },
|
||||||
|
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 15 },
|
||||||
|
{ pokemonSpeedRatio: 3.8, escapeAttempts: 0, expectedEscapeChance: 18 },
|
||||||
|
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 18 },
|
||||||
|
{ pokemonSpeedRatio: 4.2, escapeAttempts: 0, expectedEscapeChance: 19 },
|
||||||
|
{ pokemonSpeedRatio: 4.7, escapeAttempts: 0, expectedEscapeChance: 21 },
|
||||||
|
{ pokemonSpeedRatio: 5, escapeAttempts: 0, expectedEscapeChance: 22 },
|
||||||
|
{ pokemonSpeedRatio: 5.9, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 6.7, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
|
||||||
|
// retries section
|
||||||
|
{ pokemonSpeedRatio: 0.4, escapeAttempts: 1, expectedEscapeChance: 8 },
|
||||||
|
{ pokemonSpeedRatio: 1.6, escapeAttempts: 2, expectedEscapeChance: 14 },
|
||||||
|
{ pokemonSpeedRatio: 3.7, escapeAttempts: 5, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 0.2, escapeAttempts: 2, expectedEscapeChance: 10 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 3, expectedEscapeChance: 14 },
|
||||||
|
{ pokemonSpeedRatio: 2.9, escapeAttempts: 0, expectedEscapeChance: 15 },
|
||||||
|
{ pokemonSpeedRatio: 0.01, escapeAttempts: 7, expectedEscapeChance: 19 },
|
||||||
|
{ pokemonSpeedRatio: 16.2, escapeAttempts: 4, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 18 },
|
||||||
|
{ pokemonSpeedRatio: 4.5, escapeAttempts: 1, expectedEscapeChance: 22 },
|
||||||
|
{ pokemonSpeedRatio: 6.8, escapeAttempts: 6, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 5.2, escapeAttempts: 8, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 4.7, escapeAttempts: 10, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 5.1, escapeAttempts: 1, expectedEscapeChance: 24 },
|
||||||
|
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 5.9, escapeAttempts: 2, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 6.1, escapeAttempts: 3, expectedEscapeChance: 25 },
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < escapeChances.length; i++) {
|
||||||
|
// sets the number of escape attempts to the required amount
|
||||||
|
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
|
||||||
|
// set playerPokemon's speed to a multiple of the enemySpeed
|
||||||
|
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * enemySpeed]);
|
||||||
|
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
||||||
|
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("double boss opponent", async () => {
|
||||||
|
game.override.battleType("double");
|
||||||
|
game.override.startingWave(10);
|
||||||
|
await game.classicMode.startBattle([Species.BULBASAUR, Species.ABOMASNOW]);
|
||||||
|
|
||||||
|
const playerPokemon = game.scene.getPlayerField();
|
||||||
|
const enemyField = game.scene.getEnemyField();
|
||||||
|
const enemyASpeed = 70;
|
||||||
|
const enemyBSpeed = 30;
|
||||||
|
// gets the sum of the speed of the two pokemon
|
||||||
|
const totalEnemySpeed = enemyASpeed + enemyBSpeed;
|
||||||
|
// this is used to find the ratio of the player's first pokemon
|
||||||
|
const playerASpeedPercentage = 0.8;
|
||||||
|
// set enemyAPokemon's speed to 70
|
||||||
|
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
|
||||||
|
// set enemyBPokemon's speed to 30
|
||||||
|
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
|
||||||
|
|
||||||
|
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
|
||||||
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
|
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
|
||||||
|
const escapePercentage = new Utils.NumberHolder(0);
|
||||||
|
|
||||||
|
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
|
||||||
|
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
|
||||||
|
{ pokemonSpeedRatio: 0.3, escapeAttempts: 0, expectedEscapeChance: 6 },
|
||||||
|
{ pokemonSpeedRatio: 0.7, escapeAttempts: 0, expectedEscapeChance: 7 },
|
||||||
|
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 10 },
|
||||||
|
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 15 },
|
||||||
|
{ pokemonSpeedRatio: 9, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 8 },
|
||||||
|
{ pokemonSpeedRatio: 4.3, escapeAttempts: 0, expectedEscapeChance: 19 },
|
||||||
|
{ pokemonSpeedRatio: 2.7, escapeAttempts: 0, expectedEscapeChance: 14 },
|
||||||
|
{ pokemonSpeedRatio: 2.1, escapeAttempts: 0, expectedEscapeChance: 12 },
|
||||||
|
{ pokemonSpeedRatio: 1.8, escapeAttempts: 0, expectedEscapeChance: 11 },
|
||||||
|
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 18 },
|
||||||
|
{ pokemonSpeedRatio: 5.7, escapeAttempts: 0, expectedEscapeChance: 24 },
|
||||||
|
{ pokemonSpeedRatio: 5, escapeAttempts: 0, expectedEscapeChance: 22 },
|
||||||
|
{ pokemonSpeedRatio: 6.1, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 6.8, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 25 },
|
||||||
|
|
||||||
|
// retries section
|
||||||
|
{ pokemonSpeedRatio: 0.9, escapeAttempts: 1, expectedEscapeChance: 10 },
|
||||||
|
{ pokemonSpeedRatio: 3.6, escapeAttempts: 2, expectedEscapeChance: 21 },
|
||||||
|
{ pokemonSpeedRatio: 0.03, escapeAttempts: 7, expectedEscapeChance: 19 },
|
||||||
|
{ pokemonSpeedRatio: 0.02, escapeAttempts: 7, expectedEscapeChance: 19 },
|
||||||
|
{ pokemonSpeedRatio: 1, escapeAttempts: 5, expectedEscapeChance: 18 },
|
||||||
|
{ pokemonSpeedRatio: 0.7, escapeAttempts: 3, expectedEscapeChance: 13 },
|
||||||
|
{ pokemonSpeedRatio: 2.4, escapeAttempts: 9, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 1.8, escapeAttempts: 7, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 3, escapeAttempts: 1, expectedEscapeChance: 17 },
|
||||||
|
{ pokemonSpeedRatio: 4.5, escapeAttempts: 3, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 3.7, escapeAttempts: 1, expectedEscapeChance: 19 },
|
||||||
|
{ pokemonSpeedRatio: 6.5, escapeAttempts: 1, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 12, escapeAttempts: 4, expectedEscapeChance: 25 },
|
||||||
|
{ pokemonSpeedRatio: 5.2, escapeAttempts: 2, expectedEscapeChance: 25 },
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < escapeChances.length; i++) {
|
||||||
|
// sets the number of escape attempts to the required amount
|
||||||
|
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
|
||||||
|
// set the first playerPokemon's speed to a multiple of the enemySpeed
|
||||||
|
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage)]);
|
||||||
|
// set the second playerPokemon's speed to the remaining value of speed
|
||||||
|
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5]]);
|
||||||
|
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
||||||
|
// checks to make sure the escape values are the same
|
||||||
|
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
||||||
|
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
|
||||||
|
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed);
|
||||||
|
}
|
||||||
|
}, 20000);
|
||||||
|
});
|
@ -72,7 +72,7 @@ describe("Items - Dire Hit", () => {
|
|||||||
await game.phaseInterceptor.to(BattleEndPhase);
|
await game.phaseInterceptor.to(BattleEndPhase);
|
||||||
|
|
||||||
const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier) as TempCritBoosterModifier;
|
const modifier = game.scene.findModifier(m => m instanceof TempCritBoosterModifier) as TempCritBoosterModifier;
|
||||||
expect(modifier.getBattlesLeft()).toBe(4);
|
expect(modifier.getBattleCount()).toBe(4);
|
||||||
|
|
||||||
// Forced DIRE_HIT to spawn in the first slot with override
|
// Forced DIRE_HIT to spawn in the first slot with override
|
||||||
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
|
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
|
||||||
@ -90,7 +90,7 @@ describe("Items - Dire Hit", () => {
|
|||||||
for (const m of game.scene.modifiers) {
|
for (const m of game.scene.modifiers) {
|
||||||
if (m instanceof TempCritBoosterModifier) {
|
if (m instanceof TempCritBoosterModifier) {
|
||||||
count++;
|
count++;
|
||||||
expect((m as TempCritBoosterModifier).getBattlesLeft()).toBe(5);
|
expect((m as TempCritBoosterModifier).getBattleCount()).toBe(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(count).toBe(1);
|
expect(count).toBe(1);
|
||||||
|
105
src/test/items/double_battle_chance_booster.test.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { Moves } from "#app/enums/moves.js";
|
||||||
|
import { Species } from "#app/enums/species.js";
|
||||||
|
import { DoubleBattleChanceBoosterModifier } from "#app/modifier/modifier";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
|
import { ShopCursorTarget } from "#app/enums/shop-cursor-target.js";
|
||||||
|
import { Mode } from "#app/ui/ui.js";
|
||||||
|
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler.js";
|
||||||
|
import { Button } from "#app/enums/buttons.js";
|
||||||
|
|
||||||
|
describe("Items - Double Battle Chance Boosters", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should guarantee double battle with 2 unique tiers", async () => {
|
||||||
|
game.override
|
||||||
|
.startingModifier([
|
||||||
|
{ name: "LURE" },
|
||||||
|
{ name: "SUPER_LURE" }
|
||||||
|
])
|
||||||
|
.startingWave(2);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyField().length).toBe(2);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should guarantee double boss battle with 3 unique tiers", async () => {
|
||||||
|
game.override
|
||||||
|
.startingModifier([
|
||||||
|
{ name: "LURE" },
|
||||||
|
{ name: "SUPER_LURE" },
|
||||||
|
{ name: "MAX_LURE" }
|
||||||
|
])
|
||||||
|
.startingWave(10);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
const enemyField = game.scene.getEnemyField();
|
||||||
|
|
||||||
|
expect(enemyField.length).toBe(2);
|
||||||
|
expect(enemyField[0].isBoss()).toBe(true);
|
||||||
|
expect(enemyField[1].isBoss()).toBe(true);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should renew how many battles are left of existing booster when picking up new booster of same tier", async() => {
|
||||||
|
game.override
|
||||||
|
.startingModifier([{ name: "LURE" }])
|
||||||
|
.itemRewards([{ name: "LURE" }])
|
||||||
|
.moveset(SPLASH_ONLY)
|
||||||
|
.startingLevel(200);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([
|
||||||
|
Species.PIKACHU
|
||||||
|
]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
|
await game.doKillOpponents();
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BattleEndPhase");
|
||||||
|
|
||||||
|
const modifier = game.scene.findModifier(m => m instanceof DoubleBattleChanceBoosterModifier) as DoubleBattleChanceBoosterModifier;
|
||||||
|
expect(modifier.getBattleCount()).toBe(9);
|
||||||
|
|
||||||
|
// Forced LURE to spawn in the first slot with override
|
||||||
|
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
|
||||||
|
const handler = game.scene.ui.getHandler() as ModifierSelectUiHandler;
|
||||||
|
// Traverse to first modifier slot
|
||||||
|
handler.setCursor(0);
|
||||||
|
handler.setRowCursor(ShopCursorTarget.REWARDS);
|
||||||
|
handler.processInput(Button.ACTION);
|
||||||
|
}, () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), true);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnInitPhase");
|
||||||
|
|
||||||
|
// Making sure only one booster is in the modifier list even after picking up another
|
||||||
|
let count = 0;
|
||||||
|
for (const m of game.scene.modifiers) {
|
||||||
|
if (m instanceof DoubleBattleChanceBoosterModifier) {
|
||||||
|
count++;
|
||||||
|
const modifierInstance = m as DoubleBattleChanceBoosterModifier;
|
||||||
|
expect(modifierInstance.getBattleCount()).toBe(modifierInstance.getMaxBattles());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(count).toBe(1);
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
@ -10,12 +10,7 @@ import { Abilities } from "#app/enums/abilities";
|
|||||||
import { TempStatStageBoosterModifier } from "#app/modifier/modifier";
|
import { TempStatStageBoosterModifier } from "#app/modifier/modifier";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { Button } from "#app/enums/buttons";
|
import { Button } from "#app/enums/buttons";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
|
||||||
import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
|
||||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||||
import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
|
||||||
import { BattleEndPhase } from "#app/phases/battle-end-phase";
|
|
||||||
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
|
||||||
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +41,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should provide a x1.3 stat stage multiplier", async() => {
|
it("should provide a x1.3 stat stage multiplier", async() => {
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([
|
||||||
Species.PIKACHU
|
Species.PIKACHU
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -56,7 +51,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
|
|
||||||
game.move.select(Moves.TACKLE);
|
game.move.select(Moves.TACKLE);
|
||||||
|
|
||||||
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
|
await game.phaseInterceptor.runFrom("EnemyCommandPhase").to(TurnEndPhase);
|
||||||
|
|
||||||
expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.3);
|
expect(partyMember.getStatStageMultiplier).toHaveReturnedWith(1.3);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
@ -66,7 +61,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }])
|
.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }])
|
||||||
.ability(Abilities.SIMPLE);
|
.ability(Abilities.SIMPLE);
|
||||||
|
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([
|
||||||
Species.PIKACHU
|
Species.PIKACHU
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -89,7 +84,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
|
|
||||||
|
|
||||||
it("should increase existing stat stage multiplier by 3/10 for the rest of the boosters", async() => {
|
it("should increase existing stat stage multiplier by 3/10 for the rest of the boosters", async() => {
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([
|
||||||
Species.PIKACHU
|
Species.PIKACHU
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -113,7 +108,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
it("should not increase past maximum stat stage multiplier", async() => {
|
it("should not increase past maximum stat stage multiplier", async() => {
|
||||||
game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }, { name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
|
game.override.startingModifier([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ACC }, { name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
|
||||||
|
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([
|
||||||
Species.PIKACHU
|
Species.PIKACHU
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -138,7 +133,7 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
.startingLevel(200)
|
.startingLevel(200)
|
||||||
.itemRewards([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
|
.itemRewards([{ name: "TEMP_STAT_STAGE_BOOSTER", type: Stat.ATK }]);
|
||||||
|
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([
|
||||||
Species.PIKACHU
|
Species.PIKACHU
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -146,10 +141,10 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
|
|
||||||
await game.doKillOpponents();
|
await game.doKillOpponents();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BattleEndPhase);
|
await game.phaseInterceptor.to("BattleEndPhase");
|
||||||
|
|
||||||
const modifier = game.scene.findModifier(m => m instanceof TempStatStageBoosterModifier) as TempStatStageBoosterModifier;
|
const modifier = game.scene.findModifier(m => m instanceof TempStatStageBoosterModifier) as TempStatStageBoosterModifier;
|
||||||
expect(modifier.getBattlesLeft()).toBe(4);
|
expect(modifier.getBattleCount()).toBe(4);
|
||||||
|
|
||||||
// Forced X_ATTACK to spawn in the first slot with override
|
// Forced X_ATTACK to spawn in the first slot with override
|
||||||
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
|
game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => {
|
||||||
@ -158,16 +153,17 @@ describe("Items - Temporary Stat Stage Boosters", () => {
|
|||||||
handler.setCursor(0);
|
handler.setCursor(0);
|
||||||
handler.setRowCursor(ShopCursorTarget.REWARDS);
|
handler.setRowCursor(ShopCursorTarget.REWARDS);
|
||||||
handler.processInput(Button.ACTION);
|
handler.processInput(Button.ACTION);
|
||||||
}, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(NewBattlePhase), true);
|
}, () => game.isCurrentPhase("CommandPhase") || game.isCurrentPhase("NewBattlePhase"), true);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnInitPhase);
|
await game.phaseInterceptor.to("TurnInitPhase");
|
||||||
|
|
||||||
// Making sure only one booster is in the modifier list even after picking up another
|
// Making sure only one booster is in the modifier list even after picking up another
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const m of game.scene.modifiers) {
|
for (const m of game.scene.modifiers) {
|
||||||
if (m instanceof TempStatStageBoosterModifier) {
|
if (m instanceof TempStatStageBoosterModifier) {
|
||||||
count++;
|
count++;
|
||||||
expect((m as TempStatStageBoosterModifier).getBattlesLeft()).toBe(5);
|
const modifierInstance = m as TempStatStageBoosterModifier;
|
||||||
|
expect(modifierInstance.getBattleCount()).toBe(modifierInstance.getMaxBattles());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect(count).toBe(1);
|
expect(count).toBe(1);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Stat } from "#enums/stat";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
import GameManager from "#app/test/utils/gameManager";
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
import { Stat } from "#enums/stat";
|
||||||
|
import { SPLASH_ONLY } from "#test/utils/testUtils";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { SPLASH_ONLY } from "../utils/testUtils";
|
|
||||||
|
|
||||||
|
|
||||||
describe("Moves - Baton Pass", () => {
|
describe("Moves - Baton Pass", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -27,20 +27,17 @@ describe("Moves - Baton Pass", () => {
|
|||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.battleType("single")
|
.battleType("single")
|
||||||
.enemySpecies(Species.DUGTRIO)
|
.enemySpecies(Species.MAGIKARP)
|
||||||
.startingLevel(1)
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
.startingWave(97)
|
|
||||||
.moveset([Moves.BATON_PASS, Moves.NASTY_PLOT, Moves.SPLASH])
|
.moveset([Moves.BATON_PASS, Moves.NASTY_PLOT, Moves.SPLASH])
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
.enemyMoveset(SPLASH_ONLY)
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
.disableCrits();
|
.disableCrits();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("transfers all stat stages when player uses it", async() => {
|
it("transfers all stat stages when player uses it", async() => {
|
||||||
// arrange
|
// arrange
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]);
|
||||||
Species.RAICHU,
|
|
||||||
Species.SHUCKLE
|
|
||||||
]);
|
|
||||||
|
|
||||||
// round 1 - buff
|
// round 1 - buff
|
||||||
game.move.select(Moves.NASTY_PLOT);
|
game.move.select(Moves.NASTY_PLOT);
|
||||||
@ -53,7 +50,7 @@ describe("Moves - Baton Pass", () => {
|
|||||||
// round 2 - baton pass
|
// round 2 - baton pass
|
||||||
game.move.select(Moves.BATON_PASS);
|
game.move.select(Moves.BATON_PASS);
|
||||||
game.doSelectPartyPokemon(1);
|
game.doSelectPartyPokemon(1);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
playerPokemon = game.scene.getPlayerPokemon()!;
|
playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
@ -66,10 +63,7 @@ describe("Moves - Baton Pass", () => {
|
|||||||
game.override
|
game.override
|
||||||
.startingWave(5)
|
.startingWave(5)
|
||||||
.enemyMoveset(new Array(4).fill([Moves.NASTY_PLOT]));
|
.enemyMoveset(new Array(4).fill([Moves.NASTY_PLOT]));
|
||||||
await game.startBattle([
|
await game.classicMode.startBattle([Species.RAICHU, Species.SHUCKLE]);
|
||||||
Species.RAICHU,
|
|
||||||
Species.SHUCKLE
|
|
||||||
]);
|
|
||||||
|
|
||||||
// round 1 - ai buffs
|
// round 1 - ai buffs
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
@ -79,7 +73,7 @@ describe("Moves - Baton Pass", () => {
|
|||||||
game.scene.getEnemyPokemon()!.hp = 100;
|
game.scene.getEnemyPokemon()!.hp = 100;
|
||||||
game.override.enemyMoveset(new Array(4).fill(Moves.BATON_PASS));
|
game.override.enemyMoveset(new Array(4).fill(Moves.BATON_PASS));
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
await game.phaseInterceptor.to(PostSummonPhase, false);
|
await game.phaseInterceptor.to("PostSummonPhase", false);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
// check buffs are still there
|
// check buffs are still there
|
||||||
@ -94,4 +88,20 @@ describe("Moves - Baton Pass", () => {
|
|||||||
"PostSummonPhase"
|
"PostSummonPhase"
|
||||||
]);
|
]);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
it("doesn't transfer effects that aren't transferrable", async() => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.SALT_CURE));
|
||||||
|
await game.classicMode.startBattle([Species.PIKACHU, Species.FEEBAS]);
|
||||||
|
|
||||||
|
const [player1, player2] = game.scene.getParty();
|
||||||
|
|
||||||
|
game.move.select(Moves.BATON_PASS);
|
||||||
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase");
|
||||||
|
expect(player1.findTag((t) => t.tagType === BattlerTagType.SALT_CURED)).toBeTruthy();
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(player2.findTag((t) => t.tagType === BattlerTagType.SALT_CURED)).toBeUndefined();
|
||||||
|
}, 20000);
|
||||||
});
|
});
|
||||||
|
@ -110,7 +110,7 @@ describe("Moves - Ceaseless Edge", () => {
|
|||||||
|
|
||||||
const hpBeforeSpikes = game.scene.currentBattle.enemyParty[1].hp;
|
const hpBeforeSpikes = game.scene.currentBattle.enemyParty[1].hp;
|
||||||
// Check HP of pokemon that WILL BE switched in (index 1)
|
// Check HP of pokemon that WILL BE switched in (index 1)
|
||||||
game.forceOpponentToSwitch();
|
game.forceEnemyToSwitch();
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase, false);
|
await game.phaseInterceptor.to(TurnEndPhase, false);
|
||||||
expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(hpBeforeSpikes);
|
expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(hpBeforeSpikes);
|
||||||
|