Merge branch 'beta' into dev/enable-import-sorting

This commit is contained in:
NightKev 2025-07-02 03:54:41 -07:00
commit 6402692cba
10 changed files with 84 additions and 77 deletions

View File

@ -791,12 +791,14 @@ export 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[] {

View File

@ -94,6 +94,12 @@ export 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) {

View File

@ -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
// ---------------- // ----------------

View File

@ -1,33 +1,33 @@
import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { applyAbAttrs } from "#abilities/apply-ab-attrs";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import Overrides from "#app/overrides";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { EnemyPokemon, PlayerPokemon, Pokemon } from "#field/pokemon"; import { FieldPhase } from "#phases/field-phase";
import { PokemonPhase } from "#phases/pokemon-phase";
import { NumberHolder } from "#utils/common"; import { NumberHolder } from "#utils/common";
import i18next from "i18next"; import i18next from "i18next";
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);
@ -56,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.
@ -91,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;
@ -110,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,
); );

View File

@ -2,7 +2,6 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TrickRoomTag } from "#data/arena-tag"; import { TrickRoomTag } from "#data/arena-tag";
import { allMoves } from "#data/data-lists"; import { allMoves } from "#data/data-lists";
import { AbilityId } from "#enums/ability-id";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
@ -213,27 +212,8 @@ export class TurnStartPhase extends FieldPhase {
break; break;
case Command.RUN: case Command.RUN:
{ {
let runningPokemon = pokemon; // Running (like ball throwing) is a team action taking up both Pokemon's turns.
if (globalScene.currentBattle.double) { phaseManager.unshiftNew("AttemptRunPhase");
const playerActivePokemon = field.filter(pokemon => {
if (pokemon) {
return pokemon.isPlayer() && pokemon.isActive();
}
return;
});
// if only one pokemon is alive, use that one
if (playerActivePokemon.length > 1) {
// find which active pokemon has faster speed
const fasterPokemon =
playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD)
? playerActivePokemon[0]
: playerActivePokemon[1];
// check if either active pokemon has the ability "Run Away"
const hasRunAway = playerActivePokemon.find(p => p.hasAbility(AbilityId.RUN_AWAY));
runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon;
}
}
phaseManager.unshiftNew("AttemptRunPhase", runningPokemon.getFieldIndex());
} }
break; break;
} }

View File

@ -1,3 +1,4 @@
import { globalScene } from "#app/global-scene";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -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);

View File

@ -1,3 +1,4 @@
import Overrides from "#app/overrides";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -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);

View File

@ -1,3 +1,4 @@
import { globalScene } from "#app/global-scene";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerIndex } from "#enums/battler-index"; import { BattlerIndex } from "#enums/battler-index";
@ -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);

View File

@ -1,3 +1,4 @@
import Overrides from "#app/overrides";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { Command } from "#enums/command"; import { Command } from "#enums/command";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
@ -7,7 +8,7 @@ import { AttemptRunPhase } from "#phases/attempt-run-phase";
import type { CommandPhase } from "#phases/command-phase"; import type { CommandPhase } from "#phases/command-phase";
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";
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();

View File

@ -4,7 +4,6 @@ import { SpeciesId } from "#enums/species-id";
import { AttemptRunPhase } from "#phases/attempt-run-phase"; import { AttemptRunPhase } from "#phases/attempt-run-phase";
import type { CommandPhase } from "#phases/command-phase"; import type { CommandPhase } from "#phases/command-phase";
import { GameManager } from "#test/testUtils/gameManager"; import { GameManager } from "#test/testUtils/gameManager";
import { NumberHolder } from "#utils/common";
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";
@ -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,