mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 07:22:19 +02:00
Reintrocuded calculateEscapeChance into AttemptRunPhase and removed run utils
This commit is contained in:
parent
87046712af
commit
2e37173ad4
@ -2,8 +2,9 @@ import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/app
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import i18next from "i18next";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { calculateEscapeChance } from "#app/utils/run-utils";
|
||||
import { FieldPhase } from "./field-phase";
|
||||
import Overrides from "#app/overrides";
|
||||
import { Stat } from "#enums/stat";
|
||||
export class AttemptRunPhase extends FieldPhase {
|
||||
public readonly phaseName = "AttemptRunPhase";
|
||||
|
||||
@ -21,7 +22,7 @@ export class AttemptRunPhase extends FieldPhase {
|
||||
const enemyField = globalScene.getEnemyField();
|
||||
|
||||
const escapeRoll = this.getTeamRNG(100);
|
||||
const escapeChance = calculateEscapeChance(currentAttempts);
|
||||
const escapeChance = this.calculateEscapeChance(currentAttempts);
|
||||
|
||||
activePlayerField.forEach(p => {
|
||||
applyAbAttrs("RunSuccessAbAttr", p, null, false, { value: escapeChance });
|
||||
@ -69,4 +70,64 @@ export class AttemptRunPhase extends FieldPhase {
|
||||
|
||||
this.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the chance for the player's team to successfully run away from battle.
|
||||
*
|
||||
* @param escapeAttempts The number of prior failed escape attempts in the current battle
|
||||
* @returns The final escape chance, as percentage out of 100.
|
||||
*/
|
||||
calculateEscapeChance(escapeAttempts: number): number {
|
||||
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.
|
||||
* 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
|
||||
*/
|
||||
|
||||
/** Whether at least 1 pokemon on the enemy field is a boss. */
|
||||
const isBoss = enemyField.some(e => e.isBoss());
|
||||
|
||||
/** 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;
|
||||
|
||||
// Check for override, guaranteeing or forbidding random flee attempts as applicable.
|
||||
if (Overrides.RUN_SUCCESS_OVERRIDE !== null) {
|
||||
return Overrides.RUN_SUCCESS_OVERRIDE ? 100 : 0;
|
||||
}
|
||||
|
||||
// This will calculate the escape chance given all of the above and clamp it to the range of [`minChance`, `maxChance`]
|
||||
return Phaser.Math.Clamp(
|
||||
Math.round(escapeSlope * speedRatio + minChance + escapeBonus * escapeAttempts),
|
||||
minChance,
|
||||
maxChance,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { Stat } from "#enums/stat";
|
||||
import Overrides from "#app/overrides";
|
||||
/**
|
||||
* Calculate the chance for the player's team to successfully run away from battle.
|
||||
*
|
||||
* @param escapeAttempts The number of prior failed escape attempts in the current battle
|
||||
* @returns The final escape chance, as percentage out of 100.
|
||||
*/
|
||||
export function calculateEscapeChance(escapeAttempts: number): number {
|
||||
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.
|
||||
* 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
|
||||
*/
|
||||
|
||||
/** Whether at least 1 pokemon on the enemy field is a boss. */
|
||||
const isBoss = enemyField.some(e => e.isBoss());
|
||||
|
||||
/** 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;
|
||||
|
||||
// Check for override, guaranteeing or forbidding random flee attempts as applicable.
|
||||
if (Overrides.RUN_SUCCESS_OVERRIDE !== null) {
|
||||
return Overrides.RUN_SUCCESS_OVERRIDE ? 100 : 0;
|
||||
}
|
||||
|
||||
// This will calculate the escape chance given all of the above and clamp it to the range of [`minChance`, `maxChance`]
|
||||
return Phaser.Math.Clamp(
|
||||
Math.round(escapeSlope * speedRatio + minChance + escapeBonus * escapeAttempts),
|
||||
minChance,
|
||||
maxChance,
|
||||
);
|
||||
}
|
@ -6,7 +6,6 @@ import { SpeciesId } from "#enums/species-id";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { calculateEscapeChance } from "#app/utils/run-utils";
|
||||
|
||||
describe("Escape chance calculations", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -44,7 +43,7 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
|
||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||
// 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;
|
||||
@ -89,7 +88,7 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
||||
]);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
expect(chance).toBe(escapeChances[i].expectedEscapeChance);
|
||||
}
|
||||
});
|
||||
@ -115,7 +114,7 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
|
||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||
// 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;
|
||||
@ -168,7 +167,7 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
||||
]);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
// checks to make sure the escape values are the same
|
||||
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
|
||||
@ -192,6 +191,7 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||
|
||||
// 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: {
|
||||
@ -250,7 +250,7 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
||||
]);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
expect(chance).toBe(escapeChances[i].expectedEscapeChance);
|
||||
}
|
||||
});
|
||||
@ -276,6 +276,7 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
const phase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||
|
||||
// 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: {
|
||||
@ -341,7 +342,7 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
||||
]);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
const chance = phase.calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
// checks to make sure the escape values are the same
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user