[Balance] Changed escape calculation (#3973)

* Changed escape calculation as per Mega

* Adding tests

* Updates some tests

* Updated all tests for bosses

* Removed console log lines

* Added some clarifying comments

* Fixed docs

* comment add

* comment add

* Convert comments into tsdoc comments

Convert `integer`/`IntegerHolder` to `number`/`NumberHolder`

Clean up tests a bit

---------

Co-authored-by: damocleas <damocleas25@gmail.com>
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Opaque02 2024-09-05 15:29:39 +10:00 committed by GitHub
parent 237aad2184
commit 61ab52c295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 363 additions and 11 deletions

View File

@ -1,31 +1,34 @@
import BattleScene from "#app/battle-scene.js";
import { applyAbAttrs, RunSuccessAbAttr } from "#app/data/ability.js";
import { Stat } from "#app/enums/stat.js";
import { StatusEffect } from "#app/enums/status-effect.js";
import Pokemon from "#app/field/pokemon.js";
import BattleScene from "#app/battle-scene";
import { applyAbAttrs, RunSuccessAbAttr } from "#app/data/ability";
import { Stat } from "#app/enums/stat";
import { StatusEffect } from "#app/enums/status-effect";
import Pokemon, { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
import i18next from "i18next";
import * as Utils from "#app/utils.js";
import * as Utils from "#app/utils";
import { BattleEndPhase } from "./battle-end-phase";
import { NewBattlePhase } from "./new-battle-phase";
import { PokemonPhase } from "./pokemon-phase";
export class AttemptRunPhase extends PokemonPhase {
constructor(scene: BattleScene, fieldIndex: integer) {
constructor(scene: BattleScene, fieldIndex: number) {
super(scene, fieldIndex);
}
start() {
super.start();
const playerPokemon = this.getPokemon();
const playerField = this.scene.getPlayerField();
const enemyField = this.scene.getEnemyField();
const enemySpeed = enemyField.reduce((total: integer, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0) / enemyField.length;
const playerPokemon = this.getPokemon();
const escapeChance = new Utils.NumberHolder(0);
this.attemptRunAway(playerField, enemyField, escapeChance);
const escapeChance = new Utils.IntegerHolder((((playerPokemon.getStat(Stat.SPD) * 128) / enemySpeed) + (30 * this.scene.currentBattle.escapeAttempts++)) % 256);
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
if (playerPokemon.randSeedInt(256) < escapeChance.value) {
if (Utils.randSeedInt(100) < escapeChance.value) {
this.scene.playSound("se/flee");
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
@ -53,4 +56,48 @@ export class AttemptRunPhase extends PokemonPhase {
this.end();
}
attemptRunAway(playerField: PlayerPokemon[], enemyField: EnemyPokemon[], escapeChance: Utils.NumberHolder) {
/** Sum of the speed of all enemy pokemon on the field */
const enemySpeed = enemyField.reduce((total: number, enemyPokemon: Pokemon) => total + enemyPokemon.getStat(Stat.SPD), 0);
/** Sum of the speed of all player pokemon on the field */
const playerSpeed = playerField.reduce((total: number, playerPokemon: Pokemon) => total + playerPokemon.getStat(Stat.SPD), 0);
/* The way the escape chance works is by looking at the difference between your speed and the enemy field's average speed as a ratio. The higher this ratio, the higher your chance of success.
* However, there is a cap for the ratio of your speed vs enemy speed which beyond that point, you won't gain any advantage. It also looks at how many times you've tried to escape.
* Again, the more times you've tried to escape, the higher your odds of escaping. Bosses and non-bosses are calculated differently - bosses are harder to escape from vs non-bosses
* Finally, there's a minimum and maximum escape chance as well so that escapes aren't guaranteed, yet they are never 0 either.
* The percentage chance to escape from a pokemon for both bosses and non bosses is linear and based on the minimum and maximum chances, and the speed ratio cap.
*
* At the time of writing, these conditions should be met:
* - The minimum escape chance should be 5% for bosses and non bosses
* - Bosses should have a maximum escape chance of 25%, whereas non-bosses should be 95%
* - The bonus per previous escape attempt should be 2% for bosses and 10% for non-bosses
* - The speed ratio cap should be 6x for bosses and 4x for non-bosses
* - The "default" escape chance when your speed equals the enemy speed should be 8.33% for bosses and 27.5% for non-bosses
*
* From the above, we can calculate the below values
*/
let isBoss = false;
for (let e = 0; e < enemyField.length; e++) {
isBoss = isBoss || enemyField[e].isBoss(); // this line checks if any of the enemy pokemon on the field are bosses; if so, the calculation for escaping is different
}
/** The ratio between the speed of your active pokemon and the speed of the enemy field */
const speedRatio = playerSpeed / enemySpeed;
/** The max ratio before escape chance stops increasing. Increased if there is a boss on the field */
const speedCap = isBoss ? 6 : 4;
/** Minimum percent chance to escape */
const minChance = 5;
/** Maximum percent chance to escape. Decreased if a boss is on the field */
const maxChance = isBoss ? 25 : 95;
/** How much each escape attempt increases the chance of the next attempt. Decreased if a boss is on the field */
const escapeBonus = isBoss ? 2 : 10;
/** Slope of the escape chance curve */
const escapeSlope = (maxChance - minChance) / speedCap;
// This will calculate the escape chance given all of the above and clamp it to the range of [`minChance`, `maxChance`]
escapeChance.value = Phaser.Math.Clamp(Math.round((escapeSlope * speedRatio) + minChance + (escapeBonus * this.scene.currentBattle.escapeAttempts++)), minChance, maxChance);
}
}

View File

@ -0,0 +1,303 @@
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
import { CommandPhase } from "#app/phases/command-phase";
import { Command } from "#app/ui/command-ui-handler";
import * as Utils from "#app/utils";
import { Abilities } from "#enums/abilities";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Escape chance calculations", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.enemySpecies(Species.BULBASAUR)
.enemyAbility(Abilities.INSOMNIA)
.ability(Abilities.INSOMNIA);
});
it("single non-boss opponent", async () => {
await game.classicMode.startBattle([Species.BULBASAUR]);
const playerPokemon = game.scene.getPlayerField();
const enemyField = game.scene.getEnemyField();
const enemySpeed = 100;
// set enemyPokemon's speed to 100
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new Utils.NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 0.1, escapeAttempts: 0, expectedEscapeChance: 7 },
{ pokemonSpeedRatio: 0.25, escapeAttempts: 0, expectedEscapeChance: 11 },
{ pokemonSpeedRatio: 0.5, escapeAttempts: 0, expectedEscapeChance: 16 },
{ pokemonSpeedRatio: 0.8, escapeAttempts: 0, expectedEscapeChance: 23 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 28 },
{ pokemonSpeedRatio: 1.2, escapeAttempts: 0, expectedEscapeChance: 32 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 39 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 73 },
{ pokemonSpeedRatio: 3.8, escapeAttempts: 0, expectedEscapeChance: 91 },
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 4.2, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 95 },
// retries section
{ pokemonSpeedRatio: 0.4, escapeAttempts: 1, expectedEscapeChance: 24 },
{ pokemonSpeedRatio: 1.6, escapeAttempts: 2, expectedEscapeChance: 61 },
{ pokemonSpeedRatio: 3.7, escapeAttempts: 5, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 0.2, escapeAttempts: 2, expectedEscapeChance: 30 },
{ pokemonSpeedRatio: 1, escapeAttempts: 3, expectedEscapeChance: 58 },
{ pokemonSpeedRatio: 2.9, escapeAttempts: 0, expectedEscapeChance: 70 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 7, expectedEscapeChance: 75 },
{ pokemonSpeedRatio: 16.2, escapeAttempts: 4, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 80 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * enemySpeed]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
}
}, 20000);
it("double non-boss opponent", async () => {
game.override.battleType("double");
await game.classicMode.startBattle([Species.BULBASAUR, Species.ABOMASNOW]);
const playerPokemon = game.scene.getPlayerField();
const enemyField = game.scene.getEnemyField();
const enemyASpeed = 70;
const enemyBSpeed = 30;
// gets the sum of the speed of the two pokemon
const totalEnemySpeed = enemyASpeed + enemyBSpeed;
// this is used to find the ratio of the player's first pokemon
const playerASpeedPercentage = 0.4;
// set enemyAPokemon's speed to 70
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
// set enemyBPokemon's speed to 30
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new Utils.NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
{ pokemonSpeedRatio: 0.3, escapeAttempts: 0, expectedEscapeChance: 12 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 0, expectedEscapeChance: 21 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 39 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 73 },
{ pokemonSpeedRatio: 9, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 28 },
{ pokemonSpeedRatio: 4.3, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 2.7, escapeAttempts: 0, expectedEscapeChance: 66 },
{ pokemonSpeedRatio: 2.1, escapeAttempts: 0, expectedEscapeChance: 52 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 0, expectedEscapeChance: 46 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 95 },
// retries section
{ pokemonSpeedRatio: 0.9, escapeAttempts: 1, expectedEscapeChance: 35 },
{ pokemonSpeedRatio: 3.6, escapeAttempts: 2, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 0.03, escapeAttempts: 7, expectedEscapeChance: 76 },
{ pokemonSpeedRatio: 0.02, escapeAttempts: 7, expectedEscapeChance: 75 },
{ pokemonSpeedRatio: 1, escapeAttempts: 5, expectedEscapeChance: 78 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 3, expectedEscapeChance: 51 },
{ pokemonSpeedRatio: 2.4, escapeAttempts: 9, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 7, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 95 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage)]);
// set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5]]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
// checks to make sure the escape values are the same
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed);
}
}, 20000);
it("single boss opponent", async () => {
game.override.startingWave(10);
await game.classicMode.startBattle([Species.BULBASAUR]);
const playerPokemon = game.scene.getPlayerField()!;
const enemyField = game.scene.getEnemyField()!;
const enemySpeed = 100;
// set enemyPokemon's speed to 100
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new Utils.NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 0.1, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 0.25, escapeAttempts: 0, expectedEscapeChance: 6 },
{ pokemonSpeedRatio: 0.5, escapeAttempts: 0, expectedEscapeChance: 7 },
{ pokemonSpeedRatio: 0.8, escapeAttempts: 0, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 1.2, escapeAttempts: 0, expectedEscapeChance: 9 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 15 },
{ pokemonSpeedRatio: 3.8, escapeAttempts: 0, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 4.2, escapeAttempts: 0, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 4.7, escapeAttempts: 0, expectedEscapeChance: 21 },
{ pokemonSpeedRatio: 5, escapeAttempts: 0, expectedEscapeChance: 22 },
{ pokemonSpeedRatio: 5.9, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6.7, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 25 },
// retries section
{ pokemonSpeedRatio: 0.4, escapeAttempts: 1, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 1.6, escapeAttempts: 2, expectedEscapeChance: 14 },
{ pokemonSpeedRatio: 3.7, escapeAttempts: 5, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 0.2, escapeAttempts: 2, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 1, escapeAttempts: 3, expectedEscapeChance: 14 },
{ pokemonSpeedRatio: 2.9, escapeAttempts: 0, expectedEscapeChance: 15 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 7, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 16.2, escapeAttempts: 4, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 4.5, escapeAttempts: 1, expectedEscapeChance: 22 },
{ pokemonSpeedRatio: 6.8, escapeAttempts: 6, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.2, escapeAttempts: 8, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 4.7, escapeAttempts: 10, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.1, escapeAttempts: 1, expectedEscapeChance: 24 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.9, escapeAttempts: 2, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6.1, escapeAttempts: 3, expectedEscapeChance: 25 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * enemySpeed]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
}
}, 20000);
it("double boss opponent", async () => {
game.override.battleType("double");
game.override.startingWave(10);
await game.classicMode.startBattle([Species.BULBASAUR, Species.ABOMASNOW]);
const playerPokemon = game.scene.getPlayerField();
const enemyField = game.scene.getEnemyField();
const enemyASpeed = 70;
const enemyBSpeed = 30;
// gets the sum of the speed of the two pokemon
const totalEnemySpeed = enemyASpeed + enemyBSpeed;
// this is used to find the ratio of the player's first pokemon
const playerASpeedPercentage = 0.8;
// set enemyAPokemon's speed to 70
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
// set enemyBPokemon's speed to 30
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new Utils.NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: { pokemonSpeedRatio: number, escapeAttempts: number, expectedEscapeChance: number }[] = [
{ pokemonSpeedRatio: 0.3, escapeAttempts: 0, expectedEscapeChance: 6 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 0, expectedEscapeChance: 7 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 15 },
{ pokemonSpeedRatio: 9, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 4.3, escapeAttempts: 0, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 2.7, escapeAttempts: 0, expectedEscapeChance: 14 },
{ pokemonSpeedRatio: 2.1, escapeAttempts: 0, expectedEscapeChance: 12 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 0, expectedEscapeChance: 11 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 5.7, escapeAttempts: 0, expectedEscapeChance: 24 },
{ pokemonSpeedRatio: 5, escapeAttempts: 0, expectedEscapeChance: 22 },
{ pokemonSpeedRatio: 6.1, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6.8, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 25 },
// retries section
{ pokemonSpeedRatio: 0.9, escapeAttempts: 1, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 3.6, escapeAttempts: 2, expectedEscapeChance: 21 },
{ pokemonSpeedRatio: 0.03, escapeAttempts: 7, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 0.02, escapeAttempts: 7, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 1, escapeAttempts: 5, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 3, expectedEscapeChance: 13 },
{ pokemonSpeedRatio: 2.4, escapeAttempts: 9, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 7, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 3, escapeAttempts: 1, expectedEscapeChance: 17 },
{ pokemonSpeedRatio: 4.5, escapeAttempts: 3, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 3.7, escapeAttempts: 1, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 6.5, escapeAttempts: 1, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 12, escapeAttempts: 4, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.2, escapeAttempts: 2, expectedEscapeChance: 25 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage)]);
// set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5]]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
// checks to make sure the escape values are the same
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed);
}
}, 20000);
});

View File

@ -1,5 +1,6 @@
import { Phase } from "#app/phase";
import ErrorInterceptor from "#app/test/utils/errorInterceptor";
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { BerryPhase } from "#app/phases/berry-phase";
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
@ -100,6 +101,7 @@ export default class PhaseInterceptor {
[EvolutionPhase, this.startPhase],
[EndEvolutionPhase, this.startPhase],
[LevelCapPhase, this.startPhase],
[AttemptRunPhase, this.startPhase],
];
private endBySetMode = [