mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-25 16:26:25 +00:00
[Balance] Safeguard to prevent Paradox Pokemon spawning in ME's
This commit is contained in:
parent
a86afa6725
commit
695965f0df
46
src/data/balance/special-species-groups.ts
Normal file
46
src/data/balance/special-species-groups.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Species } from "#enums/species";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all {@link https://bulbapedia.bulbagarden.net/wiki/Paradox_Pok%C3%A9mon | Paradox Pokemon}, including the legendaries Miraidon and Koraidon.
|
||||||
|
*/
|
||||||
|
export const PARADOX_POKEMON = [
|
||||||
|
Species.GREAT_TUSK,
|
||||||
|
Species.SCREAM_TAIL,
|
||||||
|
Species.BRUTE_BONNET,
|
||||||
|
Species.FLUTTER_MANE,
|
||||||
|
Species.SLITHER_WING,
|
||||||
|
Species.SANDY_SHOCKS,
|
||||||
|
Species.ROARING_MOON,
|
||||||
|
Species.WALKING_WAKE,
|
||||||
|
Species.GOUGING_FIRE,
|
||||||
|
Species.RAGING_BOLT,
|
||||||
|
Species.IRON_TREADS,
|
||||||
|
Species.IRON_BUNDLE,
|
||||||
|
Species.IRON_HANDS,
|
||||||
|
Species.IRON_JUGULIS,
|
||||||
|
Species.IRON_MOTH,
|
||||||
|
Species.IRON_THORNS,
|
||||||
|
Species.IRON_VALIANT,
|
||||||
|
Species.IRON_LEAVES,
|
||||||
|
Species.IRON_BOULDER,
|
||||||
|
Species.IRON_CROWN,
|
||||||
|
Species.KORAIDON,
|
||||||
|
Species.MIRAIDON,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all {@link https://bulbapedia.bulbagarden.net/wiki/Ultra_Beast | Ultra Beasts}, NOT including legendaries such as Necrozma.
|
||||||
|
*/
|
||||||
|
export const ULTRA_BEASTS = [
|
||||||
|
Species.NIHILEGO,
|
||||||
|
Species.BUZZWOLE,
|
||||||
|
Species.PHEROMOSA,
|
||||||
|
Species.XURKITREE,
|
||||||
|
Species.CELESTEELA,
|
||||||
|
Species.KARTANA,
|
||||||
|
Species.GUZZLORD,
|
||||||
|
Species.POIPOLE,
|
||||||
|
Species.NAGANADEL,
|
||||||
|
Species.STAKATAKA,
|
||||||
|
Species.BLACEPHALON,
|
||||||
|
];
|
@ -9,7 +9,7 @@ import { EnemyPokemon } from "#app/field/pokemon";
|
|||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { IntegerHolder, randSeedInt } from "#app/utils";
|
import { IntegerHolder, randSeedInt } from "#app/utils";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
@ -19,6 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
|||||||
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
|
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
|
||||||
import { SummonPhase } from "#app/phases/summon-phase";
|
import { SummonPhase } from "#app/phases/summon-phase";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
|
import { PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounters/safariZone";
|
const namespace = "mysteryEncounters/safariZone";
|
||||||
@ -261,7 +262,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
|||||||
let enemySpecies;
|
let enemySpecies;
|
||||||
let pokemon;
|
let pokemon;
|
||||||
scene.executeWithSeedOffset(() => {
|
scene.executeWithSeedOffset(() => {
|
||||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false));
|
enemySpecies = getSafariSpeciesSpawn();
|
||||||
const level = scene.currentBattle.getLevelForWave();
|
const level = scene.currentBattle.getLevelForWave();
|
||||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
||||||
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
||||||
@ -526,3 +527,10 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) {
|
|||||||
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
|
||||||
|
*/
|
||||||
|
export function getSafariSpeciesSpawn(): PokemonSpecies {
|
||||||
|
return getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], PARADOX_POKEMON, undefined, false, false, false));
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ import BattleScene from "#app/battle-scene";
|
|||||||
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import PokemonSpecies, { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { speciesStarterCosts } from "#app/data/balance/starters";
|
import { speciesStarterCosts } from "#app/data/balance/starters";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { PokeballType } from "#enums/pokeball";
|
import { PokeballType } from "#enums/pokeball";
|
||||||
@ -17,6 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounters/thePokemonSalesman";
|
const namespace = "mysteryEncounters/thePokemonSalesman";
|
||||||
@ -60,12 +61,12 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
.withOnInit((scene: BattleScene) => {
|
.withOnInit((scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
|
|
||||||
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false));
|
let species = getSalesmanSpeciesOffer();
|
||||||
let tries = 0;
|
let tries = 0;
|
||||||
|
|
||||||
// Reroll any species that don't have HAs
|
// Reroll any species that don't have HAs
|
||||||
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
||||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], undefined, undefined, false, false, false));
|
species = getSalesmanSpeciesOffer();
|
||||||
tries++;
|
tries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,3 +165,10 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A random species that has at most 5 starter cost and is not Mythical, Paradox, etc.
|
||||||
|
*/
|
||||||
|
export function getSalesmanSpeciesOffer(): PokemonSpecies {
|
||||||
|
return getPokemonSpecies(getRandomSpeciesByStarterTier([ 0, 5 ], PARADOX_POKEMON, undefined, false, false, false));
|
||||||
|
}
|
||||||
|
173
src/test/mystery-encounter/encounters/safari-zone.test.ts
Normal file
173
src/test/mystery-encounter/encounters/safari-zone.test.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
import { Biome } from "#enums/biome";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||||
|
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||||
|
import { getSafariSpeciesSpawn, SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
|
||||||
|
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
|
||||||
|
|
||||||
|
const namespace = "mysteryEncounters/safariZone";
|
||||||
|
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||||
|
const defaultBiome = Biome.SWAMP;
|
||||||
|
const defaultWave = 45;
|
||||||
|
|
||||||
|
describe("Safari Zone - Mystery Encounter", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
let scene: BattleScene;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
scene = game.scene;
|
||||||
|
game.override.mysteryEncounterChance(100);
|
||||||
|
game.override.startingWave(defaultWave);
|
||||||
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves();
|
||||||
|
|
||||||
|
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
[ Biome.VOLCANO, [ MysteryEncounterType.FIGHT_OR_FLIGHT ]],
|
||||||
|
[ Biome.FOREST, [ MysteryEncounterType.SAFARI_ZONE ]],
|
||||||
|
[ Biome.SWAMP, [ MysteryEncounterType.SAFARI_ZONE ]],
|
||||||
|
[ Biome.JUNGLE, [ MysteryEncounterType.SAFARI_ZONE ]],
|
||||||
|
]);
|
||||||
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the correct properties", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.SAFARI_ZONE, defaultParty);
|
||||||
|
|
||||||
|
expect(SafariZoneEncounter.encounterType).toBe(MysteryEncounterType.SAFARI_ZONE);
|
||||||
|
expect(SafariZoneEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
|
||||||
|
expect(SafariZoneEncounter.dialogue).toBeDefined();
|
||||||
|
expect(SafariZoneEncounter.dialogue.intro).toStrictEqual([
|
||||||
|
{ text: `${namespace}:intro` },
|
||||||
|
]);
|
||||||
|
expect(SafariZoneEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||||
|
expect(SafariZoneEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}:description`);
|
||||||
|
expect(SafariZoneEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}:query`);
|
||||||
|
expect(SafariZoneEncounter.options.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not spawn outside of the forest, swamp, or jungle biomes", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
|
||||||
|
game.override.startingBiome(Biome.VOLCANO);
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.SAFARI_ZONE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize fully", async () => {
|
||||||
|
initSceneWithoutEncounterPhase(scene, defaultParty);
|
||||||
|
scene.currentBattle.mysteryEncounter = new MysteryEncounter(SafariZoneEncounter);
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
|
scene.currentBattle.waveIndex = defaultWave;
|
||||||
|
|
||||||
|
const { onInit } = encounter;
|
||||||
|
|
||||||
|
expect(encounter.onInit).toBeDefined();
|
||||||
|
|
||||||
|
encounter.populateDialogueTokensFromRequirements(scene);
|
||||||
|
const onInitResult = onInit!(scene);
|
||||||
|
expect(onInitResult).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 1 - Enter", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = SafariZoneEncounter.options[0];
|
||||||
|
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||||
|
expect(option.dialogue).toBeDefined();
|
||||||
|
expect(option.dialogue).toStrictEqual({
|
||||||
|
buttonLabel: `${namespace}:option.1.label`,
|
||||||
|
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}:option.1.selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should NOT be selectable if the player doesn't have enough money", async () => {
|
||||||
|
game.scene.money = 0;
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.SAFARI_ZONE, defaultParty);
|
||||||
|
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||||
|
|
||||||
|
const encounterPhase = scene.getCurrentPhase();
|
||||||
|
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||||
|
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||||
|
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||||
|
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||||
|
vi.spyOn(scene.ui, "playError");
|
||||||
|
|
||||||
|
await runSelectMysteryEncounterOption(game, 1);
|
||||||
|
|
||||||
|
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||||
|
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||||
|
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||||
|
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not spawn any Paradox Pokemon", async () => {
|
||||||
|
const NUM_ROLLS = 2000; // As long as this is greater than total number of species, this should cover all possible RNG rolls
|
||||||
|
let rngSweepProgress = 0; // Will simulate full range of RNG rolls by steadily increasing from 0 to 1
|
||||||
|
|
||||||
|
vi.spyOn(Phaser.Math.RND, "realInRange").mockImplementation((min: number, max: number) => {
|
||||||
|
return rngSweepProgress * (max - min) + min;
|
||||||
|
});
|
||||||
|
vi.spyOn(Phaser.Math.RND, "shuffle").mockImplementation((arr: any[]) => arr);
|
||||||
|
|
||||||
|
for (let i = 0; i < NUM_ROLLS; i++) {
|
||||||
|
rngSweepProgress = (2 * i + 1) / (2 * NUM_ROLLS);
|
||||||
|
const simSpecies = getSafariSpeciesSpawn().speciesId;
|
||||||
|
expect(PARADOX_POKEMON).not.toContain(simSpecies);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Tests for player actions inside the Safari Zone (Pokeball, Mud, Bait, Flee)
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 2 - Leave", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = SafariZoneEncounter.options[1];
|
||||||
|
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||||
|
expect(option.dialogue).toBeDefined();
|
||||||
|
expect(option.dialogue).toStrictEqual({
|
||||||
|
buttonLabel: `${namespace}:option.2.label`,
|
||||||
|
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}:option.2.selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave encounter without battle", async () => {
|
||||||
|
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.SAFARI_ZONE, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 2);
|
||||||
|
|
||||||
|
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -9,11 +9,12 @@ import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { PlayerPokemon } from "#app/field/pokemon";
|
import { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
import { ThePokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/the-pokemon-salesman-encounter";
|
import { getSalesmanSpeciesOffer, ThePokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/the-pokemon-salesman-encounter";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||||
|
import { PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
|
||||||
|
|
||||||
const namespace = "mysteryEncounters/thePokemonSalesman";
|
const namespace = "mysteryEncounters/thePokemonSalesman";
|
||||||
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||||
@ -172,6 +173,22 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
|||||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not spawn any Paradox Pokemon", async () => {
|
||||||
|
const NUM_ROLLS = 2000; // As long as this is greater than total number of species, this should cover all possible RNG rolls
|
||||||
|
let rngSweepProgress = 0; // Will simulate full range of RNG rolls by steadily increasing from 0 to 1
|
||||||
|
|
||||||
|
vi.spyOn(Phaser.Math.RND, "realInRange").mockImplementation((min: number, max: number) => {
|
||||||
|
return rngSweepProgress * (max - min) + min;
|
||||||
|
});
|
||||||
|
vi.spyOn(Phaser.Math.RND, "shuffle").mockImplementation((arr: any[]) => arr);
|
||||||
|
|
||||||
|
for (let i = 0; i < NUM_ROLLS; i++) {
|
||||||
|
rngSweepProgress = (2 * i + 1) / (2 * NUM_ROLLS);
|
||||||
|
const simSpecies = getSalesmanSpeciesOffer().speciesId;
|
||||||
|
expect(PARADOX_POKEMON).not.toContain(simSpecies);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("should leave encounter without battle", async () => {
|
it("should leave encounter without battle", async () => {
|
||||||
scene.money = 20000;
|
scene.money = 20000;
|
||||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||||
|
Loading…
Reference in New Issue
Block a user