mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-27 01:06:09 +00:00
start adding unit tests
This commit is contained in:
parent
07ffe1ca64
commit
f803de6b23
@ -1,14 +1,14 @@
|
||||
# 📝 Most immediate things to-do list
|
||||
|
||||
- ### High priority
|
||||
- 🐛 Intimidate and other ETB abilities proc twice at the start of wild MEs (fight or flight, dark deal)
|
||||
- ⚙️ Add a tag system so MEs don't show where they shouldn't and bricking Challenge runs:
|
||||
- ⚙️ Add a tag system so MEs to filter or change spawn rates in Challenge runs:
|
||||
- noChallenge (cant be spawned in challenge runs)
|
||||
- allChallenge (can spawn in all challenge modes)
|
||||
- (typespecific)Challenge:
|
||||
- Example: fireOnly (can only spawn in fire related challenges)
|
||||
|
||||
- ### Medium priority
|
||||
- ⚙️ Update Chest visuals for Mysterious Chest (with animated chest)
|
||||
|
||||
- ### Low priority
|
||||
- 🐛 Mysterious Challengers can spawn two trainers (or three) of the same type [Dev comment: not a bug]
|
||||
@ -106,9 +106,9 @@ Events (referred to as 'Mysterious Encounters, MEs' in the code) aim to be an ad
|
||||
|
||||
### 🌟 **Rarity** tier of the ME, common by default.
|
||||
- ⚪ Common pool
|
||||
- 🔵 Rare pool
|
||||
- 🟣 Epic pool
|
||||
- 🟡 Legendary pool
|
||||
- 🔵 Uncommon pool
|
||||
- 🟣 Rare pool
|
||||
- 🟡 Super Rare pool
|
||||
|
||||
### **Optional Requirements** for Mystery Encounters.
|
||||
- 🛠️ They give granular control over whether encounters will spawn in certain situations
|
||||
@ -135,13 +135,10 @@ Events (referred to as 'Mysterious Encounters, MEs' in the code) aim to be an ad
|
||||
|
||||
# 📝 Known bugs (squash 'em all!):
|
||||
- ## 🔴 __**Really bad ones**__
|
||||
- 🐛 Picking up certain items in Fight or Flight is still broken. Workaround is leave encounter.
|
||||
- 🐛 Modifiers that are applied to pokemon get skipped in Fight or Flight.
|
||||
|
||||
- ## 🟡 __**Bad ones under certain circumstances**__
|
||||
- 🐛 Needs further replication : At wave 51, wild PKMN encounter caused a freezed after pressing "ESC" key upon being asked to switch PKMNs
|
||||
- 🐛 Wave seed generates different encounter data if you roll to a new wave, see the spawned stuff, and refresh the app
|
||||
- 🐛 Type-buffing items (like Silk Scarf) get swapped around when offered as a reward in Fight or Flight
|
||||
|
||||
- ## 🟢 __**Non-game breaking**__
|
||||
- Both of these bugs seem to have in common that they don't "forget" their last passed string:
|
||||
@ -157,7 +154,6 @@ Events (referred to as 'Mysterious Encounters, MEs' in the code) aim to be an ad
|
||||
#### More requirements (with helper functions)
|
||||
- Having X item
|
||||
- Having Y amount of X item
|
||||
- Being in a specific Biome
|
||||
- A Pokémon X in player's party can learn Y move
|
||||
- A Pokémon X in player's party knows Y move
|
||||
- A Pokémon X in player's party has Y ability
|
||||
|
@ -93,7 +93,7 @@ import { UiTheme } from "#enums/ui-theme";
|
||||
import { TimedEventManager } from "#app/timed-event-manager.js";
|
||||
import i18next from "i18next";
|
||||
import MysteryEncounter, { MysteryEncounterTier, MysteryEncounterVariant } from "./data/mystery-encounter";
|
||||
import { mysteryEncountersByBiome, allMysteryEncounters, BASE_MYSTERY_ENCOUNTER_WEIGHT, AVERAGE_ENCOUNTERS_PER_RUN_TARGET } from "./data/mystery-encounters/mystery-encounters";
|
||||
import { mysteryEncountersByBiome, allMysteryEncounters, BASE_MYSTERY_ENCOUNTER_WEIGHT, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
|
||||
import { MysteryEncounterFlags } from "#app/data/mystery-encounter-flags";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
||||
@ -1098,22 +1098,22 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
// Check for mystery encounter
|
||||
// Can only occur in place of a standard wild battle, waves 10-180
|
||||
// let testStartingWeight = 10;
|
||||
// while (testStartingWeight < 30) {
|
||||
// let testStartingWeight = 0;
|
||||
// while (testStartingWeight < 20) {
|
||||
// calculateMEAggregateStats(this, testStartingWeight);
|
||||
// testStartingWeight += 2;
|
||||
// testStartingWeight += 1;
|
||||
// }
|
||||
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < 180 && newWaveIndex > 10) {
|
||||
const roll = Utils.randSeedInt(256);
|
||||
|
||||
// Base spawn weight is 3/256, and increases by 1/256 for each missed attempt at spawning an encounter on a valid floor
|
||||
const sessionEncounterRate = !isNullOrUndefined(this.mysteryEncounterFlags?.encounterSpawnChance) ? this.mysteryEncounterFlags.encounterSpawnChance : BASE_MYSTERY_ENCOUNTER_WEIGHT;
|
||||
// Base spawn weight is 1/256, and increases by 5/256 for each missed attempt at spawning an encounter on a valid floor
|
||||
const sessionEncounterRate = !isNullOrUndefined(this.mysteryEncounterData?.encounterSpawnChance) ? this.mysteryEncounterData.encounterSpawnChance : BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
|
||||
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn
|
||||
// Do the reverse as well
|
||||
// Reduces occurrence of runs with very few (<6) and a ton (>10) of encounters
|
||||
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (180 - 10) * newWaveIndex;
|
||||
const currentRunDiffFromAvg = expectedEncountersByFloor - (this.mysteryEncounterFlags?.encounteredEvents?.length || 0);
|
||||
const currentRunDiffFromAvg = expectedEncountersByFloor - (this.mysteryEncounterData?.encounteredEvents?.length || 0);
|
||||
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * 5;
|
||||
|
||||
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE;
|
||||
@ -1121,9 +1121,9 @@ export default class BattleScene extends SceneBase {
|
||||
if (roll < successRate) {
|
||||
newBattleType = BattleType.MYSTERY_ENCOUNTER;
|
||||
// Reset base spawn weight
|
||||
this.mysteryEncounterFlags.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_WEIGHT;
|
||||
this.mysteryEncounterData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
} else {
|
||||
this.mysteryEncounterFlags.encounterSpawnChance = sessionEncounterRate + 1;
|
||||
this.mysteryEncounterData.encounterSpawnChance = sessionEncounterRate + WIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1168,7 +1168,9 @@ export default class BattleScene extends SceneBase {
|
||||
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
|
||||
this.currentBattle.double = false;
|
||||
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounter);
|
||||
this.executeWithSeedOffset(() => {
|
||||
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounter);
|
||||
}, this.currentBattle.waveIndex << 4);
|
||||
}
|
||||
|
||||
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
|
||||
@ -2652,10 +2654,10 @@ export default class BattleScene extends SceneBase {
|
||||
}
|
||||
|
||||
// Check for queued encounters first
|
||||
if (!encounter && this.mysteryEncounterFlags?.nextEncounterQueue?.length > 0) {
|
||||
if (!encounter && this.mysteryEncounterData?.nextEncounterQueue?.length > 0) {
|
||||
let i = 0;
|
||||
while (i < this.mysteryEncounterFlags.nextEncounterQueue.length && !!encounter) {
|
||||
const candidate = this.mysteryEncounterFlags.nextEncounterQueue[i];
|
||||
while (i < this.mysteryEncounterData.nextEncounterQueue.length && !!encounter) {
|
||||
const candidate = this.mysteryEncounterData.nextEncounterQueue[i];
|
||||
const forcedChance = candidate[1];
|
||||
if (Utils.randSeedInt(100) < forcedChance) {
|
||||
encounter = allMysteryEncounters[candidate[0]];
|
||||
@ -2675,7 +2677,7 @@ export default class BattleScene extends SceneBase {
|
||||
const tierWeights = [61, 40, 21, 6];
|
||||
|
||||
// Adjust tier weights by previously encountered events to lower odds of only common/uncommons in run
|
||||
this.mysteryEncounterFlags.encounteredEvents.forEach(val => {
|
||||
this.mysteryEncounterData.encounteredEvents.forEach(val => {
|
||||
const tier = val[1];
|
||||
if (tier === MysteryEncounterTier.COMMON) {
|
||||
tierWeights[0] = tierWeights[0] - 6;
|
||||
@ -2697,7 +2699,7 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
let availableEncounters = [];
|
||||
// New encounter will never be the same as the most recent encounter
|
||||
const previousEncounter = this.mysteryEncounterFlags.encounteredEvents?.length > 0 ? this.mysteryEncounterFlags.encounteredEvents[this.mysteryEncounterFlags.encounteredEvents.length - 1][0] : null;
|
||||
const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1][0] : null;
|
||||
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType);
|
||||
// If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available
|
||||
while (availableEncounters.length === 0 && tier >= 0) {
|
||||
|
@ -1,14 +1,14 @@
|
||||
import {MysteryEncounterTier} from "#app/data/mystery-encounter";
|
||||
import {MysteryEncounterType} from "#enums/mystery-encounter-type";
|
||||
import {BASE_MYSTERY_ENCOUNTER_WEIGHT} from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import {BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT} from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import {isNullOrUndefined} from "../utils";
|
||||
|
||||
export class MysteryEncounterFlags {
|
||||
export class MysteryEncounterData {
|
||||
encounteredEvents: [MysteryEncounterType, MysteryEncounterTier][] = [];
|
||||
encounterSpawnChance: number = BASE_MYSTERY_ENCOUNTER_WEIGHT;
|
||||
encounterSpawnChance: number = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
nextEncounterQueue: [MysteryEncounterType, integer][] = [];
|
||||
|
||||
constructor(flags: MysteryEncounterFlags) {
|
||||
constructor(flags: MysteryEncounterData) {
|
||||
if (!isNullOrUndefined(flags)) {
|
||||
Object.assign(this, flags);
|
||||
}
|
@ -63,11 +63,11 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement {
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
return scene.mysteryEncounterFlags.encounteredEvents.some(e => e[0] === this.previousEncounterRequirement);
|
||||
return scene.mysteryEncounterData.encounteredEvents.some(e => e[0] === this.previousEncounterRequirement);
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
return ["previousEncounter", scene.mysteryEncounterFlags.encounteredEvents.find(e => e[0] === this.previousEncounterRequirement)[0].toString()];
|
||||
return ["previousEncounter", scene.mysteryEncounterData.encounteredEvents.find(e => e[0] === this.previousEncounterRequirement)[0].toString()];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,10 +27,10 @@ import {BattlerTagType} from "#enums/battler-tag-type";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import {Biome} from "#enums/biome";
|
||||
import {biomeLinks} from "#app/data/biomes";
|
||||
import {EncounterSceneRequirement} from "#app/data/mystery-encounter-requirements";
|
||||
import {Mode} from "#app/ui/ui";
|
||||
import {PartyOption, PartyUiMode} from "#app/ui/party-ui-handler";
|
||||
import {OptionSelectConfig, OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler";
|
||||
import {WIGHT_INCREMENT_ON_SPAWN_MISS} from "#app/data/mystery-encounters/mystery-encounters";
|
||||
|
||||
/**
|
||||
*
|
||||
@ -62,15 +62,15 @@ export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: bo
|
||||
return chosenPokemon;
|
||||
}
|
||||
|
||||
export function getTokensFromScene(scene: BattleScene, reqs: EncounterSceneRequirement[]): Array<[RegExp, String]> {
|
||||
const arr = [];
|
||||
if (scene) {
|
||||
for (const req of reqs) {
|
||||
req.getDialogueToken(scene);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
// export function getTokensFromScene(scene: BattleScene, reqs: EncounterSceneRequirement[]): Array<[RegExp, String]> {
|
||||
// const arr = [];
|
||||
// if (scene) {
|
||||
// for (const req of reqs) {
|
||||
// req.getDialogueToken(scene);
|
||||
// }
|
||||
// }
|
||||
// return arr;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
@ -114,6 +114,48 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boole
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* NOTE: This returns ANY random species, including those locked behind eggs, etc.
|
||||
* @param starterTiers
|
||||
* @param excludedSpecies
|
||||
* @param types
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
||||
let min = starterTiers instanceof Array ? starterTiers[0] : starterTiers;
|
||||
let max = starterTiers instanceof Array ? starterTiers[1] : starterTiers;
|
||||
|
||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
||||
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
|
||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||
|
||||
if (!isNullOrUndefined(types) && types.length > 0) {
|
||||
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || types.includes(s[0].type2));
|
||||
}
|
||||
|
||||
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
||||
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
|
||||
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
|
||||
while (tryFilterStarterTiers.length === 0 && (min !== 0 && max !== 10)) {
|
||||
if (min > 0) {
|
||||
min--;
|
||||
} else {
|
||||
max++;
|
||||
}
|
||||
|
||||
tryFilterStarterTiers = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
|
||||
}
|
||||
|
||||
if (tryFilterStarterTiers.length > 0) {
|
||||
const index = Utils.randSeedInt(tryFilterStarterTiers.length);
|
||||
return Phaser.Math.RND.shuffle(tryFilterStarterTiers)[index][0].speciesId;
|
||||
}
|
||||
|
||||
return Species.BULBASAUR;
|
||||
}
|
||||
|
||||
export function koPlayerPokemon(pokemon: PlayerPokemon) {
|
||||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
@ -173,48 +215,6 @@ export function showEncounterDialogue(scene: BattleScene, textContentKey: Templa
|
||||
scene.ui.showDialogue(text, speaker, null, callback, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* NOTE: This returns ANY random species, including those locked behind eggs, etc.
|
||||
* @param starterTiers
|
||||
* @param excludedSpecies
|
||||
* @param types
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
||||
let min = starterTiers instanceof Array ? starterTiers[0] : starterTiers;
|
||||
let max = starterTiers instanceof Array ? starterTiers[1] : starterTiers;
|
||||
|
||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
||||
.filter(s => getPokemonSpecies(s[0]) && !excludedSpecies.includes(s[0]))
|
||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||
|
||||
if (!isNullOrUndefined(types) && types.length > 0) {
|
||||
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || types.includes(s[0].type2));
|
||||
}
|
||||
|
||||
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
||||
// Starts by decrementing starter tier min until it is 0, then increments tier max up to 10
|
||||
let tryFilterStarterTiers: [PokemonSpecies, number][] = filteredSpecies.filter(s => (s[1] >= min && s[1] <= max));
|
||||
while (tryFilterStarterTiers.length === 0 && (min !== 0 && max !== 10)) {
|
||||
if (min > 0) {
|
||||
min--;
|
||||
} else {
|
||||
max++;
|
||||
}
|
||||
|
||||
tryFilterStarterTiers = filteredSpecies.filter(s => s[1] >= min && s[1] <= max);
|
||||
}
|
||||
|
||||
if (tryFilterStarterTiers.length > 0) {
|
||||
const index = Utils.randSeedInt(tryFilterStarterTiers.length);
|
||||
return Phaser.Math.RND.shuffle(tryFilterStarterTiers)[index][0].speciesId;
|
||||
}
|
||||
|
||||
return Species.BULBASAUR;
|
||||
}
|
||||
|
||||
export class EnemyPokemonConfig {
|
||||
species: PokemonSpecies;
|
||||
isBoss: boolean = false;
|
||||
@ -594,10 +594,10 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
|
||||
export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: number) {
|
||||
const numRuns = 1000;
|
||||
let run = 0;
|
||||
const targetEncountersPerRun = 15;
|
||||
const targetEncountersPerRun = 15; // AVERAGE_ENCOUNTERS_PER_RUN_TARGET
|
||||
|
||||
const calculateNumEncounters = (): number[] => {
|
||||
let encounterRate = baseSpawnWeight;
|
||||
let encounterRate = baseSpawnWeight; // BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT
|
||||
const numEncounters = [0, 0, 0, 0];
|
||||
let currentBiome = Biome.TOWN;
|
||||
let currentArena = scene.newArena(currentBiome);
|
||||
@ -669,7 +669,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
||||
|
||||
tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
|
||||
} else {
|
||||
encounterRate++;
|
||||
encounterRate += WIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,9 @@ import { Biome } from "#app/enums/biome";
|
||||
import { SleepingSnorlaxEncounter } from "./sleeping-snorlax";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
||||
export const BASE_MYSTERY_ENCOUNTER_WEIGHT = 19;
|
||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||
export const WIGHT_INCREMENT_ON_SPAWN_MISS = 5;
|
||||
export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 15;
|
||||
|
||||
export const allMysteryEncounters : {[encounterType:string]: MysteryEncounter} = {};
|
||||
|
@ -1,6 +1,8 @@
|
||||
import {SimpleTranslationEntries} from "#app/interfaces/locales";
|
||||
|
||||
export const mysteryEncounter: SimpleTranslationEntries = {
|
||||
// DO NOT REMOVE
|
||||
"unit_test_dialogue": "@ec{test}@ec{test} @ec{test@ec{test}} @ec{test1} @ec{test\} @ec{test\\} @ec{test\\\} {test}",
|
||||
|
||||
// Mysterious Encounters -- Common Tier
|
||||
|
||||
|
@ -817,7 +817,7 @@ export class EncounterPhase extends BattlePhase {
|
||||
if (mysteryEncounter.onInit) {
|
||||
mysteryEncounter.onInit(this.scene);
|
||||
}
|
||||
mysteryEncounter.populateDialogueTokensFromRequirements();
|
||||
mysteryEncounter.populateDialogueTokensFromRequirements(this.scene);
|
||||
}, this.scene.currentBattle.waveIndex);
|
||||
|
||||
// Add intro visuals for mystery encounter
|
||||
|
@ -40,7 +40,7 @@ export class MysteryEncounterPhase extends Phase {
|
||||
|
||||
// Sets flag that ME was encountered
|
||||
// Can be used in later MEs to check for requirements to spawn, etc.
|
||||
this.scene.mysteryEncounterFlags.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
|
||||
this.scene.mysteryEncounterData.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
|
||||
|
||||
// Initiates encounter dialogue window and option select
|
||||
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER);
|
||||
@ -55,7 +55,7 @@ export class MysteryEncounterPhase extends Phase {
|
||||
}
|
||||
|
||||
// Populate dialogue tokens for option requirements
|
||||
this.scene.currentBattle.mysteryEncounter.populateDialogueTokensFromRequirements();
|
||||
this.scene.currentBattle.mysteryEncounter.populateDialogueTokensFromRequirements(this.scene);
|
||||
|
||||
if (option.onPreOptionPhase) {
|
||||
this.scene.executeWithSeedOffset(async () => {
|
||||
|
@ -40,7 +40,7 @@ import { GameDataType } from "#enums/game-data-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { Species } from "#enums/species";
|
||||
import { MysteryEncounterFlags } from "../data/mystery-encounter-flags";
|
||||
import { MysteryEncounterData } from "../data/mystery-encounter-data";
|
||||
import MysteryEncounter from "../data/mystery-encounter";
|
||||
|
||||
export const defaultStarterSpecies: Species[] = [
|
||||
@ -125,7 +125,7 @@ export interface SessionSaveData {
|
||||
timestamp: integer;
|
||||
challenges: ChallengeData[];
|
||||
mysteryEncounter: MysteryEncounter;
|
||||
mysteryEncounterFlags: MysteryEncounterFlags;
|
||||
mysteryEncounterFlags: MysteryEncounterData;
|
||||
}
|
||||
|
||||
interface Unlocks {
|
||||
@ -842,7 +842,7 @@ export class GameData {
|
||||
timestamp: new Date().getTime(),
|
||||
challenges: scene.gameMode.challenges.map(c => new ChallengeData(c)),
|
||||
mysteryEncounter: scene.currentBattle.mysteryEncounter,
|
||||
mysteryEncounterFlags: scene.mysteryEncounterFlags
|
||||
mysteryEncounterFlags: scene.mysteryEncounterData
|
||||
} as SessionSaveData;
|
||||
}
|
||||
|
||||
@ -933,7 +933,7 @@ export class GameData {
|
||||
scene.score = sessionData.score;
|
||||
scene.updateScoreText();
|
||||
|
||||
scene.mysteryEncounterFlags = sessionData?.mysteryEncounterFlags ? sessionData?.mysteryEncounterFlags : new MysteryEncounterFlags(null);
|
||||
scene.mysteryEncounterData = sessionData?.mysteryEncounterFlags ? sessionData?.mysteryEncounterFlags : new MysteryEncounterData(null);
|
||||
|
||||
scene.newArena(sessionData.arena.biome);
|
||||
|
||||
@ -1159,7 +1159,7 @@ export class GameData {
|
||||
}
|
||||
|
||||
if (k === "mysteryEncounterFlags") {
|
||||
return new MysteryEncounterFlags(v);
|
||||
return new MysteryEncounterData(v);
|
||||
}
|
||||
|
||||
return v;
|
||||
|
366
src/test/mystery-encounter/mystery-encounter-utils.test.ts
Normal file
366
src/test/mystery-encounter/mystery-encounter-utils.test.ts
Normal file
@ -0,0 +1,366 @@
|
||||
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import {
|
||||
getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon,
|
||||
getRandomPlayerPokemon, getRandomSpeciesByStarterTier, getTextWithEncounterDialogueTokens,
|
||||
koPlayerPokemon, queueEncounterMessage, showEncounterDialogue, showEncounterText,
|
||||
} from "#app/data/mystery-encounters/mystery-encounter-utils";
|
||||
import {initSceneWithoutEncounterPhase} from "#test/utils/gameManagerUtils";
|
||||
import {Species} from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import {StatusEffect} from "#app/data/status-effect";
|
||||
import MysteryEncounter from "#app/data/mystery-encounter";
|
||||
import {MessagePhase} from "#app/phases";
|
||||
import {getPokemonSpecies, speciesStarters} from "#app/data/pokemon-species";
|
||||
import {Type} from "#app/data/type";
|
||||
|
||||
describe("Mystery Encounter Utils", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
let scene: BattleScene;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
scene = game.scene;
|
||||
initSceneWithoutEncounterPhase(game.scene, [Species.ARCEUS, Species.MANAPHY]);
|
||||
// vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(256);
|
||||
// vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(11);
|
||||
});
|
||||
|
||||
describe("getRandomPlayerPokemon", () => {
|
||||
it("gets a random pokemon from player party", () => {
|
||||
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
|
||||
scene.waveSeed = "random";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
let result = getRandomPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
|
||||
scene.waveSeed = "random2";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
result = getRandomPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
});
|
||||
|
||||
it("gets a fainted pokemon from player party if isAllowedInBattle is false", () => {
|
||||
// Both pokemon fainted
|
||||
scene.getParty().forEach(p => {
|
||||
p.hp = 0;
|
||||
p.trySetStatus(StatusEffect.FAINT);
|
||||
p.updateInfo();
|
||||
});
|
||||
|
||||
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
|
||||
scene.waveSeed = "random";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
let result = getRandomPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
|
||||
scene.waveSeed = "random2";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
result = getRandomPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
});
|
||||
|
||||
it("gets an unfainted pokemon from player party if isAllowedInBattle is true", () => {
|
||||
// Only faint 1st pokemon
|
||||
const party = scene.getParty();
|
||||
party[0].hp = 0;
|
||||
party[0].trySetStatus(StatusEffect.FAINT);
|
||||
party[0].updateInfo();
|
||||
|
||||
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
|
||||
scene.waveSeed = "random";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
let result = getRandomPlayerPokemon(scene, true);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
|
||||
scene.waveSeed = "random2";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
result = getRandomPlayerPokemon(scene, true);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
});
|
||||
|
||||
it("returns last unfainted pokemon if doNotReturnLastAbleMon is false", () => {
|
||||
// Only faint 1st pokemon
|
||||
const party = scene.getParty();
|
||||
party[0].hp = 0;
|
||||
party[0].trySetStatus(StatusEffect.FAINT);
|
||||
party[0].updateInfo();
|
||||
|
||||
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
|
||||
scene.waveSeed = "random";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
let result = getRandomPlayerPokemon(scene, true, false);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
|
||||
scene.waveSeed = "random2";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
result = getRandomPlayerPokemon(scene, true, false);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
});
|
||||
|
||||
it("never returns last unfainted pokemon if doNotReturnLastAbleMon is true", () => {
|
||||
// Only faint 1st pokemon
|
||||
const party = scene.getParty();
|
||||
party[0].hp = 0;
|
||||
party[0].trySetStatus(StatusEffect.FAINT);
|
||||
party[0].updateInfo();
|
||||
|
||||
// Seeds are calculated to return index 0 first, 1 second (if both pokemon are legal)
|
||||
scene.waveSeed = "random";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
let result = getRandomPlayerPokemon(scene, true, true);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
|
||||
scene.waveSeed = "random2";
|
||||
Phaser.Math.RND.sow([ scene.waveSeed ]);
|
||||
scene.rngCounter = 0;
|
||||
|
||||
result = getRandomPlayerPokemon(scene, true, true);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHighestLevelPlayerPokemon", () => {
|
||||
it("gets highest level pokemon", () => {
|
||||
const party = scene.getParty();
|
||||
party[0].level = 100;
|
||||
|
||||
const result = getHighestLevelPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
});
|
||||
|
||||
it("gets highest level pokemon at different index", () => {
|
||||
const party = scene.getParty();
|
||||
party[1].level = 100;
|
||||
|
||||
const result = getHighestLevelPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
});
|
||||
|
||||
it("breaks ties by getting returning lower index", () => {
|
||||
const party = scene.getParty();
|
||||
party[0].level = 100;
|
||||
party[1].level = 100;
|
||||
|
||||
const result = getHighestLevelPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
});
|
||||
|
||||
it("returns highest level unfainted if unfainted is true", () => {
|
||||
const party = scene.getParty();
|
||||
party[0].level = 100;
|
||||
party[0].hp = 0;
|
||||
party[0].trySetStatus(StatusEffect.FAINT);
|
||||
party[0].updateInfo();
|
||||
party[1].level = 10;
|
||||
|
||||
const result = getHighestLevelPlayerPokemon(scene, true);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getLowestLevelPokemon", () => {
|
||||
it("gets lowest level pokemon", () => {
|
||||
const party = scene.getParty();
|
||||
party[0].level = 100;
|
||||
|
||||
const result = getLowestLevelPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
});
|
||||
|
||||
it("gets lowest level pokemon at different index", () => {
|
||||
const party = scene.getParty();
|
||||
party[1].level = 100;
|
||||
|
||||
const result = getLowestLevelPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
});
|
||||
|
||||
it("breaks ties by getting returning lower index", () => {
|
||||
const party = scene.getParty();
|
||||
party[0].level = 100;
|
||||
party[1].level = 100;
|
||||
|
||||
const result = getLowestLevelPlayerPokemon(scene);
|
||||
expect(result.species.speciesId).toBe(Species.ARCEUS);
|
||||
});
|
||||
|
||||
it("returns lowest level unfainted if unfainted is true", () => {
|
||||
const party = scene.getParty();
|
||||
party[0].level = 10;
|
||||
party[0].hp = 0;
|
||||
party[0].trySetStatus(StatusEffect.FAINT);
|
||||
party[0].updateInfo();
|
||||
party[1].level = 100;
|
||||
|
||||
const result = getLowestLevelPlayerPokemon(scene, true);
|
||||
expect(result.species.speciesId).toBe(Species.MANAPHY);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRandomSpeciesByStarterTier", () => {
|
||||
it("gets species for a starter tier", () => {
|
||||
const result = getRandomSpeciesByStarterTier(5);
|
||||
const pokeSpecies = getPokemonSpecies(result);
|
||||
|
||||
expect(pokeSpecies.speciesId).toBe(result);
|
||||
expect(speciesStarters[result]).toBe(5);
|
||||
});
|
||||
|
||||
it("gets species for a starter tier range", () => {
|
||||
const result = getRandomSpeciesByStarterTier([5, 8]);
|
||||
const pokeSpecies = getPokemonSpecies(result);
|
||||
|
||||
expect(pokeSpecies.speciesId).toBe(result);
|
||||
expect(speciesStarters[result]).toBeGreaterThanOrEqual(5);
|
||||
expect(speciesStarters[result]).toBeLessThanOrEqual(8);
|
||||
});
|
||||
|
||||
it("excludes species from search", () => {
|
||||
// Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian
|
||||
const result = getRandomSpeciesByStarterTier(9, [Species.KORAIDON, Species.MIRAIDON, Species.ARCEUS, Species.RAYQUAZA, Species.KYOGRE, Species.GROUDON]);
|
||||
const pokeSpecies = getPokemonSpecies(result);
|
||||
expect(pokeSpecies.speciesId).toBe(Species.ZACIAN);
|
||||
});
|
||||
|
||||
it("gets species of specified types", () => {
|
||||
// Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian
|
||||
const result = getRandomSpeciesByStarterTier(9, null, [Type.GROUND]);
|
||||
const pokeSpecies = getPokemonSpecies(result);
|
||||
expect(pokeSpecies.speciesId).toBe(Species.GROUDON);
|
||||
});
|
||||
});
|
||||
|
||||
describe("koPlayerPokemon", () => {
|
||||
it("KOs a pokemon", () => {
|
||||
const party = scene.getParty();
|
||||
const arceus = party[0];
|
||||
arceus.hp = 100;
|
||||
expect(arceus.isAllowedInBattle()).toBe(true);
|
||||
|
||||
koPlayerPokemon(arceus);
|
||||
expect(arceus.isAllowedInBattle()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTextWithEncounterDialogueTokens", () => {
|
||||
it("injects dialogue tokens", () => {
|
||||
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
|
||||
|
||||
const result = getTextWithEncounterDialogueTokens(scene, "mysteryEncounter:unit_test_dialogue");
|
||||
expect(result).toEqual("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}");
|
||||
});
|
||||
|
||||
it("can perform nested dialogue token injection", () => {
|
||||
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new");
|
||||
|
||||
const result = getTextWithEncounterDialogueTokens(scene, "mysteryEncounter:unit_test_dialogue");
|
||||
expect(result).toEqual("valuevalue new @ec{test1} value @ec{test\\} @ec{test\\} {test}");
|
||||
});
|
||||
});
|
||||
|
||||
describe("queueEncounterMessage", () => {
|
||||
it("queues a message with encounter dialogue tokens", async () => {
|
||||
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
|
||||
const spy = vi.spyOn(game.scene, "queueMessage");
|
||||
const phaseSpy = vi.spyOn(game.scene, "unshiftPhase");
|
||||
|
||||
queueEncounterMessage(scene, "mysteryEncounter:unit_test_dialogue");
|
||||
expect(spy).toHaveBeenCalledWith("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", null, true);
|
||||
expect(phaseSpy).toHaveBeenCalledWith(expect.any(MessagePhase));
|
||||
});
|
||||
});
|
||||
|
||||
describe("showEncounterText", () => {
|
||||
it("showText with dialogue tokens", async () => {
|
||||
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
|
||||
const spy = vi.spyOn(game.scene.ui, "showText");
|
||||
|
||||
showEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
|
||||
expect(spy).toHaveBeenCalledWith("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", null, expect.any(Function), 0, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("showEncounterDialogue", () => {
|
||||
it("showText with dialogue tokens", async () => {
|
||||
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
|
||||
const spy = vi.spyOn(game.scene.ui, "showDialogue");
|
||||
|
||||
showEncounterDialogue(scene, "mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue");
|
||||
expect(spy).toHaveBeenCalledWith("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", "valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", null, undefined, 0, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initBattleWithEnemyConfig", () => {
|
||||
it("", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("setCustomEncounterRewards", () => {
|
||||
it("", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectPokemonForOption", () => {
|
||||
it("", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("setEncounterExp", () => {
|
||||
it("", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("leaveEncounterWithoutBattle", () => {
|
||||
it("", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleMysteryEncounterVictory", () => {
|
||||
it("", () => {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,11 @@
|
||||
import {afterEach, beforeAll, beforeEach, describe, it, vi} from "vitest";
|
||||
import {afterEach, beforeAll, beforeEach, expect, describe, it, vi} from "vitest";
|
||||
import * as overrides from "../../overrides";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import {Species} from "#enums/species";
|
||||
import {MysteryEncounterPhase} from "#app/phases/mystery-encounter-phase";
|
||||
|
||||
describe("Mystery Encounter", () => {
|
||||
describe("Mystery Encounters", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
@ -19,17 +21,29 @@ describe("Mystery Encounter", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(64);
|
||||
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
|
||||
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(256);
|
||||
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(11);
|
||||
});
|
||||
|
||||
it("spawns a mystery encounter", async() => {
|
||||
// await game.runToSummon([
|
||||
// Species.CHARIZARD,
|
||||
// Species.VOLCARONA
|
||||
// ]);
|
||||
// expect(game.scene.getCurrentPhase().constructor.name).toBe(EncounterPhase.name);
|
||||
}, 100000);
|
||||
it("Spawns a mystery encounter", async() => {
|
||||
await game.runToMysteryEncounter([
|
||||
Species.CHARIZARD,
|
||||
Species.VOLCARONA
|
||||
]);
|
||||
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
});
|
||||
|
||||
it("", async() => {
|
||||
await game.runToMysteryEncounter([
|
||||
Species.CHARIZARD,
|
||||
Species.VOLCARONA
|
||||
]);
|
||||
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
});
|
||||
|
||||
it("spawns mysterious challengers encounter", async() => {
|
||||
});
|
||||
|
159
src/test/phases/mystery-encounter-phase.test.ts
Normal file
159
src/test/phases/mystery-encounter-phase.test.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import {afterEach, beforeAll, beforeEach, expect, describe, it, vi} from "vitest";
|
||||
import * as overrides from "../../overrides";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import {Species} from "#enums/species";
|
||||
import {MysteryEncounterOptionSelectedPhase, MysteryEncounterPhase} from "#app/phases/mystery-encounter-phase";
|
||||
import {Mode} from "#app/ui/ui";
|
||||
import {Button} from "#enums/buttons";
|
||||
import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
|
||||
import {MysteryEncounterType} from "#enums/mystery-encounter-type";
|
||||
import {MysteryEncounterTier} from "#app/data/mystery-encounter";
|
||||
|
||||
describe("Mystery Encounter Phases", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(256);
|
||||
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(11);
|
||||
});
|
||||
|
||||
describe("MysteryEncounterPhase", () => {
|
||||
it("Runs to MysteryEncounterPhase", async() => {
|
||||
await game.runToMysteryEncounter([
|
||||
Species.CHARIZARD,
|
||||
Species.VOLCARONA
|
||||
]);
|
||||
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
});
|
||||
|
||||
it("Runs MysteryEncounterPhase", async() => {
|
||||
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||
await game.runToMysteryEncounter([
|
||||
Species.CHARIZARD,
|
||||
Species.VOLCARONA
|
||||
]);
|
||||
|
||||
game.onNextPrompt("MysteryEncounterPhase", Mode.MYSTERY_ENCOUNTER, () => {
|
||||
// End phase early for test
|
||||
game.phaseInterceptor.superEndPhase();
|
||||
});
|
||||
await game.phaseInterceptor.run(MysteryEncounterPhase);
|
||||
|
||||
expect(game.scene.mysteryEncounterData.encounteredEvents.length).toBeGreaterThan(0);
|
||||
expect(game.scene.mysteryEncounterData.encounteredEvents[0][0]).toEqual(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||
expect(game.scene.mysteryEncounterData.encounteredEvents[0][1]).toEqual(MysteryEncounterTier.UNCOMMON);
|
||||
expect(game.scene.ui.getMode()).toBe(Mode.MYSTERY_ENCOUNTER);
|
||||
});
|
||||
|
||||
it("Selects an option for MysteryEncounterPhase", async() => {
|
||||
vi.spyOn(overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||
const dialogueSpy = vi.spyOn(game.scene.ui, "showDialogue");
|
||||
const messageSpy = vi.spyOn(game.scene.ui, "showText");
|
||||
await game.runToMysteryEncounter([
|
||||
Species.CHARIZARD,
|
||||
Species.VOLCARONA
|
||||
]);
|
||||
|
||||
game.onNextPrompt("MysteryEncounterPhase", Mode.MYSTERY_ENCOUNTER, () => {
|
||||
// Select option 1 for encounter
|
||||
const handler = game.scene.ui.getHandler() as MysteryEncounterUiHandler;
|
||||
handler.unblockInput();
|
||||
handler.processInput(Button.ACTION);
|
||||
});
|
||||
await game.phaseInterceptor.run(MysteryEncounterPhase);
|
||||
|
||||
// After option selected
|
||||
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterOptionSelectedPhase.name);
|
||||
expect(game.scene.ui.getMode()).toBe(Mode.MESSAGE);
|
||||
expect(dialogueSpy).toHaveBeenCalledTimes(1);
|
||||
expect(messageSpy).toHaveBeenCalledTimes(2);
|
||||
expect(dialogueSpy).toHaveBeenCalledWith("What's this?", "???", null, expect.any(Function));
|
||||
expect(messageSpy).toHaveBeenCalledWith("Mysterious challengers have appeared!", null, expect.any(Function), 750, true);
|
||||
expect(messageSpy).toHaveBeenCalledWith("The trainer steps forward...", null, expect.any(Function), 750, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MysteryEncounterOptionSelectedPhase", () => {
|
||||
it("runs phase", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles onOptionSelect execution", () => {
|
||||
|
||||
});
|
||||
|
||||
it("hides intro visuals", () => {
|
||||
|
||||
});
|
||||
|
||||
it("does not hide intro visuals if option disabled", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("MysteryEncounterBattlePhase", () => {
|
||||
it("runs phase", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles TRAINER_BATTLE variant", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles BOSS_BATTLE variant", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles WILD_BATTLE variant", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles double battle", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("MysteryEncounterRewardsPhase", () => {
|
||||
it("runs phase", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles doEncounterRewards", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles heal phase if enabled", () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("PostMysteryEncounterPhase", () => {
|
||||
it("runs phase", () => {
|
||||
|
||||
});
|
||||
|
||||
it("handles onPostOptionSelect execution", () => {
|
||||
|
||||
});
|
||||
|
||||
it("runs to next EncounterPhase", () => {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -33,6 +33,7 @@ import { Species } from "#enums/species";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { BattlerIndex } from "#app/battle.js";
|
||||
import TargetSelectUiHandler from "#app/ui/target-select-ui-handler.js";
|
||||
import BattleMessageUiHandler from "#app/ui/battle-message-ui-handler";
|
||||
|
||||
/**
|
||||
* Class to manage the game state and transitions between phases.
|
||||
@ -137,6 +138,30 @@ export default class GameManager {
|
||||
await this.phaseInterceptor.run(EncounterPhase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the game to a mystery encounter phase.
|
||||
* @param species - Optional array of species for party.
|
||||
* @returns A promise that resolves when the EncounterPhase ends.
|
||||
*/
|
||||
async runToMysteryEncounter(species?: Species[]) {
|
||||
await this.runToTitle();
|
||||
|
||||
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
this.scene.gameMode = getGameMode(GameModes.CLASSIC);
|
||||
const starters = generateStarter(this.scene, species);
|
||||
const selectStarterPhase = new SelectStarterPhase(this.scene);
|
||||
this.scene.pushPhase(new EncounterPhase(this.scene, false));
|
||||
selectStarterPhase.initBattle(starters);
|
||||
});
|
||||
|
||||
this.onNextPrompt("EncounterPhase", Mode.MESSAGE, () => {
|
||||
const handler = this.scene.ui.getHandler() as BattleMessageUiHandler;
|
||||
handler.processInput(Button.ACTION);
|
||||
}, null, true);
|
||||
|
||||
await this.phaseInterceptor.run(EncounterPhase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions to the start of a battle.
|
||||
* @param species - Optional array of species to start the battle with.
|
||||
|
@ -33,6 +33,10 @@ export default class MockContainer {
|
||||
// same as remove or destroy
|
||||
}
|
||||
|
||||
removeBetween(startIndex, endIndex, destroyChild) {
|
||||
// Removes multiple children across an index range
|
||||
}
|
||||
|
||||
addedToScene() {
|
||||
// This callback is invoked when this Game Object is added to a Scene.
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ export default class MockSprite {
|
||||
};
|
||||
this.anims = {
|
||||
pause: () => null,
|
||||
stop: () => null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
if (cursor === this.viewPartyIndex) {
|
||||
// Handle view party
|
||||
success = true;
|
||||
this.clear();
|
||||
// this.clear();
|
||||
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.CHECK, -1, () => {
|
||||
this.scene.ui.setMode(Mode.MYSTERY_ENCOUNTER, true);
|
||||
setTimeout(() => {
|
||||
@ -116,7 +116,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
} else {
|
||||
const selected = this.filteredEncounterOptions[cursor];
|
||||
if ((this.scene.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)) {
|
||||
this.clear();
|
||||
// this.clear();
|
||||
success = true;
|
||||
} else {
|
||||
ui.playError();
|
||||
|
Loading…
Reference in New Issue
Block a user