mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-04 07:22:19 +02:00
Made adjustments based on reviewer suggestions
This commit is contained in:
parent
c4f1ec2a7a
commit
87046712af
@ -800,7 +800,7 @@ export default class BattleScene extends SceneBase {
|
||||
// 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.
|
||||
* @param active - If true, returns only the PlayerPokemon that are currently active
|
||||
* @param active - Whether to consider only active on-field pokemon ({@see {@linkcode Pokemon.isActiveZ} for more information}); default `false`
|
||||
* @returns array of {@linkcode PlayerPokemon}
|
||||
*/
|
||||
public getPlayerField(active = false): PlayerPokemon[] {
|
||||
|
@ -94,7 +94,10 @@ export default class Battle {
|
||||
/** If the current battle is a Mystery Encounter, this will always be defined */
|
||||
public mysteryEncounter?: MysteryEncounter;
|
||||
|
||||
/**Tracks whether the last run attempt in battle failed*/
|
||||
/**
|
||||
* Tracker for whether the last run attempt failed.
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
public failedRunAway = false;
|
||||
|
||||
private rngCounter = 0;
|
||||
|
@ -118,7 +118,13 @@ class DefaultOverrides {
|
||||
* or `false` to force it to never trigger.
|
||||
*/
|
||||
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
|
||||
// ----------------
|
||||
|
@ -1,17 +1,14 @@
|
||||
import { applyAbAttrs, applyPreLeaveFieldAbAttrs } from "#app/data/abilities/apply-ab-attrs";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import i18next from "i18next";
|
||||
import { NumberHolder, randSeedInt } from "#app/utils/common";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { calculateEscapeChance } from "#app/utils/run-utils";
|
||||
import { FieldPhase } from "./field-phase";
|
||||
export class AttemptRunPhase extends FieldPhase {
|
||||
public readonly phaseName = "AttemptRunPhase";
|
||||
/** For testing purposes: this is to force the pokemon to fail and escape */
|
||||
public forceFailEscape = false;
|
||||
|
||||
private getTeamRNG(range: number, min = 0) {
|
||||
return globalScene.currentBattle ? globalScene.randBattleSeedInt(range, min) : randSeedInt(range, min);
|
||||
return globalScene.randBattleSeedInt(range, min);
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -24,15 +21,13 @@ export class AttemptRunPhase extends FieldPhase {
|
||||
const enemyField = globalScene.getEnemyField();
|
||||
|
||||
const escapeRoll = this.getTeamRNG(100);
|
||||
const escapeChance = new NumberHolder(0);
|
||||
|
||||
calculateEscapeChance(activePlayerField, enemyField, escapeChance, currentAttempts);
|
||||
const escapeChance = calculateEscapeChance(currentAttempts);
|
||||
|
||||
activePlayerField.forEach(p => {
|
||||
applyAbAttrs("RunSuccessAbAttr", p, null, false, escapeChance);
|
||||
applyAbAttrs("RunSuccessAbAttr", p, null, false, { value: escapeChance });
|
||||
});
|
||||
|
||||
if (escapeRoll < escapeChance.value && !this.forceFailEscape) {
|
||||
if (escapeRoll < escapeChance) {
|
||||
enemyField.forEach(enemyPokemon => applyPreLeaveFieldAbAttrs("PreLeaveFieldAbAttr", enemyPokemon));
|
||||
|
||||
globalScene.playSound("se/flee");
|
||||
@ -65,7 +60,10 @@ export class AttemptRunPhase extends FieldPhase {
|
||||
|
||||
globalScene.phaseManager.pushNew("NewBattlePhase");
|
||||
} else {
|
||||
globalScene.currentBattle.failedRunAway = true;
|
||||
activePlayerField.forEach(p => {
|
||||
p.turnData.failedRunAway = true;
|
||||
});
|
||||
|
||||
globalScene.phaseManager.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
|
||||
}
|
||||
|
||||
|
@ -3,19 +3,6 @@ import { TrainerSlot } from "#enums/trainer-slot";
|
||||
import { Phase } from "#app/phase";
|
||||
|
||||
export abstract class BattlePhase extends Phase {
|
||||
start() {
|
||||
if (globalScene.currentBattle.failedRunAway) {
|
||||
const activePlayerField = globalScene.getPlayerField(true);
|
||||
|
||||
activePlayerField.forEach(p => {
|
||||
p.turnData.failedRunAway = true;
|
||||
});
|
||||
|
||||
//Reset flag for future run attempts
|
||||
globalScene.currentBattle.failedRunAway = false;
|
||||
}
|
||||
}
|
||||
|
||||
showEnemyTrainer(trainerSlot: TrainerSlot = TrainerSlot.NONE): void {
|
||||
if (!globalScene.currentBattle.trainer) {
|
||||
console.warn("Enemy trainer is missing!");
|
||||
|
@ -208,7 +208,7 @@ export class TurnStartPhase extends FieldPhase {
|
||||
break;
|
||||
case Command.RUN:
|
||||
{
|
||||
//Team Based action, no need for checking individual pokemon or whether its doubles
|
||||
// Running (like ball throwing) is a team action taking up both Pokemon's turns.
|
||||
phaseManager.unshiftNew("AttemptRunPhase");
|
||||
}
|
||||
break;
|
||||
|
@ -1,32 +1,20 @@
|
||||
import type { PlayerPokemon, EnemyPokemon } from "#app/field/pokemon";
|
||||
import type { NumberHolder } from "#app/utils/common";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import { Stat } from "#enums/stat";
|
||||
|
||||
import Overrides from "#app/overrides";
|
||||
/**
|
||||
* Calculates the chance for the player's team to successfully run away from battle.
|
||||
* Calculate the chance for the player's team to successfully run away from battle.
|
||||
*
|
||||
* @param playerField The player's currently active Pokémon
|
||||
* @param enemyField The enemy team's Pokémon
|
||||
* @param escapeChance A mutable NumberHolder to be assigned the final escape chance value
|
||||
* @param escapeAttempts The number of previous escape attempts made in the 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(
|
||||
playerField: PlayerPokemon[],
|
||||
enemyField: EnemyPokemon[],
|
||||
escapeChance: NumberHolder,
|
||||
escapeAttempts: number,
|
||||
) {
|
||||
/** 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,
|
||||
);
|
||||
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.
|
||||
@ -44,10 +32,8 @@ export function calculateEscapeChance(
|
||||
* 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
|
||||
}
|
||||
/** 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;
|
||||
@ -62,8 +48,13 @@ export function calculateEscapeChance(
|
||||
/** 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`]
|
||||
escapeChance.value = Phaser.Math.Clamp(
|
||||
return Phaser.Math.Clamp(
|
||||
Math.round(escapeSlope * speedRatio + minChance + escapeBonus * escapeAttempts),
|
||||
minChance,
|
||||
maxChance,
|
||||
|
@ -6,6 +6,7 @@ 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 Overrides from "#app/overrides";
|
||||
|
||||
describe("Abilities - Honey Gather", () => {
|
||||
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
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
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;
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
@ -4,10 +4,11 @@ import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
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 { Command } from "#enums/command";
|
||||
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||
import Overrides from "#app/overrides";
|
||||
|
||||
describe("Abilities - Speed Boost", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -96,12 +97,15 @@ describe("Abilities - Speed Boost", () => {
|
||||
});
|
||||
|
||||
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]);
|
||||
|
||||
vi.spyOn(Overrides, "RUN_SUCCESS_OVERRIDE", "get").mockReturnValue(false);
|
||||
|
||||
const commandPhase = game.scene.phaseManager.getCurrentPhase() as CommandPhase;
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
const runPhase = game.scene.phaseManager.getCurrentPhase() as AttemptRunPhase;
|
||||
runPhase.forceFailEscape = true;
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase);
|
||||
await game.toNextTurn();
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
|
||||
import type { CommandPhase } from "#app/phases/command-phase";
|
||||
import { Command } from "#enums/command";
|
||||
import { NumberHolder } from "#app/utils/common";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
@ -45,7 +44,6 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
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
|
||||
const escapeChances: {
|
||||
@ -91,8 +89,8 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
||||
]);
|
||||
calculateEscapeChance(playerPokemon, enemyField, escapePercentage, game.scene.currentBattle.escapeAttempts);
|
||||
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
expect(chance).toBe(escapeChances[i].expectedEscapeChance);
|
||||
}
|
||||
});
|
||||
|
||||
@ -117,7 +115,6 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
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
|
||||
const escapeChances: {
|
||||
@ -171,9 +168,9 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
||||
]);
|
||||
calculateEscapeChance(playerPokemon, enemyField, escapePercentage, game.scene.currentBattle.escapeAttempts);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
// 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
|
||||
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
|
||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
|
||||
@ -195,7 +192,6 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
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
|
||||
const escapeChances: {
|
||||
@ -254,8 +250,8 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * enemySpeed,
|
||||
]);
|
||||
calculateEscapeChance(playerPokemon, enemyField, escapePercentage, game.scene.currentBattle.escapeAttempts);
|
||||
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
expect(chance).toBe(escapeChances[i].expectedEscapeChance);
|
||||
}
|
||||
});
|
||||
|
||||
@ -280,7 +276,6 @@ describe("Escape chance calculations", () => {
|
||||
commandPhase.handleCommand(Command.RUN, 0);
|
||||
|
||||
await game.phaseInterceptor.to(AttemptRunPhase, false);
|
||||
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
|
||||
const escapeChances: {
|
||||
@ -346,9 +341,9 @@ describe("Escape chance calculations", () => {
|
||||
20,
|
||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
|
||||
]);
|
||||
calculateEscapeChance(playerPokemon, enemyField, escapePercentage, game.scene.currentBattle.escapeAttempts);
|
||||
const chance = calculateEscapeChance(game.scene.currentBattle.escapeAttempts);
|
||||
// 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
|
||||
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
|
||||
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
|
||||
|
Loading…
Reference in New Issue
Block a user