mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-08 08:29:37 +02:00
Merge branch 'beta' into turn-start-phase
This commit is contained in:
commit
70d49e546d
@ -26,10 +26,11 @@ If you have the motivation and experience with Typescript/Javascript (or are wil
|
|||||||
|
|
||||||
- node: >=22.14.0 - [manage with pnpm](https://pnpm.io/cli/env) | [manage with fnm](https://github.com/Schniz/fnm) | [manage with nvm](https://github.com/nvm-sh/nvm)
|
- node: >=22.14.0 - [manage with pnpm](https://pnpm.io/cli/env) | [manage with fnm](https://github.com/Schniz/fnm) | [manage with nvm](https://github.com/nvm-sh/nvm)
|
||||||
- pnpm: 10.x - [how to install](https://pnpm.io/installation) (not recommended to install via `npm` on Windows native) | [alternate method - volta.sh](https://volta.sh/)
|
- pnpm: 10.x - [how to install](https://pnpm.io/installation) (not recommended to install via `npm` on Windows native) | [alternate method - volta.sh](https://volta.sh/)
|
||||||
|
- The repository [forked](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and [cloned](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) locally on your device
|
||||||
|
|
||||||
### Running Locally
|
### Running Locally
|
||||||
|
|
||||||
1. Clone the repo and in the root directory run `pnpm install`
|
1. Run `pnpm install` from the repository root
|
||||||
- *if you run into any errors, reach out in the **#dev-corner** channel on Discord*
|
- *if you run into any errors, reach out in the **#dev-corner** channel on Discord*
|
||||||
2. Run `pnpm start:dev` to locally run the project at `localhost:8000`
|
2. Run `pnpm start:dev` to locally run the project at `localhost:8000`
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { Graphviz } from "@hpcc-js/wasm/graphviz";
|
|
||||||
|
|
||||||
const graphviz = await Graphviz.load();
|
|
||||||
|
|
||||||
const inputFile = [];
|
|
||||||
for await (const chunk of process.stdin) {
|
|
||||||
inputFile.push(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = Buffer.concat(inputFile).toString("utf-8");
|
|
||||||
|
|
||||||
const svg = graphviz.dot(file, "svg");
|
|
||||||
process.stdout.write(svg);
|
|
@ -21,15 +21,13 @@
|
|||||||
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
|
"biome-ci": "biome ci --diagnostic-level=error --reporter=github --no-errors-on-unmatched",
|
||||||
"docs": "typedoc",
|
"docs": "typedoc",
|
||||||
"depcruise": "depcruise src test",
|
"depcruise": "depcruise src test",
|
||||||
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
|
"postinstall": "lefthook install; git submodule update --init --recursive",
|
||||||
"postinstall": "lefthook install && lefthook run post-merge",
|
|
||||||
"update-version:patch": "pnpm version patch --force --no-git-tag-version",
|
"update-version:patch": "pnpm version patch --force --no-git-tag-version",
|
||||||
"update-version:minor": "pnpm version minor --force --no-git-tag-version",
|
"update-version:minor": "pnpm version minor --force --no-git-tag-version",
|
||||||
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
|
"update-locales:remote": "git submodule update --progress --init --recursive --force --remote"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.0.0",
|
"@biomejs/biome": "2.0.0",
|
||||||
"@hpcc-js/wasm": "^2.22.4",
|
|
||||||
"@types/jsdom": "^21.1.7",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^22.13.14",
|
"@types/node": "^22.13.14",
|
||||||
"@vitest/coverage-istanbul": "^3.0.9",
|
"@vitest/coverage-istanbul": "^3.0.9",
|
||||||
|
@ -45,9 +45,6 @@ importers:
|
|||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 2.0.0
|
specifier: 2.0.0
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
'@hpcc-js/wasm':
|
|
||||||
specifier: ^2.22.4
|
|
||||||
version: 2.22.4
|
|
||||||
'@types/jsdom':
|
'@types/jsdom':
|
||||||
specifier: ^21.1.7
|
specifier: ^21.1.7
|
||||||
version: 21.1.7
|
version: 21.1.7
|
||||||
@ -436,10 +433,6 @@ packages:
|
|||||||
'@gerrit0/mini-shiki@3.2.2':
|
'@gerrit0/mini-shiki@3.2.2':
|
||||||
resolution: {integrity: sha512-vaZNGhGLKMY14HbF53xxHNgFO9Wz+t5lTlGNpl2N9xFiKQ0I5oIe0vKjU9dh7Nb3Dw6lZ7wqUE0ri+zcdpnK+Q==}
|
resolution: {integrity: sha512-vaZNGhGLKMY14HbF53xxHNgFO9Wz+t5lTlGNpl2N9xFiKQ0I5oIe0vKjU9dh7Nb3Dw6lZ7wqUE0ri+zcdpnK+Q==}
|
||||||
|
|
||||||
'@hpcc-js/wasm@2.22.4':
|
|
||||||
resolution: {integrity: sha512-58JkRkxZffiBAbZhc7z+9iaaAOmn1cyxLL3rRwsUvco/I0Wwb7uVAlHM9HiU6XASe2k11jrIjCFff1t9QKjlqg==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
'@inquirer/checkbox@4.1.4':
|
'@inquirer/checkbox@4.1.4':
|
||||||
resolution: {integrity: sha512-d30576EZdApjAMceijXA5jDzRQHT/MygbC+J8I7EqA6f/FRpYxlRtRJbHF8gHeWYeSdOuTEJqonn7QLB1ELezA==}
|
resolution: {integrity: sha512-d30576EZdApjAMceijXA5jDzRQHT/MygbC+J8I7EqA6f/FRpYxlRtRJbHF8gHeWYeSdOuTEJqonn7QLB1ELezA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -2332,10 +2325,6 @@ snapshots:
|
|||||||
'@shikijs/types': 3.2.1
|
'@shikijs/types': 3.2.1
|
||||||
'@shikijs/vscode-textmate': 10.0.2
|
'@shikijs/vscode-textmate': 10.0.2
|
||||||
|
|
||||||
'@hpcc-js/wasm@2.22.4':
|
|
||||||
dependencies:
|
|
||||||
yargs: 17.7.2
|
|
||||||
|
|
||||||
'@inquirer/checkbox@4.1.4(@types/node@22.13.14)':
|
'@inquirer/checkbox@4.1.4(@types/node@22.13.14)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@inquirer/core': 10.1.9(@types/node@22.13.14)
|
'@inquirer/core': 10.1.9(@types/node@22.13.14)
|
||||||
|
6
pnpm-workspace.yaml
Normal file
6
pnpm-workspace.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- msw
|
||||||
|
- lefthook
|
||||||
|
|
||||||
|
shellEmulator: true
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
BIN
public/images/items/berry_juice_bad.png
Normal file
BIN
public/images/items/berry_juice_bad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 327 B |
@ -1 +1 @@
|
|||||||
Subproject commit fade123e20ff951e199d7c0466686fe8c5511643
|
Subproject commit aa94b0b68265c26f728d154998582bb629f2b850
|
@ -797,12 +797,14 @@ export default class BattleScene extends SceneBase {
|
|||||||
// TODO: Add `undefined` to return type
|
// TODO: Add `undefined` to return type
|
||||||
/**
|
/**
|
||||||
* Returns an array of PlayerPokemon of length 1 or 2 depending on if in a double battle or not.
|
* Returns an array of PlayerPokemon of length 1 or 2 depending on if in a double battle or not.
|
||||||
* Does not actually check if the pokemon are on the field or not.
|
* @param active - (Default `false`) Whether to consider only {@linkcode Pokemon.isActive | active} on-field pokemon
|
||||||
* @returns array of {@linkcode PlayerPokemon}
|
* @returns array of {@linkcode PlayerPokemon}
|
||||||
*/
|
*/
|
||||||
public getPlayerField(): PlayerPokemon[] {
|
public getPlayerField(active = false): PlayerPokemon[] {
|
||||||
const party = this.getPlayerParty();
|
const party = this.getPlayerParty();
|
||||||
return party.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1));
|
return party
|
||||||
|
.slice(0, Math.min(party.length, this.currentBattle?.double ? 2 : 1))
|
||||||
|
.filter(p => !active || p.isActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEnemyParty(): EnemyPokemon[] {
|
public getEnemyParty(): EnemyPokemon[] {
|
||||||
|
@ -95,6 +95,12 @@ export default class Battle {
|
|||||||
/** If the current battle is a Mystery Encounter, this will always be defined */
|
/** If the current battle is a Mystery Encounter, this will always be defined */
|
||||||
public mysteryEncounter?: MysteryEncounter;
|
public mysteryEncounter?: MysteryEncounter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracker for whether the last run attempt failed.
|
||||||
|
* @defaultValue `false`
|
||||||
|
*/
|
||||||
|
public failedRunAway = false;
|
||||||
|
|
||||||
private rngCounter = 0;
|
private rngCounter = 0;
|
||||||
|
|
||||||
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double = false) {
|
constructor(gameMode: GameMode, waveIndex: number, battleType: BattleType, trainer?: Trainer, double = false) {
|
||||||
|
@ -7263,11 +7263,14 @@ export function initAbilities() {
|
|||||||
new Ability(AbilityId.MERCILESS, 7)
|
new Ability(AbilityId.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(AbilityId.SHIELDS_DOWN, 7, -1)
|
new Ability(AbilityId.SHIELDS_DOWN, 7, -1)
|
||||||
.attr(PostBattleInitFormChangeAbAttr, () => 0)
|
// Change into Meteor Form on switch-in or turn end if HP >= 50%,
|
||||||
|
// or Core Form if HP <= 50%.
|
||||||
|
.attr(PostBattleInitFormChangeAbAttr, p => p.formIndex % 7)
|
||||||
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
.attr(PostSummonFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
||||||
.attr(PostTurnFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
.attr(PostTurnFormChangeAbAttr, p => p.formIndex % 7 + (p.getHpRatio() <= 0.5 ? 7 : 0))
|
||||||
.conditionalAttr(p => p.formIndex !== 7, StatusEffectImmunityAbAttr)
|
// All variants of Meteor Form are immune to status effects & Yawn
|
||||||
.conditionalAttr(p => p.formIndex !== 7, BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
.conditionalAttr(p => p.formIndex < 7, StatusEffectImmunityAbAttr)
|
||||||
|
.conditionalAttr(p => p.formIndex < 7, BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||||
.attr(NoFusionAbilityAbAttr)
|
.attr(NoFusionAbilityAbAttr)
|
||||||
.attr(NoTransformAbilityAbAttr)
|
.attr(NoTransformAbilityAbAttr)
|
||||||
.uncopiable()
|
.uncopiable()
|
||||||
@ -7333,12 +7336,11 @@ export function initAbilities() {
|
|||||||
.unsuppressable()
|
.unsuppressable()
|
||||||
.bypassFaint(),
|
.bypassFaint(),
|
||||||
new Ability(AbilityId.POWER_CONSTRUCT, 7)
|
new Ability(AbilityId.POWER_CONSTRUCT, 7)
|
||||||
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2)
|
// Change to 10% complete or 50% complete on switchout/turn end if at <50% HP;
|
||||||
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3)
|
// revert to 10% PC or 50% PC before a new battle starts
|
||||||
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
.conditionalAttr(p => p.formIndex === 4 || p.formIndex === 5, PostBattleInitFormChangeAbAttr, p => p.formIndex - 2)
|
||||||
.conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
|
.conditionalAttr(p => p.getHpRatio() <= 0.5 && (p.formIndex === 2 || p.formIndex === 3), PostSummonFormChangeAbAttr, p => p.formIndex + 2)
|
||||||
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
|
.conditionalAttr(p => p.getHpRatio() <= 0.5 && (p.formIndex === 2 || p.formIndex === 3), PostTurnFormChangeAbAttr, p => p.formIndex + 2)
|
||||||
.conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3)
|
|
||||||
.attr(NoFusionAbilityAbAttr)
|
.attr(NoFusionAbilityAbAttr)
|
||||||
.uncopiable()
|
.uncopiable()
|
||||||
.unreplaceable()
|
.unreplaceable()
|
||||||
|
@ -54,7 +54,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
.withFleeAllowed(false)
|
.withFleeAllowed(false)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "berry_juice",
|
spriteKey: "berry_juice_good",
|
||||||
fileRoot: "items",
|
fileRoot: "items",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
isItem: true,
|
isItem: true,
|
||||||
@ -171,11 +171,11 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder
|
|||||||
sortedParty.forEach((pokemon, index) => {
|
sortedParty.forEach((pokemon, index) => {
|
||||||
if (index < 2) {
|
if (index < 2) {
|
||||||
// -15 to the two highest BST mons
|
// -15 to the two highest BST mons
|
||||||
modifyPlayerPokemonBST(pokemon, -HIGH_BST_REDUCTION_VALUE);
|
modifyPlayerPokemonBST(pokemon, false);
|
||||||
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
|
encounter.setDialogueToken("highBstPokemon" + (index + 1), pokemon.getNameToRender());
|
||||||
} else {
|
} else {
|
||||||
// +10 for the rest
|
// +10 for the rest
|
||||||
modifyPlayerPokemonBST(pokemon, BST_INCREASE_VALUE);
|
modifyPlayerPokemonBST(pokemon, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ import {
|
|||||||
TransformationScreenPosition,
|
TransformationScreenPosition,
|
||||||
} from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
} from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||||
import { getLevelTotalExp } from "#app/data/exp";
|
import { getLevelTotalExp } from "#app/data/exp";
|
||||||
import { Stat } from "#enums/stat";
|
|
||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
import { ModifierTier } from "#enums/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
@ -104,8 +103,6 @@ const EXCLUDED_TRANSFORMATION_SPECIES = [
|
|||||||
const SUPER_LEGENDARY_BST_THRESHOLD = 600;
|
const SUPER_LEGENDARY_BST_THRESHOLD = 600;
|
||||||
const NON_LEGENDARY_BST_THRESHOLD = 570;
|
const NON_LEGENDARY_BST_THRESHOLD = 570;
|
||||||
|
|
||||||
const OLD_GATEAU_STATS_UP = 20;
|
|
||||||
|
|
||||||
/** 0-100 */
|
/** 0-100 */
|
||||||
const PERCENT_LEVEL_LOSS_ON_REFUSE = 10;
|
const PERCENT_LEVEL_LOSS_ON_REFUSE = 10;
|
||||||
|
|
||||||
@ -275,12 +272,8 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
|||||||
}
|
}
|
||||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||||
if (shouldGetOldGateau(newPokemon)) {
|
if (shouldGetOldGateau(newPokemon)) {
|
||||||
const stats = getOldGateauBoostedStats(newPokemon);
|
|
||||||
newPokemonHeldItemConfigs.push({
|
newPokemonHeldItemConfigs.push({
|
||||||
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [
|
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU) as PokemonHeldItemModifierType,
|
||||||
OLD_GATEAU_STATS_UP,
|
|
||||||
stats,
|
|
||||||
]) as PokemonHeldItemModifierType,
|
|
||||||
stackCount: 1,
|
stackCount: 1,
|
||||||
isTransferable: false,
|
isTransferable: false,
|
||||||
});
|
});
|
||||||
@ -461,11 +454,7 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
|||||||
}
|
}
|
||||||
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
|
||||||
if (shouldGetOldGateau(newPokemon)) {
|
if (shouldGetOldGateau(newPokemon)) {
|
||||||
const stats = getOldGateauBoostedStats(newPokemon);
|
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU();
|
||||||
const modType = modifierTypes
|
|
||||||
.MYSTERY_ENCOUNTER_OLD_GATEAU()
|
|
||||||
.generateType(globalScene.getPlayerParty(), [OLD_GATEAU_STATS_UP, stats])
|
|
||||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
|
|
||||||
const modifier = modType?.newModifier(newPokemon);
|
const modifier = modType?.newModifier(newPokemon);
|
||||||
if (modifier) {
|
if (modifier) {
|
||||||
globalScene.addModifier(modifier, false, false, false, true);
|
globalScene.addModifier(modifier, false, false, false, true);
|
||||||
@ -616,22 +605,6 @@ function shouldGetOldGateau(pokemon: Pokemon): boolean {
|
|||||||
return pokemon.getSpeciesForm().getBaseStatTotal() < NON_LEGENDARY_BST_THRESHOLD;
|
return pokemon.getSpeciesForm().getBaseStatTotal() < NON_LEGENDARY_BST_THRESHOLD;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
|
||||||
* @returns Array of 3 {@linkcode Stat}s to boost
|
|
||||||
*/
|
|
||||||
function getOldGateauBoostedStats(pokemon: Pokemon): Stat[] {
|
|
||||||
const stats: Stat[] = [];
|
|
||||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
|
||||||
// HP or Speed
|
|
||||||
stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD);
|
|
||||||
// Attack or SpAtk
|
|
||||||
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
|
||||||
// Def or SpDef
|
|
||||||
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTransformedSpecies(
|
function getTransformedSpecies(
|
||||||
originalBst: number,
|
originalBst: number,
|
||||||
bstSearchRange: [number, number],
|
bstSearchRange: [number, number],
|
||||||
|
@ -375,10 +375,10 @@ export function applyHealToPokemon(pokemon: PlayerPokemon, heal: number) {
|
|||||||
* @param pokemon
|
* @param pokemon
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, good: boolean) {
|
||||||
const modType = modifierTypes
|
const modType = modifierTypes
|
||||||
.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
||||||
.generateType(globalScene.getPlayerParty(), [value])
|
.generateType(globalScene.getPlayerParty(), [good ? 10 : -15])
|
||||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
|
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
|
||||||
const modifier = modType?.newModifier(pokemon);
|
const modifier = modType?.newModifier(pokemon);
|
||||||
if (modifier) {
|
if (modifier) {
|
||||||
|
@ -967,31 +967,23 @@ export class PokemonBaseStatTotalModifierType
|
|||||||
extends PokemonHeldItemModifierType
|
extends PokemonHeldItemModifierType
|
||||||
implements GeneratedPersistentModifierType
|
implements GeneratedPersistentModifierType
|
||||||
{
|
{
|
||||||
private readonly statModifier: number;
|
private readonly statModifier: 10 | -15;
|
||||||
|
|
||||||
constructor(statModifier: number) {
|
constructor(statModifier: 10 | -15) {
|
||||||
super(
|
super(
|
||||||
"modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE",
|
statModifier > 0
|
||||||
"berry_juice",
|
? "modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD"
|
||||||
(_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, this.statModifier),
|
: "modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD",
|
||||||
|
statModifier > 0 ? "berry_juice_good" : "berry_juice_bad",
|
||||||
|
(_type, args) => new PokemonBaseStatTotalModifier(this, (args[0] as Pokemon).id, statModifier),
|
||||||
);
|
);
|
||||||
this.statModifier = statModifier;
|
this.statModifier = statModifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getDescription(): string {
|
override getDescription(): string {
|
||||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatTotalModifierType.description", {
|
return this.statModifier > 0
|
||||||
increaseDecrease: i18next.t(
|
? i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_GOOD.description")
|
||||||
this.statModifier >= 0
|
: i18next.t("modifierType:ModifierType.MYSTERY_ENCOUNTER_SHUCKLE_JUICE_BAD.description");
|
||||||
? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.increase"
|
|
||||||
: "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.decrease",
|
|
||||||
),
|
|
||||||
blessCurse: i18next.t(
|
|
||||||
this.statModifier >= 0
|
|
||||||
? "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.blessed"
|
|
||||||
: "modifierType:ModifierType.PokemonBaseStatTotalModifierType.extra.cursed",
|
|
||||||
),
|
|
||||||
statValue: this.statModifier,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPregenArgs(): any[] {
|
public getPregenArgs(): any[] {
|
||||||
@ -999,38 +991,6 @@ export class PokemonBaseStatTotalModifierType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Old Gateau item
|
|
||||||
*/
|
|
||||||
export class PokemonBaseStatFlatModifierType
|
|
||||||
extends PokemonHeldItemModifierType
|
|
||||||
implements GeneratedPersistentModifierType
|
|
||||||
{
|
|
||||||
private readonly statModifier: number;
|
|
||||||
private readonly stats: Stat[];
|
|
||||||
|
|
||||||
constructor(statModifier: number, stats: Stat[]) {
|
|
||||||
super(
|
|
||||||
"modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU",
|
|
||||||
"old_gateau",
|
|
||||||
(_type, args) => new PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats),
|
|
||||||
);
|
|
||||||
this.statModifier = statModifier;
|
|
||||||
this.stats = stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
override getDescription(): string {
|
|
||||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", {
|
|
||||||
stats: this.stats.map(stat => i18next.t(getStatKey(stat))).join("/"),
|
|
||||||
statValue: this.statModifier,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPregenArgs(): any[] {
|
|
||||||
return [this.statModifier, this.stats];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AllPokemonFullHpRestoreModifierType extends ModifierType {
|
class AllPokemonFullHpRestoreModifierType extends ModifierType {
|
||||||
private descriptionKey: string;
|
private descriptionKey: string;
|
||||||
|
|
||||||
@ -2331,17 +2291,16 @@ const modifierTypeInitObj = Object.freeze({
|
|||||||
MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () =>
|
MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () =>
|
||||||
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||||
if (pregenArgs) {
|
if (pregenArgs) {
|
||||||
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number);
|
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as 10 | -15);
|
||||||
}
|
}
|
||||||
return new PokemonBaseStatTotalModifierType(randSeedInt(20, 1));
|
return new PokemonBaseStatTotalModifierType(10);
|
||||||
}),
|
}),
|
||||||
MYSTERY_ENCOUNTER_OLD_GATEAU: () =>
|
MYSTERY_ENCOUNTER_OLD_GATEAU: () =>
|
||||||
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
new PokemonHeldItemModifierType(
|
||||||
if (pregenArgs) {
|
"modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU",
|
||||||
return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]);
|
"old_gateau",
|
||||||
}
|
(type, args) => new PokemonBaseStatFlatModifier(type, (args[0] as Pokemon).id),
|
||||||
return new PokemonBaseStatFlatModifierType(randSeedInt(20, 1), [Stat.HP, Stat.ATK, Stat.DEF]);
|
),
|
||||||
}),
|
|
||||||
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () =>
|
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () =>
|
||||||
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {
|
||||||
if (pregenArgs) {
|
if (pregenArgs) {
|
||||||
|
@ -952,10 +952,9 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
|
|||||||
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||||
public override type: PokemonBaseStatTotalModifierType;
|
public override type: PokemonBaseStatTotalModifierType;
|
||||||
public isTransferable = false;
|
public isTransferable = false;
|
||||||
|
public statModifier: 10 | -15;
|
||||||
|
|
||||||
private statModifier: number;
|
constructor(type: PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: 10 | -15, stackCount?: number) {
|
||||||
|
|
||||||
constructor(type: PokemonBaseStatTotalModifierType, pokemonId: number, statModifier: number, stackCount?: number) {
|
|
||||||
super(type, pokemonId, stackCount);
|
super(type, pokemonId, stackCount);
|
||||||
this.statModifier = statModifier;
|
this.statModifier = statModifier;
|
||||||
}
|
}
|
||||||
@ -1012,31 +1011,14 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
|||||||
* Currently used by Old Gateau item
|
* Currently used by Old Gateau item
|
||||||
*/
|
*/
|
||||||
export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||||
private statModifier: number;
|
|
||||||
private stats: Stat[];
|
|
||||||
public isTransferable = false;
|
public isTransferable = false;
|
||||||
|
|
||||||
constructor(type: ModifierType, pokemonId: number, statModifier: number, stats: Stat[], stackCount?: number) {
|
|
||||||
super(type, pokemonId, stackCount);
|
|
||||||
|
|
||||||
this.statModifier = statModifier;
|
|
||||||
this.stats = stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
override matchType(modifier: Modifier): boolean {
|
override matchType(modifier: Modifier): boolean {
|
||||||
return (
|
return modifier instanceof PokemonBaseStatFlatModifier;
|
||||||
modifier instanceof PokemonBaseStatFlatModifier &&
|
|
||||||
modifier.statModifier === this.statModifier &&
|
|
||||||
this.stats.every(s => modifier.stats.some(stat => s === stat))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override clone(): PersistentModifier {
|
override clone(): PersistentModifier {
|
||||||
return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.statModifier, this.stats, this.stackCount);
|
return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.stackCount);
|
||||||
}
|
|
||||||
|
|
||||||
override getArgs(): any[] {
|
|
||||||
return [...super.getArgs(), this.statModifier, this.stats];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1055,11 +1037,13 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
|||||||
* @param baseStats The base stats of the {@linkcode Pokemon}
|
* @param baseStats The base stats of the {@linkcode Pokemon}
|
||||||
* @returns always `true`
|
* @returns always `true`
|
||||||
*/
|
*/
|
||||||
override apply(_pokemon: Pokemon, baseStats: number[]): boolean {
|
override apply(pokemon: Pokemon, baseStats: number[]): boolean {
|
||||||
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
||||||
|
const stats = this.getStats(pokemon);
|
||||||
|
const statModifier = 20;
|
||||||
baseStats.forEach((v, i) => {
|
baseStats.forEach((v, i) => {
|
||||||
if (this.stats.includes(i)) {
|
if (stats.includes(i)) {
|
||||||
const newVal = Math.floor(v + this.statModifier);
|
const newVal = Math.floor(v + statModifier);
|
||||||
baseStats[i] = Math.min(Math.max(newVal, 1), 999999);
|
baseStats[i] = Math.min(Math.max(newVal, 1), 999999);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1067,6 +1051,22 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||||
|
* @returns Array of 3 {@linkcode Stat}s to boost
|
||||||
|
*/
|
||||||
|
getStats(pokemon: Pokemon): Stat[] {
|
||||||
|
const stats: Stat[] = [];
|
||||||
|
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||||
|
// HP or Speed
|
||||||
|
stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD);
|
||||||
|
// Attack or SpAtk
|
||||||
|
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
||||||
|
// Def or SpDef
|
||||||
|
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
override getScoreMultiplier(): number {
|
override getScoreMultiplier(): number {
|
||||||
return 1.1;
|
return 1.1;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,13 @@ class DefaultOverrides {
|
|||||||
* or `false` to force it to never trigger.
|
* or `false` to force it to never trigger.
|
||||||
*/
|
*/
|
||||||
readonly CONFUSION_ACTIVATION_OVERRIDE: boolean | null = null;
|
readonly CONFUSION_ACTIVATION_OVERRIDE: boolean | null = null;
|
||||||
|
/**
|
||||||
|
* If non-null, will override random flee attempts to always or never succeed by forcing {@linkcode calculateEscapeChance} to return 100% or 0%.
|
||||||
|
* Set to `null` to disable.
|
||||||
|
*
|
||||||
|
* Is overridden if either player Pokemon has {@linkcode AbilityId.RUN_AWAY | Run Away}.
|
||||||
|
*/
|
||||||
|
readonly RUN_SUCCESS_OVERRIDE: boolean | null = null;
|
||||||
// ----------------
|
// ----------------
|
||||||
// PLAYER OVERRIDES
|
// PLAYER OVERRIDES
|
||||||
// ----------------
|
// ----------------
|
||||||
|
@ -1,34 +1,33 @@
|
|||||||
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import Overrides from "#app/overrides";
|
||||||
|
import { FieldPhase } from "#app/phases/field-phase";
|
||||||
|
import { NumberHolder } from "#app/utils/common";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { StatusEffect } from "#enums/status-effect";
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
|
||||||
import type Pokemon from "#app/field/pokemon";
|
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { NumberHolder } from "#app/utils/common";
|
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
|
||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
|
|
||||||
export class AttemptRunPhase extends PokemonPhase {
|
export class AttemptRunPhase extends FieldPhase {
|
||||||
public readonly phaseName = "AttemptRunPhase";
|
public readonly phaseName = "AttemptRunPhase";
|
||||||
/** For testing purposes: this is to force the pokemon to fail and escape */
|
|
||||||
public forceFailEscape = false;
|
|
||||||
|
|
||||||
start() {
|
public start() {
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
const playerField = globalScene.getPlayerField();
|
// Increment escape attempts count on entry
|
||||||
|
const currentAttempts = globalScene.currentBattle.escapeAttempts++;
|
||||||
|
|
||||||
|
const activePlayerField = globalScene.getPlayerField(true);
|
||||||
const enemyField = globalScene.getEnemyField();
|
const enemyField = globalScene.getEnemyField();
|
||||||
|
|
||||||
const playerPokemon = this.getPokemon();
|
const escapeRoll = globalScene.randBattleSeedInt(100);
|
||||||
|
const escapeChance = new NumberHolder(this.calculateEscapeChance(currentAttempts));
|
||||||
|
|
||||||
const escapeChance = new NumberHolder(0);
|
activePlayerField.forEach(pokemon => {
|
||||||
|
applyAbAttrs("RunSuccessAbAttr", { pokemon, chance: escapeChance });
|
||||||
|
});
|
||||||
|
|
||||||
this.attemptRunAway(playerField, enemyField, escapeChance);
|
if (escapeRoll < escapeChance.value) {
|
||||||
|
enemyField.forEach(pokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon }));
|
||||||
applyAbAttrs("RunSuccessAbAttr", { pokemon: playerPokemon, chance: escapeChance });
|
|
||||||
|
|
||||||
if (playerPokemon.randBattleSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
|
|
||||||
enemyField.forEach(enemyPokemon => applyAbAttrs("PreLeaveFieldAbAttr", { pokemon: enemyPokemon }));
|
|
||||||
|
|
||||||
globalScene.playSound("se/flee");
|
globalScene.playSound("se/flee");
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||||
@ -57,24 +56,35 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
|
|
||||||
globalScene.phaseManager.pushNew("NewBattlePhase");
|
globalScene.phaseManager.pushNew("NewBattlePhase");
|
||||||
} else {
|
} else {
|
||||||
playerPokemon.turnData.failedRunAway = true;
|
activePlayerField.forEach(p => {
|
||||||
|
p.turnData.failedRunAway = true;
|
||||||
|
});
|
||||||
|
|
||||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
|
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: NumberHolder) {
|
/**
|
||||||
/** Sum of the speed of all enemy pokemon on the field */
|
* Calculate the chance for the player's team to successfully run away from battle.
|
||||||
const enemySpeed = enemyField.reduce(
|
*
|
||||||
(total: number, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD),
|
* @param escapeAttempts - The number of prior failed escape attempts in the current battle
|
||||||
0,
|
* @returns The final escape chance, as percentage out of 100.
|
||||||
);
|
*/
|
||||||
/** Sum of the speed of all player pokemon on the field */
|
public calculateEscapeChance(escapeAttempts: number): number {
|
||||||
const playerSpeed = playerField.reduce(
|
// Check for override, guaranteeing or forbidding random flee attempts as applicable.
|
||||||
(total: number, playerPokemon: Pokemon) => total + playerPokemon.getStat(Stat.SPD),
|
if (Overrides.RUN_SUCCESS_OVERRIDE !== null) {
|
||||||
0,
|
return Overrides.RUN_SUCCESS_OVERRIDE ? 100 : 0;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
const enemyField = globalScene.getEnemyField();
|
||||||
|
const activePlayerField = globalScene.getPlayerField(true);
|
||||||
|
|
||||||
|
// Cf https://bulbapedia.bulbagarden.net/wiki/Escape#Generation_V_onwards
|
||||||
|
// From gen 5 onwards, running takes the _base_ speed totals of both party sides.
|
||||||
|
const enemySpeed = enemyField.reduce((total, enemy) => total + enemy.getStat(Stat.SPD), 0);
|
||||||
|
const playerSpeed = activePlayerField.reduce((total, player) => total + player.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.
|
/* 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.
|
* 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.
|
||||||
@ -92,10 +102,8 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
* From the above, we can calculate the below values
|
* From the above, we can calculate the below values
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let isBoss = false;
|
/** Whether at least 1 pokemon on the enemy field is a boss. */
|
||||||
for (let e = 0; e < enemyField.length; e++) {
|
const isBoss = enemyField.some(e => e.isBoss());
|
||||||
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 */
|
/** The ratio between the speed of your active pokemon and the speed of the enemy field */
|
||||||
const speedRatio = playerSpeed / enemySpeed;
|
const speedRatio = playerSpeed / enemySpeed;
|
||||||
@ -111,8 +119,8 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||||||
const escapeSlope = (maxChance - minChance) / speedCap;
|
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`]
|
// 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(
|
return Phaser.Math.Clamp(
|
||||||
Math.round(escapeSlope * speedRatio + minChance + escapeBonus * globalScene.currentBattle.escapeAttempts++),
|
Math.round(escapeSlope * speedRatio + minChance + escapeBonus * escapeAttempts),
|
||||||
minChance,
|
minChance,
|
||||||
maxChance,
|
maxChance,
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
|
||||||
import { Stat } from "#app/enums/stat";
|
import { Stat } from "#app/enums/stat";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
@ -207,22 +206,7 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Command.RUN:
|
case Command.RUN:
|
||||||
{
|
globalScene.phaseManager.unshiftNew("AttemptRunPhase");
|
||||||
const playerActivePokemon = globalScene.getPokemonAllowedInBattle();
|
|
||||||
if (!globalScene.currentBattle.double || playerActivePokemon.length === 1) {
|
|
||||||
// If not in doubles, attempt to run with the currently active Pokemon.
|
|
||||||
globalScene.phaseManager.unshiftNew("AttemptRunPhase", pokemon.getFieldIndex());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the fastest first pokemon we find with Run Away, or else the faster of the 2 player pokemon.
|
|
||||||
// This intentionally does not check for Trick Room.
|
|
||||||
// TODO: This phase should not take a pokemon at all
|
|
||||||
const sortedPkmn = playerActivePokemon.sort((p1, p2) => p1.getStat(Stat.SPD) - p2.getStat(Stat.SPD));
|
|
||||||
const runningPokemon = sortedPkmn.find(p => p.hasAbility(AbilityId.RUN_AWAY)) ?? sortedPkmn[0];
|
|
||||||
|
|
||||||
globalScene.phaseManager.unshiftNew("AttemptRunPhase", runningPokemon.getFieldIndex());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { SpeciesId } from "#enums/species-id";
|
|||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
describe("Abilities - Desolate Land", () => {
|
describe("Abilities - Desolate Land", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -145,6 +146,7 @@ describe("Abilities - Desolate Land", () => {
|
|||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||||
|
|
||||||
vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0);
|
vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0);
|
||||||
|
vi.spyOn(globalScene, "randBattleSeedInt").mockReturnValue(0);
|
||||||
|
|
||||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||||
commandPhase.handleCommand(Command.RUN, 0);
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
@ -45,7 +45,7 @@ describe("Abilities - Good As Gold", () => {
|
|||||||
|
|
||||||
const player = game.scene.getPlayerPokemon()!;
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH, 0);
|
game.move.select(MoveId.SPLASH);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
@ -54,12 +54,13 @@ describe("Abilities - Good As Gold", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should block memento and prevent the user from fainting", async () => {
|
it("should block memento and prevent the user from fainting", async () => {
|
||||||
game.override.enemyMoveset([MoveId.MEMENTO]);
|
game.override.enemyAbility(AbilityId.GOOD_AS_GOLD);
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||||
game.move.select(MoveId.MEMENTO);
|
|
||||||
|
game.move.use(MoveId.MEMENTO);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
expect(game.scene.getPlayerPokemon()!.isFainted()).toBe(false);
|
expect(game.field.getPlayerPokemon().isFainted()).toBe(false);
|
||||||
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(0);
|
expect(game.field.getEnemyPokemon().getStatStage(Stat.ATK)).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not block any status moves that target the field, one side, or all pokemon", async () => {
|
it("should not block any status moves that target the field, one side, or all pokemon", async () => {
|
||||||
|
@ -6,6 +6,7 @@ import { SpeciesId } from "#enums/species-id";
|
|||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import Overrides from "#app/overrides";
|
||||||
|
|
||||||
describe("Abilities - Honey Gather", () => {
|
describe("Abilities - Honey Gather", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -63,6 +64,8 @@ describe("Abilities - Honey Gather", () => {
|
|||||||
// something weird is going on with the test framework, so this is required to prevent a crash
|
// something weird is going on with the test framework, so this is required to prevent a crash
|
||||||
const enemy = game.scene.getEnemyPokemon()!;
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
vi.spyOn(enemy, "scene", "get").mockReturnValue(game.scene);
|
vi.spyOn(enemy, "scene", "get").mockReturnValue(game.scene);
|
||||||
|
// Expects next wave so run must succeed
|
||||||
|
vi.spyOn(Overrides, "RUN_SUCCESS_OVERRIDE", "get").mockReturnValue(true);
|
||||||
|
|
||||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||||
commandPhase.handleCommand(Command.RUN, 0);
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
@ -259,7 +259,7 @@ describe("Abilities - Ice Face", () => {
|
|||||||
|
|
||||||
const eiscue = game.scene.getEnemyPokemon()!;
|
const eiscue = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
|
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined();
|
||||||
expect(eiscue.formIndex).toBe(icefaceForm);
|
expect(eiscue.formIndex).toBe(icefaceForm);
|
||||||
expect(eiscue.hasAbility(AbilityId.ICE_FACE)).toBe(true);
|
expect(eiscue.hasAbility(AbilityId.ICE_FACE)).toBe(true);
|
||||||
});
|
});
|
||||||
@ -269,13 +269,9 @@ describe("Abilities - Ice Face", () => {
|
|||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
game.move.select(MoveId.SIMPLE_BEAM);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnInitPhase);
|
|
||||||
|
|
||||||
const eiscue = game.scene.getEnemyPokemon()!;
|
const eiscue = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
|
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).toBeDefined();
|
||||||
expect(eiscue.formIndex).toBe(icefaceForm);
|
expect(eiscue.formIndex).toBe(icefaceForm);
|
||||||
expect(game.scene.getPlayerPokemon()!.hasAbility(AbilityId.TRACE)).toBe(true);
|
expect(game.scene.getPlayerPokemon()!.hasAbility(AbilityId.TRACE)).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -74,9 +74,8 @@ describe("Abilities - Imposter", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: this doesn't actually test imposter - transforming happens before poewr split
|
||||||
it("should copy in-battle overridden stats", async () => {
|
it("should copy in-battle overridden stats", async () => {
|
||||||
game.override.enemyMoveset([MoveId.POWER_SPLIT]);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||||
|
|
||||||
const player = game.scene.getPlayerPokemon()!;
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
@ -85,7 +84,8 @@ describe("Abilities - Imposter", () => {
|
|||||||
const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2);
|
const avgAtk = Math.floor((player.getStat(Stat.ATK, false) + enemy.getStat(Stat.ATK, false)) / 2);
|
||||||
const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2);
|
const avgSpAtk = Math.floor((player.getStat(Stat.SPATK, false) + enemy.getStat(Stat.SPATK, false)) / 2);
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
game.move.use(MoveId.SPLASH);
|
||||||
|
await game.move.forceEnemyMove(MoveId.POWER_SPLIT);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
expect(player.getStat(Stat.ATK, false)).toBe(avgAtk);
|
expect(player.getStat(Stat.ATK, false)).toBe(avgAtk);
|
||||||
@ -101,9 +101,6 @@ describe("Abilities - Imposter", () => {
|
|||||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||||
const player = game.scene.getPlayerPokemon()!;
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
|
|
||||||
player.getMoveset().forEach(move => {
|
player.getMoveset().forEach(move => {
|
||||||
// Should set correct maximum PP without touching `ppUp`
|
// Should set correct maximum PP without touching `ppUp`
|
||||||
if (move) {
|
if (move) {
|
||||||
@ -122,15 +119,10 @@ describe("Abilities - Imposter", () => {
|
|||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE);
|
|
||||||
await game.phaseInterceptor.to("MoveEndPhase");
|
|
||||||
|
|
||||||
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
expect(game.scene.getEnemyPokemon()?.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should persist transformed attributes across reloads", async () => {
|
it("should persist transformed attributes across reloads", async () => {
|
||||||
game.override.moveset([MoveId.ABSORB]);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||||
|
|
||||||
const player = game.scene.getPlayerPokemon()!;
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
@ -162,7 +154,7 @@ describe("Abilities - Imposter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should stay transformed with the correct form after reload", async () => {
|
it("should stay transformed with the correct form after reload", async () => {
|
||||||
game.override.moveset([MoveId.ABSORB]).enemySpecies(SpeciesId.UNOWN);
|
game.override.enemySpecies(SpeciesId.UNOWN);
|
||||||
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
await game.classicMode.startBattle([SpeciesId.DITTO]);
|
||||||
|
|
||||||
const enemy = game.scene.getEnemyPokemon()!;
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
@ -3,7 +3,6 @@ import Phaser from "phaser";
|
|||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { getMovePosition } from "#test/testUtils/gameManagerUtils";
|
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
@ -114,7 +113,7 @@ describe("Abilities - Intimidate", () => {
|
|||||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
|
||||||
game.move.select(getMovePosition(game.scene, 0, MoveId.SPLASH));
|
game.move.select(MoveId.SPLASH);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
enemyPokemon = game.scene.getEnemyPokemon()!;
|
enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
@ -36,10 +36,8 @@ describe("Abilities - Lightningrod", () => {
|
|||||||
it("should redirect electric type moves", async () => {
|
it("should redirect electric type moves", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||||
|
|
||||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
|
||||||
|
|
||||||
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||||
@ -52,10 +50,8 @@ describe("Abilities - Lightningrod", () => {
|
|||||||
game.override.moveset([MoveId.SPLASH, MoveId.AERIAL_ACE]);
|
game.override.moveset([MoveId.SPLASH, MoveId.AERIAL_ACE]);
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||||
|
|
||||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
|
||||||
|
|
||||||
game.move.select(MoveId.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||||
@ -68,8 +64,7 @@ describe("Abilities - Lightningrod", () => {
|
|||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
const enemy2 = game.scene.getEnemyField()[1];
|
||||||
|
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
|
||||||
|
|
||||||
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||||
@ -81,31 +76,25 @@ describe("Abilities - Lightningrod", () => {
|
|||||||
|
|
||||||
it("should not redirect moves changed from electric type via ability", async () => {
|
it("should not redirect moves changed from electric type via ability", async () => {
|
||||||
game.override.ability(AbilityId.NORMALIZE);
|
game.override.ability(AbilityId.NORMALIZE);
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||||
|
|
||||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
|
||||||
|
|
||||||
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.SHOCK_WAVE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemy1.isFullHp()).toBe(false);
|
expect(enemy1.isFullHp()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should redirect moves changed to electric type via ability", async () => {
|
it("should redirect moves changed to electric type via ability", async () => {
|
||||||
game.override.ability(AbilityId.GALVANIZE).moveset(MoveId.TACKLE);
|
game.override.ability(AbilityId.GALVANIZE);
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
game.field.mockAbility(enemy2, AbilityId.LIGHTNING_ROD);
|
||||||
|
|
||||||
enemy2.summonData.ability = AbilityId.LIGHTNING_ROD;
|
game.move.use(MoveId.TACKLE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
|
|
||||||
game.move.select(MoveId.TACKLE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemy1.isFullHp()).toBe(true);
|
expect(enemy1.isFullHp()).toBe(true);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { BattlerIndex } from "#enums/battler-index";
|
|
||||||
import { globalScene } from "#app/global-scene";
|
|
||||||
import { AbilityId } from "#enums/ability-id";
|
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
@ -24,29 +22,28 @@ describe("Abilities - Mold Breaker", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.moveset([MoveId.SPLASH])
|
|
||||||
.ability(AbilityId.MOLD_BREAKER)
|
.ability(AbilityId.MOLD_BREAKER)
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.criticalHits(false)
|
.criticalHits(false)
|
||||||
.enemySpecies(SpeciesId.MAGIKARP)
|
.enemySpecies(SpeciesId.MAGIKARP)
|
||||||
.enemyAbility(AbilityId.BALL_FETCH)
|
.enemyAbility(AbilityId.STURDY)
|
||||||
.enemyMoveset(MoveId.SPLASH);
|
.enemyMoveset(MoveId.SPLASH);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should turn off the ignore abilities arena variable after the user's move", async () => {
|
it("should turn off the ignore abilities arena variable after the user's move", async () => {
|
||||||
game.override
|
await game.classicMode.startBattle([SpeciesId.PINSIR]);
|
||||||
.enemyMoveset(MoveId.SPLASH)
|
|
||||||
.ability(AbilityId.MOLD_BREAKER)
|
|
||||||
.moveset([MoveId.ERUPTION])
|
|
||||||
.startingLevel(100)
|
|
||||||
.enemyLevel(2);
|
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
|
||||||
const enemy = game.scene.getEnemyPokemon()!;
|
|
||||||
|
|
||||||
expect(enemy.isFainted()).toBe(false);
|
const player = game.field.getPlayerPokemon();
|
||||||
game.move.select(MoveId.SPLASH);
|
const enemy = game.field.getEnemyPokemon();
|
||||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
|
||||||
await game.phaseInterceptor.to("MoveEndPhase", true);
|
game.move.use(MoveId.X_SCISSOR);
|
||||||
expect(globalScene.arena.ignoreAbilities).toBe(false);
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
|
expect(game.scene.arena.ignoreAbilities).toBe(true);
|
||||||
|
expect(game.scene.arena.ignoringEffectSource).toBe(player.getBattlerIndex());
|
||||||
|
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
expect(game.scene.arena.ignoreAbilities).toBe(false);
|
||||||
|
expect(enemy.isFainted()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -65,8 +65,7 @@ describe("Abilities - Moxie", () => {
|
|||||||
|
|
||||||
secondPokemon.hp = 1;
|
secondPokemon.hp = 1;
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse, BattlerIndex.PLAYER_2);
|
||||||
game.selectTarget(BattlerIndex.PLAYER_2);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import { Stat } from "#enums/stat";
|
|||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { globalScene } from "#app/global-scene";
|
||||||
|
|
||||||
describe("Abilities - Neutralizing Gas", () => {
|
describe("Abilities - Neutralizing Gas", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -164,6 +165,7 @@ describe("Abilities - Neutralizing Gas", () => {
|
|||||||
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();
|
expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined();
|
||||||
|
|
||||||
vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0);
|
vi.spyOn(game.scene.getPlayerPokemon()!, "randBattleSeedInt").mockReturnValue(0);
|
||||||
|
vi.spyOn(globalScene, "randBattleSeedInt").mockReturnValue(0);
|
||||||
|
|
||||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||||
commandPhase.handleCommand(Command.RUN, 0);
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
|
@ -28,11 +28,11 @@ describe("Abilities - Screen Cleaner", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("removes Aurora Veil", async () => {
|
it("removes Aurora Veil", async () => {
|
||||||
game.override.moveset([MoveId.HAIL]).enemyMoveset(MoveId.AURORA_VEIL);
|
game.override.enemyMoveset(MoveId.AURORA_VEIL);
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
game.move.select(MoveId.HAIL);
|
game.move.use(MoveId.HAIL);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
expect(game.scene.arena.getTag(ArenaTagType.AURORA_VEIL)).toBeDefined();
|
expect(game.scene.arena.getTag(ArenaTagType.AURORA_VEIL)).toBeDefined();
|
||||||
@ -49,7 +49,7 @@ describe("Abilities - Screen Cleaner", () => {
|
|||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
expect(game.scene.arena.getTag(ArenaTagType.LIGHT_SCREEN)).toBeDefined();
|
expect(game.scene.arena.getTag(ArenaTagType.LIGHT_SCREEN)).toBeDefined();
|
||||||
@ -66,7 +66,7 @@ describe("Abilities - Screen Cleaner", () => {
|
|||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.MAGIKARP, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
expect(game.scene.arena.getTag(ArenaTagType.REFLECT)).toBeDefined();
|
expect(game.scene.arena.getTag(ArenaTagType.REFLECT)).toBeDefined();
|
||||||
|
@ -4,10 +4,11 @@ import { MoveId } from "#enums/move-id";
|
|||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { CommandPhase } from "#app/phases/command-phase";
|
import type { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { Command } from "#enums/command";
|
import { Command } from "#enums/command";
|
||||||
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||||
|
import Overrides from "#app/overrides";
|
||||||
|
|
||||||
describe("Abilities - Speed Boost", () => {
|
describe("Abilities - Speed Boost", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -96,12 +97,15 @@ describe("Abilities - Speed Boost", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not trigger if pokemon fails to escape", async () => {
|
it("should not trigger if pokemon fails to escape", async () => {
|
||||||
|
//Account for doubles, should not trigger on either pokemon
|
||||||
|
game.override.battleStyle("double");
|
||||||
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
||||||
|
|
||||||
|
vi.spyOn(Overrides, "RUN_SUCCESS_OVERRIDE", "get").mockReturnValue(false);
|
||||||
|
|
||||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||||
commandPhase.handleCommand(Command.RUN, 0);
|
commandPhase.handleCommand(Command.RUN, 0);
|
||||||
const runPhase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
|
||||||
runPhase.forceFailEscape = true;
|
|
||||||
await game.phaseInterceptor.to(AttemptRunPhase);
|
await game.phaseInterceptor.to(AttemptRunPhase);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import GameManager from "#test/testUtils/gameManager";
|
|||||||
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";
|
||||||
|
|
||||||
|
// TODO: Condense with lightning rod tests sometime
|
||||||
describe("Abilities - Storm Drain", () => {
|
describe("Abilities - Storm Drain", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
let game: GameManager;
|
let game: GameManager;
|
||||||
@ -39,7 +40,7 @@ describe("Abilities - Storm Drain", () => {
|
|||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const enemy1 = game.scene.getEnemyField()[0];
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
const enemy2 = game.scene.getEnemyField()[1];
|
||||||
|
|
||||||
enemy2.summonData.ability = AbilityId.STORM_DRAIN;
|
game.field.mockAbility(enemy2, AbilityId.STORM_DRAIN);
|
||||||
|
|
||||||
game.move.select(MoveId.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||||
@ -55,7 +56,7 @@ describe("Abilities - Storm Drain", () => {
|
|||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const enemy1 = game.scene.getEnemyField()[0];
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
const enemy2 = game.scene.getEnemyField()[1];
|
||||||
|
|
||||||
enemy2.summonData.ability = AbilityId.STORM_DRAIN;
|
game.field.mockAbility(enemy2, AbilityId.STORM_DRAIN);
|
||||||
|
|
||||||
game.move.select(MoveId.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.AERIAL_ACE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||||
@ -68,8 +69,7 @@ describe("Abilities - Storm Drain", () => {
|
|||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
||||||
|
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
const enemy2 = game.scene.getEnemyField()[1];
|
||||||
|
game.field.mockAbility(enemy2, AbilityId.STORM_DRAIN);
|
||||||
enemy2.summonData.ability = AbilityId.STORM_DRAIN;
|
|
||||||
|
|
||||||
game.move.select(MoveId.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||||
@ -85,8 +85,7 @@ describe("Abilities - Storm Drain", () => {
|
|||||||
|
|
||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const enemy1 = game.scene.getEnemyField()[0];
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
const enemy2 = game.scene.getEnemyField()[1];
|
||||||
|
game.field.mockAbility(enemy2, AbilityId.STORM_DRAIN);
|
||||||
enemy2.summonData.ability = AbilityId.STORM_DRAIN;
|
|
||||||
|
|
||||||
game.move.select(MoveId.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.select(MoveId.WATER_GUN, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
||||||
@ -96,16 +95,15 @@ describe("Abilities - Storm Drain", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should redirect moves changed to water type via ability", async () => {
|
it("should redirect moves changed to water type via ability", async () => {
|
||||||
game.override.ability(AbilityId.LIQUID_VOICE).moveset(MoveId.PSYCHIC_NOISE);
|
game.override.ability(AbilityId.LIQUID_VOICE);
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS, SpeciesId.MAGIKARP]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
const enemy1 = game.scene.getEnemyField()[0];
|
const enemy1 = game.scene.getEnemyField()[0];
|
||||||
const enemy2 = game.scene.getEnemyField()[1];
|
const enemy2 = game.scene.getEnemyField()[1];
|
||||||
|
|
||||||
enemy2.summonData.ability = AbilityId.STORM_DRAIN;
|
game.field.mockAbility(enemy2, AbilityId.STORM_DRAIN);
|
||||||
|
|
||||||
game.move.select(MoveId.PSYCHIC_NOISE, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
game.move.use(MoveId.ROUND, BattlerIndex.PLAYER, BattlerIndex.ENEMY);
|
||||||
game.move.select(MoveId.SPLASH, BattlerIndex.PLAYER_2);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(enemy1.isFullHp()).toBe(true);
|
expect(enemy1.isFullHp()).toBe(true);
|
||||||
|
@ -86,7 +86,7 @@ describe("Weather - Strong Winds", () => {
|
|||||||
const enemy = game.scene.getEnemyPokemon()!;
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
enemy.hp = 1;
|
enemy.hp = 1;
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBeUndefined();
|
expect(game.scene.arena.weather?.weatherType).toBeUndefined();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||||
import type { CommandPhase } from "#app/phases/command-phase";
|
import type { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { Command } from "#enums/command";
|
import { Command } from "#enums/command";
|
||||||
import { NumberHolder } from "#app/utils/common";
|
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
@ -45,8 +44,6 @@ describe("Escape chance calculations", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||||
const escapePercentage = new 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
|
// 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: {
|
const escapeChances: {
|
||||||
pokemonSpeedRatio: number;
|
pokemonSpeedRatio: number;
|
||||||
@ -91,8 +88,8 @@ describe("Escape chance calculations", () => {
|
|||||||
20,
|
20,
|
||||||
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
||||||
]);
|
]);
|
||||||
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||||
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
expect(chance).toBe(escapeChances[i].expectedEscapeChance);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -118,8 +115,6 @@ describe("Escape chance calculations", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||||
const escapePercentage = new 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
|
// 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: {
|
const escapeChances: {
|
||||||
pokemonSpeedRatio: number;
|
pokemonSpeedRatio: number;
|
||||||
@ -172,9 +167,9 @@ describe("Escape chance calculations", () => {
|
|||||||
20,
|
20,
|
||||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
||||||
]);
|
]);
|
||||||
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||||
// checks to make sure the escape values are the same
|
// checks to make sure the escape values are the same
|
||||||
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
expect(chance).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
|
// 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(
|
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
|
||||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
|
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
|
||||||
@ -197,7 +192,6 @@ describe("Escape chance calculations", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||||
const escapePercentage = new 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
|
// 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: {
|
const escapeChances: {
|
||||||
@ -256,8 +250,8 @@ describe("Escape chance calculations", () => {
|
|||||||
20,
|
20,
|
||||||
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
||||||
]);
|
]);
|
||||||
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||||
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
expect(chance).toBe(escapeChances[i].expectedEscapeChance);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -283,7 +277,6 @@ describe("Escape chance calculations", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||||
const escapePercentage = new 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
|
// 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: {
|
const escapeChances: {
|
||||||
@ -349,9 +342,9 @@ describe("Escape chance calculations", () => {
|
|||||||
20,
|
20,
|
||||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
||||||
]);
|
]);
|
||||||
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
|
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||||
// checks to make sure the escape values are the same
|
// checks to make sure the escape values are the same
|
||||||
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
expect(chance).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
|
// 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(
|
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
|
||||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
|
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
|
||||||
|
@ -58,8 +58,7 @@ describe("Items - Dire Hit", () => {
|
|||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
|
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
|
|
||||||
await game.doKillOpponents();
|
await game.doKillOpponents();
|
||||||
|
|
||||||
await game.phaseInterceptor.to(BattleEndPhase);
|
await game.phaseInterceptor.to(BattleEndPhase);
|
||||||
|
@ -4,7 +4,6 @@ import type Move from "#app/data/moves/move";
|
|||||||
import { allMoves } from "#app/data/data-lists";
|
import { allMoves } from "#app/data/data-lists";
|
||||||
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
import { ArenaTagType } from "#app/enums/arena-tag-type";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
import { NumberHolder } from "#app/utils/common";
|
import { NumberHolder } from "#app/utils/common";
|
||||||
import { AbilityId } from "#enums/ability-id";
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
@ -12,7 +11,7 @@ import { SpeciesId } from "#enums/species-id";
|
|||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
let globalScene: BattleScene;
|
let globalScene: BattleScene;
|
||||||
|
|
||||||
@ -52,10 +51,10 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.toEndOfTurn();
|
||||||
const mockedDmg = getMockedMoveDamage(
|
const mockedDmg = getMockedMoveDamage(
|
||||||
game.scene.getEnemyPokemon()!,
|
game.field.getEnemyPokemon(),
|
||||||
game.scene.getPlayerPokemon()!,
|
game.field.getPlayerPokemon(),
|
||||||
allMoves[moveToUse],
|
allMoves[moveToUse],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -71,10 +70,10 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
game.move.select(moveToUse, 1);
|
game.move.select(moveToUse, 1);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.toEndOfTurn();
|
||||||
const mockedDmg = getMockedMoveDamage(
|
const mockedDmg = getMockedMoveDamage(
|
||||||
game.scene.getEnemyPokemon()!,
|
game.field.getEnemyPokemon(),
|
||||||
game.scene.getPlayerPokemon()!,
|
game.field.getPlayerPokemon(),
|
||||||
allMoves[moveToUse],
|
allMoves[moveToUse],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -82,72 +81,48 @@ describe("Moves - Aurora Veil", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("reduces damage of special attacks by half in a single battle", async () => {
|
it("reduces damage of special attacks by half in a single battle", async () => {
|
||||||
const moveToUse = MoveId.ABSORB;
|
|
||||||
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.use(MoveId.ABSORB);
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
const mockedDmg = getMockedMoveDamage(
|
const mockedDmg = getMockedMoveDamage(
|
||||||
game.scene.getEnemyPokemon()!,
|
game.field.getEnemyPokemon(),
|
||||||
game.scene.getPlayerPokemon()!,
|
game.field.getPlayerPokemon(),
|
||||||
allMoves[moveToUse],
|
allMoves[MoveId.ABSORB],
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockedDmg).toBe(allMoves[moveToUse].power * singleBattleMultiplier);
|
expect(mockedDmg).toBe(allMoves[MoveId.ABSORB].power * singleBattleMultiplier);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reduces damage of special attacks by a third in a double battle", async () => {
|
it("reduces damage of special attacks by a third in a double battle", async () => {
|
||||||
game.override.battleStyle("double");
|
game.override.battleStyle("double");
|
||||||
|
|
||||||
const moveToUse = MoveId.DAZZLING_GLEAM;
|
|
||||||
await game.classicMode.startBattle([SpeciesId.SHUCKLE, SpeciesId.SHUCKLE]);
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
|
||||||
game.move.select(moveToUse, 1);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
|
||||||
const mockedDmg = getMockedMoveDamage(
|
|
||||||
game.scene.getEnemyPokemon()!,
|
|
||||||
game.scene.getPlayerPokemon()!,
|
|
||||||
allMoves[moveToUse],
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mockedDmg).toBe(allMoves[moveToUse].power * doubleBattleMultiplier);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not affect physical critical hits", async () => {
|
|
||||||
game.override.moveset([MoveId.WICKED_BLOW]);
|
|
||||||
const moveToUse = MoveId.WICKED_BLOW;
|
|
||||||
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.use(MoveId.ABSORB);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
const mockedDmg = getMockedMoveDamage(
|
const mockedDmg = getMockedMoveDamage(
|
||||||
game.scene.getEnemyPokemon()!,
|
game.field.getEnemyPokemon(),
|
||||||
game.scene.getPlayerPokemon()!,
|
game.field.getPlayerPokemon(),
|
||||||
allMoves[moveToUse],
|
allMoves[MoveId.ABSORB],
|
||||||
);
|
);
|
||||||
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
|
||||||
|
expect(mockedDmg).toBe(allMoves[MoveId.ABSORB].power * doubleBattleMultiplier);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not affect critical hits", async () => {
|
it("does not affect critical hits", async () => {
|
||||||
game.override.moveset([MoveId.FROST_BREATH]);
|
|
||||||
const moveToUse = MoveId.FROST_BREATH;
|
|
||||||
vi.spyOn(allMoves[MoveId.FROST_BREATH], "accuracy", "get").mockReturnValue(100);
|
|
||||||
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
await game.classicMode.startBattle([SpeciesId.SHUCKLE]);
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.use(MoveId.WICKED_BLOW);
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
const mockedDmg = getMockedMoveDamage(
|
const mockedDmg = getMockedMoveDamage(
|
||||||
game.scene.getEnemyPokemon()!,
|
game.field.getEnemyPokemon(),
|
||||||
game.scene.getPlayerPokemon()!,
|
game.field.getPlayerPokemon(),
|
||||||
allMoves[moveToUse],
|
allMoves[MoveId.WICKED_BLOW],
|
||||||
);
|
);
|
||||||
expect(mockedDmg).toBe(allMoves[moveToUse].power);
|
expect(mockedDmg).toBe(allMoves[MoveId.WICKED_BLOW].power);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ describe("Moves - Baddy Bad", () => {
|
|||||||
game.override.enemyMoveset(MoveId.PROTECT);
|
game.override.enemyMoveset(MoveId.PROTECT);
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
game.move.select(MoveId.BADDY_BAD);
|
game.move.use(MoveId.BADDY_BAD);
|
||||||
await game.phaseInterceptor.to("BerryPhase");
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
expect(game.scene.arena.tags.length).toBe(0);
|
expect(game.scene.arena.tags.length).toBe(0);
|
||||||
|
@ -100,12 +100,12 @@ describe("Moves - Gastro Acid", () => {
|
|||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(enemyPokemon.summonData.abilitySuppressed).toBe(true);
|
expect(enemyPokemon.summonData.abilitySuppressed).toBe(true);
|
||||||
|
|
||||||
game.move.select(MoveId.WATER_GUN);
|
game.move.use(MoveId.WATER_GUN);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
// water gun should've dealt damage due to suppressed Water Absorb
|
// water gun should've dealt damage due to suppressed Water Absorb
|
||||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
||||||
|
|
||||||
game.move.select(MoveId.SPORE);
|
game.move.use(MoveId.SPORE);
|
||||||
await game.toEndOfTurn();
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
// Comatose should block stauts effect
|
// Comatose should block stauts effect
|
||||||
|
@ -361,7 +361,7 @@ describe("Moves - Instruct", () => {
|
|||||||
useMode: MoveUseMode.NORMAL,
|
useMode: MoveUseMode.NORMAL,
|
||||||
});
|
});
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
|
||||||
await game.toEndOfTurn();
|
await game.toEndOfTurn();
|
||||||
expect(game.field.getEnemyPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
expect(game.field.getEnemyPokemon().getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
@ -30,30 +30,26 @@ describe("Moves - Reflect Type", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => {
|
it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => {
|
||||||
game.override
|
game.override.startingLevel(60).enemySpecies(SpeciesId.CHARMANDER);
|
||||||
.moveset([MoveId.FORESTS_CURSE, MoveId.REFLECT_TYPE])
|
|
||||||
.startingLevel(60)
|
|
||||||
.enemySpecies(SpeciesId.CHARMANDER)
|
|
||||||
.enemyMoveset([MoveId.BURN_UP, MoveId.SPLASH]);
|
|
||||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon();
|
const playerPokemon = game.field.getPlayerPokemon();
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
const enemyPokemon = game.field.getEnemyPokemon();
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.use(MoveId.SPLASH);
|
||||||
await game.move.selectEnemyMove(MoveId.BURN_UP);
|
await game.move.forceEnemyMove(MoveId.BURN_UP);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
game.move.select(MoveId.FORESTS_CURSE);
|
game.move.use(MoveId.FORESTS_CURSE);
|
||||||
await game.move.selectEnemyMove(MoveId.SPLASH);
|
await game.move.forceEnemyMove(MoveId.SPLASH);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
expect(enemyPokemon?.getTypes().includes(PokemonType.UNKNOWN)).toBe(true);
|
expect(enemyPokemon.getTypes().includes(PokemonType.UNKNOWN)).toBe(true);
|
||||||
expect(enemyPokemon?.getTypes().includes(PokemonType.GRASS)).toBe(true);
|
expect(enemyPokemon.getTypes().includes(PokemonType.GRASS)).toBe(true);
|
||||||
|
|
||||||
game.move.select(MoveId.REFLECT_TYPE);
|
game.move.use(MoveId.REFLECT_TYPE);
|
||||||
await game.move.selectEnemyMove(MoveId.SPLASH);
|
await game.move.forceEnemyMove(MoveId.SPLASH);
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
expect(playerPokemon?.getTypes()[0]).toBe(PokemonType.NORMAL);
|
expect(playerPokemon.getTypes()[0]).toBe(PokemonType.NORMAL);
|
||||||
expect(playerPokemon?.getTypes().includes(PokemonType.GRASS)).toBe(true);
|
expect(playerPokemon.getTypes().includes(PokemonType.GRASS)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import Phaser from "phaser";
|
|||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { ArenaTrapTag } from "#app/data/arena-tag";
|
import { ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
import { ArenaTagSide } from "#enums/arena-tag-side";
|
import { ArenaTagSide } from "#enums/arena-tag-side";
|
||||||
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
|
|
||||||
describe("Moves - Spikes", () => {
|
describe("Moves - Spikes", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -80,14 +81,19 @@ describe("Moves - Spikes", () => {
|
|||||||
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should work when all targets fainted", async () => {
|
// TODO: re-enable after re-fixing hazards moves
|
||||||
game.override.enemySpecies(SpeciesId.DIGLETT).battleStyle("double").startingLevel(50);
|
it.todo("should work when all targets fainted", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.RAYQUAZA, SpeciesId.ROWLET]);
|
game.override.enemySpecies(SpeciesId.DIGLETT).battleStyle("double").startingLevel(1000);
|
||||||
|
await game.classicMode.startBattle([SpeciesId.RAYQUAZA, SpeciesId.SHUCKLE]);
|
||||||
|
|
||||||
game.move.select(MoveId.EARTHQUAKE);
|
const [enemy1, enemy2] = game.scene.getEnemyField();
|
||||||
game.move.select(MoveId.SPIKES, 1);
|
|
||||||
await game.phaseInterceptor.to("TurnEndPhase");
|
|
||||||
|
|
||||||
|
game.move.use(MoveId.HYPER_VOICE, BattlerIndex.PLAYER);
|
||||||
|
game.move.use(MoveId.SPIKES, BattlerIndex.PLAYER_2);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(enemy1.isFainted()).toBe(true);
|
||||||
|
expect(enemy2.isFainted()).toBe(true);
|
||||||
expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined();
|
expect(game.scene.arena.getTagOnSide(ArenaTrapTag, ArenaTagSide.ENEMY)).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -12,7 +12,6 @@ import overrides from "#app/overrides";
|
|||||||
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
|
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
|
||||||
import { CommandPhase } from "#app/phases/command-phase";
|
import { CommandPhase } from "#app/phases/command-phase";
|
||||||
import { EncounterPhase } from "#app/phases/encounter-phase";
|
import { EncounterPhase } from "#app/phases/encounter-phase";
|
||||||
import { FaintPhase } from "#app/phases/faint-phase";
|
|
||||||
import { LoginPhase } from "#app/phases/login-phase";
|
import { LoginPhase } from "#app/phases/login-phase";
|
||||||
import { MovePhase } from "#app/phases/move-phase";
|
import { MovePhase } from "#app/phases/move-phase";
|
||||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||||
@ -201,9 +200,8 @@ export default class GameManager {
|
|||||||
/**
|
/**
|
||||||
* Helper function to run to the final boss encounter as it's a bit tricky due to extra dialogue
|
* Helper function to run to the final boss encounter as it's a bit tricky due to extra dialogue
|
||||||
* Also handles Major/Minor bosses from endless modes
|
* Also handles Major/Minor bosses from endless modes
|
||||||
* @param game - The game manager
|
* @param species - Array of {@linkcode SpeciesId}s to start the final battle with.
|
||||||
* @param species
|
* @param mode - The {@linkcode GameModes} to spawn the final boss encounter in.
|
||||||
* @param mode
|
|
||||||
*/
|
*/
|
||||||
async runToFinalBossEncounter(species: SpeciesId[], mode: GameModes) {
|
async runToFinalBossEncounter(species: SpeciesId[], mode: GameModes) {
|
||||||
console.log("===to final boss encounter===");
|
console.log("===to final boss encounter===");
|
||||||
@ -230,9 +228,9 @@ export default class GameManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs the game to a mystery encounter phase.
|
* Runs the game to a mystery encounter phase.
|
||||||
* @param encounterType if specified, will expect encounter to have been spawned
|
* @param encounterType - If specified, will expect encounter to be the given type.
|
||||||
* @param species Optional array of species for party.
|
* @param species - Optional array of species for party to start with.
|
||||||
* @returns A promise that resolves when the EncounterPhase ends.
|
* @returns A Promise that resolves when the EncounterPhase ends.
|
||||||
*/
|
*/
|
||||||
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: SpeciesId[]) {
|
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: SpeciesId[]) {
|
||||||
if (!isNullOrUndefined(encounterType)) {
|
if (!isNullOrUndefined(encounterType)) {
|
||||||
@ -277,6 +275,7 @@ export default class GameManager {
|
|||||||
* Will trigger during the next {@linkcode SelectTargetPhase}
|
* Will trigger during the next {@linkcode SelectTargetPhase}
|
||||||
* @param targetIndex - The {@linkcode BattlerIndex} of the attack target, or `undefined` for multi-target attacks
|
* @param targetIndex - The {@linkcode BattlerIndex} of the attack target, or `undefined` for multi-target attacks
|
||||||
* @param movePosition - The 0-indexed position of the move in the pokemon's moveset array
|
* @param movePosition - The 0-indexed position of the move in the pokemon's moveset array
|
||||||
|
* @throws Immediately fails tests
|
||||||
*/
|
*/
|
||||||
selectTarget(movePosition: number, targetIndex?: BattlerIndex) {
|
selectTarget(movePosition: number, targetIndex?: BattlerIndex) {
|
||||||
this.onNextPrompt(
|
this.onNextPrompt(
|
||||||
@ -292,7 +291,7 @@ export default class GameManager {
|
|||||||
handler.setCursor(targetIndex !== undefined ? targetIndex : BattlerIndex.ENEMY);
|
handler.setCursor(targetIndex !== undefined ? targetIndex : BattlerIndex.ENEMY);
|
||||||
}
|
}
|
||||||
if (move.isMultiTarget() && targetIndex !== undefined) {
|
if (move.isMultiTarget() && targetIndex !== undefined) {
|
||||||
throw new Error(`targetIndex was passed to selectMove() but move ("${move.name}") is not targetted`);
|
expect.fail(`targetIndex was passed to selectMove() but move ("${move.name}") is not targetted`);
|
||||||
}
|
}
|
||||||
handler.processInput(Button.ACTION);
|
handler.processInput(Button.ACTION);
|
||||||
},
|
},
|
||||||
@ -452,17 +451,14 @@ export default class GameManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Faints a player or enemy pokemon instantly by setting their HP to 0.
|
* Faint a player or enemy pokemon instantly by setting their HP to 0.
|
||||||
* @param pokemon - The player/enemy pokemon being fainted
|
* @param pokemon - The player/enemy pokemon being fainted
|
||||||
* @returns A promise that resolves once the fainted pokemon's FaintPhase finishes running.
|
* @returns A Promise that resolves once the fainted pokemon's FaintPhase finishes running.
|
||||||
*/
|
*/
|
||||||
async killPokemon(pokemon: PlayerPokemon | EnemyPokemon) {
|
async killPokemon(pokemon: PlayerPokemon | EnemyPokemon) {
|
||||||
return new Promise<void>(async (resolve, reject) => {
|
|
||||||
pokemon.hp = 0;
|
pokemon.hp = 0;
|
||||||
this.scene.phaseManager.pushPhase(new FaintPhase(pokemon.getBattlerIndex(), true));
|
this.scene.phaseManager.pushNew("FaintPhase", pokemon.getBattlerIndex(), true);
|
||||||
await this.phaseInterceptor.to(FaintPhase).catch(e => reject(e));
|
await this.phaseInterceptor.to("FaintPhase");
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +10,7 @@ import { getGameMode } from "#app/game-mode";
|
|||||||
import { GameModes } from "#enums/game-modes";
|
import { GameModes } from "#enums/game-modes";
|
||||||
import type { StarterMoveset } from "#app/system/game-data";
|
import type { StarterMoveset } from "#app/system/game-data";
|
||||||
import type { Starter } from "#app/ui/starter-select-ui-handler";
|
import type { Starter } from "#app/ui/starter-select-ui-handler";
|
||||||
import { MoveId } from "#enums/move-id";
|
import type { MoveId } from "#enums/move-id";
|
||||||
import type { SpeciesId } from "#enums/species-id";
|
import type { SpeciesId } from "#enums/species-id";
|
||||||
|
|
||||||
/** Function to convert Blob to string */
|
/** Function to convert Blob to string */
|
||||||
@ -98,15 +98,6 @@ export function waitUntil(truth): Promise<unknown> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the index of `move` from the moveset of the pokemon on the player's field at location `pokemonIndex`. */
|
|
||||||
export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: MoveId): number {
|
|
||||||
const playerPokemon = scene.getPlayerField()[pokemonIndex];
|
|
||||||
const moveSet = playerPokemon.getMoveset();
|
|
||||||
const index = moveSet.findIndex(m => m.moveId === move && m.ppUsed < m.getMovePp());
|
|
||||||
console.log(`Move position for ${MoveId[move]} (=${move}):`, index);
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful for populating party, wave index, etc. without having to spin up and run through an entire EncounterPhase
|
* Useful for populating party, wave index, etc. without having to spin up and run through an entire EncounterPhase
|
||||||
*/
|
*/
|
||||||
|
@ -70,7 +70,7 @@ export class FieldHelper extends GameManagerHelper {
|
|||||||
* @see {@linkcode vi.spyOn}
|
* @see {@linkcode vi.spyOn}
|
||||||
* @see https://vitest.dev/api/mock#mockreturnvalue
|
* @see https://vitest.dev/api/mock#mockreturnvalue
|
||||||
*/
|
*/
|
||||||
public mockAbility(pokemon: Pokemon, ability: AbilityId): MockInstance<(baseOnly?: boolean) => Ability> {
|
public mockAbility(pokemon: Pokemon, ability: AbilityId): MockInstance<(ignoreOverride?: boolean) => Ability> {
|
||||||
return vi.spyOn(pokemon, "getAbility").mockReturnValue(allAbilities[ability]);
|
return vi.spyOn(pokemon, "getAbility").mockReturnValue(allAbilities[ability]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { BattlerIndex } from "#enums/battler-index";
|
import { BattlerIndex } from "#enums/battler-index";
|
||||||
import { getMoveTargets } from "#app/data/moves/move-utils";
|
import { getMoveTargets } from "#app/data/moves/move-utils";
|
||||||
import type Pokemon from "#app/field/pokemon";
|
import type Pokemon from "#app/field/pokemon";
|
||||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||||
@ -9,14 +9,13 @@ import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
|||||||
import { Command } from "#enums/command";
|
import { Command } from "#enums/command";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { getMovePosition } from "#test/testUtils/gameManagerUtils";
|
|
||||||
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
|
import { GameManagerHelper } from "#test/testUtils/helpers/gameManagerHelper";
|
||||||
import { vi } from "vitest";
|
import { expect, vi } from "vitest";
|
||||||
import { coerceArray } from "#app/utils/common";
|
import { coerceArray, toReadableString } from "#app/utils/common";
|
||||||
import { MoveUseMode } from "#enums/move-use-mode";
|
import { MoveUseMode } from "#enums/move-use-mode";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to handle a Pokemon's move
|
* Helper to handle using a Pokemon's moves.
|
||||||
*/
|
*/
|
||||||
export class MoveHelper extends GameManagerHelper {
|
export class MoveHelper extends GameManagerHelper {
|
||||||
/**
|
/**
|
||||||
@ -49,13 +48,31 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select the move to be used by the given Pokemon(-index). Triggers during the next {@linkcode CommandPhase}
|
* Select a move _already in the player's moveset_ to be used during the next {@linkcode CommandPhase}.
|
||||||
* @param move - the move to use
|
* @param move - The {@linkcode MoveId} to use.
|
||||||
* @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0)
|
* @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified.
|
||||||
* @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required
|
* @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves.
|
||||||
|
* If set to `null`, will forgo normal target selection entirely (useful for UI tests).
|
||||||
|
* @remarks
|
||||||
|
* Will fail the current test if the move being selected is not in the user's moveset.
|
||||||
*/
|
*/
|
||||||
public select(move: MoveId, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null) {
|
public select(
|
||||||
const movePosition = getMovePosition(this.game.scene, pkmIndex, move);
|
move: MoveId,
|
||||||
|
pkmIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2 = BattlerIndex.PLAYER,
|
||||||
|
targetIndex?: BattlerIndex | null,
|
||||||
|
) {
|
||||||
|
const movePosition = this.getMovePosition(pkmIndex, move);
|
||||||
|
if (movePosition === -1) {
|
||||||
|
expect.fail(
|
||||||
|
`MoveHelper.select called with move '${toReadableString(MoveId[move])}' not in moveset!` +
|
||||||
|
`\nBattler Index: ${toReadableString(BattlerIndex[pkmIndex])}` +
|
||||||
|
`\nMoveset: [${this.game.scene
|
||||||
|
.getPlayerParty()
|
||||||
|
[pkmIndex].getMoveset()
|
||||||
|
.map(pm => toReadableString(MoveId[pm.moveId]))
|
||||||
|
.join(", ")}]`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => {
|
this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => {
|
||||||
this.game.scene.ui.setMode(
|
this.game.scene.ui.setMode(
|
||||||
@ -77,14 +94,30 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select the move to be used by the given Pokemon(-index), **which will also terastallize on this turn**.
|
* Select a move _already in the player's moveset_ to be used during the next {@linkcode CommandPhase}, **which will also terastallize on this turn**.
|
||||||
* Triggers during the next {@linkcode CommandPhase}
|
* @param move - The {@linkcode MoveId} to use.
|
||||||
* @param move - the move to use
|
* @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified.
|
||||||
* @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0)
|
* @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves.
|
||||||
* @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required
|
* If set to `null`, will forgo normal target selection entirely (useful for UI tests)
|
||||||
*/
|
*/
|
||||||
public selectWithTera(move: MoveId, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null) {
|
public selectWithTera(
|
||||||
const movePosition = getMovePosition(this.game.scene, pkmIndex, move);
|
move: MoveId,
|
||||||
|
pkmIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2 = BattlerIndex.PLAYER,
|
||||||
|
targetIndex?: BattlerIndex | null,
|
||||||
|
) {
|
||||||
|
const movePosition = this.getMovePosition(pkmIndex, move);
|
||||||
|
if (movePosition === -1) {
|
||||||
|
expect.fail(
|
||||||
|
`MoveHelper.selectWithTera called with move '${toReadableString(MoveId[move])}' not in moveset!` +
|
||||||
|
`\nBattler Index: ${toReadableString(BattlerIndex[pkmIndex])}` +
|
||||||
|
`\nMoveset: [${this.game.scene
|
||||||
|
.getPlayerParty()
|
||||||
|
[pkmIndex].getMoveset()
|
||||||
|
.map(pm => toReadableString(MoveId[pm.moveId]))
|
||||||
|
.join(", ")}]`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.game.scene.getPlayerParty()[pkmIndex].isTerastallized = false;
|
this.game.scene.getPlayerParty()[pkmIndex].isTerastallized = false;
|
||||||
|
|
||||||
this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => {
|
this.game.onNextPrompt("CommandPhase", UiMode.COMMAND, () => {
|
||||||
@ -107,6 +140,15 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Helper function to get the index of the selected move in the selected part member's moveset. */
|
||||||
|
private getMovePosition(pokemonIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2, move: MoveId): number {
|
||||||
|
const playerPokemon = this.game.scene.getPlayerField()[pokemonIndex];
|
||||||
|
const moveset = playerPokemon.getMoveset();
|
||||||
|
const index = moveset.findIndex(m => m.moveId === move && m.ppUsed < m.getMovePp());
|
||||||
|
console.log(`Move position for ${MoveId[move]} (=${move}):`, index);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifies a player pokemon's moveset to contain only the selected move and then
|
* Modifies a player pokemon's moveset to contain only the selected move and then
|
||||||
* selects it to be used during the next {@linkcode CommandPhase}.
|
* selects it to be used during the next {@linkcode CommandPhase}.
|
||||||
@ -116,14 +158,19 @@ export class MoveHelper extends GameManagerHelper {
|
|||||||
* Note: If you need to check for changes in the player's moveset as part of the test, it may be
|
* Note: If you need to check for changes in the player's moveset as part of the test, it may be
|
||||||
* best to use {@linkcode changeMoveset} and {@linkcode select} instead.
|
* best to use {@linkcode changeMoveset} and {@linkcode select} instead.
|
||||||
* @param moveId - the move to use
|
* @param moveId - the move to use
|
||||||
* @param pkmIndex - the pokemon index. Relevant for double-battles only (defaults to 0)
|
* @param pkmIndex - The {@linkcode BattlerIndex} of the player Pokemon using the move. Relevant for double battles only and defaults to {@linkcode BattlerIndex.PLAYER} if not specified.
|
||||||
* @param targetIndex - (optional) The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves, or `null` if a manual call to `selectTarget()` is required
|
* @param targetIndex - The {@linkcode BattlerIndex} of the Pokemon to target for single-target moves; should be omitted for multi-target moves.
|
||||||
* @param useTera - If `true`, the Pokemon also chooses to Terastallize. This does not require a Tera Orb. Default: `false`.
|
* @param useTera - If `true`, the Pokemon will attempt to Terastallize even without a Tera Orb; default `false`.
|
||||||
*/
|
*/
|
||||||
public use(moveId: MoveId, pkmIndex: 0 | 1 = 0, targetIndex?: BattlerIndex | null, useTera = false): void {
|
public use(
|
||||||
|
moveId: MoveId,
|
||||||
|
pkmIndex: BattlerIndex.PLAYER | BattlerIndex.PLAYER_2 = BattlerIndex.PLAYER,
|
||||||
|
targetIndex?: BattlerIndex,
|
||||||
|
useTera = false,
|
||||||
|
): void {
|
||||||
if ([Overrides.MOVESET_OVERRIDE].flat().length > 0) {
|
if ([Overrides.MOVESET_OVERRIDE].flat().length > 0) {
|
||||||
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([]);
|
||||||
console.warn("Warning: `use` overwrites the Pokemon's moveset and disables the player moveset override!");
|
console.warn("Warning: `MoveHelper.use` overwriting player pokemon moveset and disabling moveset override!");
|
||||||
}
|
}
|
||||||
|
|
||||||
const pokemon = this.game.scene.getPlayerField()[pkmIndex];
|
const pokemon = this.game.scene.getPlayerField()[pkmIndex];
|
||||||
|
Loading…
Reference in New Issue
Block a user