Merge pull request #128 from AsdarDevelops/clowning-around
Clowning around
This commit is contained in:
commit
ed12d18205
File diff suppressed because it is too large
Load Diff
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"textures": [
|
|
||||||
{
|
|
||||||
"image": "encounter_radar.png",
|
|
||||||
"format": "RGBA8888",
|
|
||||||
"size": {
|
|
||||||
"w": 17,
|
|
||||||
"h": 16
|
|
||||||
},
|
|
||||||
"scale": 1,
|
|
||||||
"frames": [
|
|
||||||
{
|
|
||||||
"filename": "0001.png",
|
|
||||||
"rotated": false,
|
|
||||||
"trimmed": false,
|
|
||||||
"sourceSize": {
|
|
||||||
"w": 15,
|
|
||||||
"h": 14
|
|
||||||
},
|
|
||||||
"spriteSourceSize": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"w": 15,
|
|
||||||
"h": 14
|
|
||||||
},
|
|
||||||
"frame": {
|
|
||||||
"x": 1,
|
|
||||||
"y": 1,
|
|
||||||
"w": 15,
|
|
||||||
"h": 14
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"meta": {
|
|
||||||
"app": "https://www.codeandweb.com/texturepacker",
|
|
||||||
"version": "3.0",
|
|
||||||
"smartupdate": "$TexturePacker:SmartUpdate:eb3445f19546ab36edb2909c89b8aa86:c8de156a28ef70ee4ddf70cffe1ba3ba:e7008b81ccf0cb0325145a809afa6165$"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"textures": [
|
|
||||||
{
|
|
||||||
"image": "exclaim.png",
|
|
||||||
"format": "RGBA8888",
|
|
||||||
"size": {
|
|
||||||
"w": 32,
|
|
||||||
"h": 32
|
|
||||||
},
|
|
||||||
"scale": 1,
|
|
||||||
"frames": [
|
|
||||||
{
|
|
||||||
"filename": "0001.png",
|
|
||||||
"rotated": false,
|
|
||||||
"trimmed": false,
|
|
||||||
"sourceSize": {
|
|
||||||
"w": 32,
|
|
||||||
"h": 32
|
|
||||||
},
|
|
||||||
"spriteSourceSize": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"w": 32,
|
|
||||||
"h": 32
|
|
||||||
},
|
|
||||||
"frame": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"w": 32,
|
|
||||||
"h": 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"meta": {
|
|
||||||
"app": "https://www.codeandweb.com/texturepacker",
|
|
||||||
"version": "3.0",
|
|
||||||
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1430,7 +1430,7 @@ export default class BattleScene extends SceneBase {
|
||||||
const wave = waveIndex || this.currentBattle?.waveIndex || 0;
|
const wave = waveIndex || this.currentBattle?.waveIndex || 0;
|
||||||
this.waveSeed = Utils.shiftCharCodes(this.seed, wave);
|
this.waveSeed = Utils.shiftCharCodes(this.seed, wave);
|
||||||
Phaser.Math.RND.sow([ this.waveSeed ]);
|
Phaser.Math.RND.sow([ this.waveSeed ]);
|
||||||
// console.log("Wave Seed:", this.waveSeed, wave);
|
console.log("Wave Seed:", this.waveSeed, wave);
|
||||||
this.rngCounter = 0;
|
this.rngCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2760,7 +2760,7 @@ export default class BattleScene extends SceneBase {
|
||||||
const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.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) ?? [];
|
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
|
// If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available
|
||||||
while (availableEncounters.length === 0 && tier >= 0) {
|
while (availableEncounters.length === 0 && tier !== null) {
|
||||||
availableEncounters = biomeMysteryEncounters
|
availableEncounters = biomeMysteryEncounters
|
||||||
.filter((encounterType) => {
|
.filter((encounterType) => {
|
||||||
const encounterCandidate = allMysteryEncounters[encounterType];
|
const encounterCandidate = allMysteryEncounters[encounterType];
|
||||||
|
@ -2784,7 +2784,16 @@ export default class BattleScene extends SceneBase {
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.map((m) => (allMysteryEncounters[m]));
|
.map((m) => (allMysteryEncounters[m]));
|
||||||
tier--;
|
// Decrement tier
|
||||||
|
if (tier === MysteryEncounterTier.ROGUE) {
|
||||||
|
tier = MysteryEncounterTier.ULTRA;
|
||||||
|
} else if (tier === MysteryEncounterTier.ULTRA) {
|
||||||
|
tier = MysteryEncounterTier.GREAT;
|
||||||
|
} else if (tier === MysteryEncounterTier.GREAT) {
|
||||||
|
tier = MysteryEncounterTier.COMMON;
|
||||||
|
} else {
|
||||||
|
tier = null; // Ends loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If absolutely no encounters are available, spawn 0th encounter
|
// If absolutely no encounters are available, spawn 0th encounter
|
||||||
|
|
|
@ -110,7 +110,8 @@ export enum CommonAnim {
|
||||||
*/
|
*/
|
||||||
export enum EncounterAnim {
|
export enum EncounterAnim {
|
||||||
MAGMA_BG,
|
MAGMA_BG,
|
||||||
MAGMA_SPOUT
|
MAGMA_SPOUT,
|
||||||
|
SMOKESCREEN
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AnimConfig {
|
export class AnimConfig {
|
||||||
|
@ -533,16 +534,14 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
||||||
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
||||||
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
|
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
|
||||||
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
|
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
|
||||||
const encounterAnimIds = Utils.getEnumValues(EncounterAnim);
|
|
||||||
const encounterAnimFetches = [];
|
const encounterAnimFetches = [];
|
||||||
for (const anim of anims) {
|
for (const anim of anims) {
|
||||||
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
|
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const encounterAnimId = encounterAnimIds[anim];
|
|
||||||
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
|
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(cas => encounterAnims.set(encounterAnimId, new AnimConfig(cas))));
|
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))));
|
||||||
}
|
}
|
||||||
await Promise.allSettled(encounterAnimFetches);
|
await Promise.allSettled(encounterAnimFetches);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const AnOfferYouCantRefuseEncounter: IMysteryEncounter =
|
||||||
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
|
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
|
||||||
const price = scene.getWaveMoneyAmount(10);
|
const price = scene.getWaveMoneyAmount(10);
|
||||||
|
|
||||||
encounter.setDialogueToken("strongestPokemon", pokemon.name);
|
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
|
||||||
encounter.setDialogueToken("price", price.toString());
|
encounter.setDialogueToken("price", price.toString());
|
||||||
|
|
||||||
// Store pokemon and price
|
// Store pokemon and price
|
||||||
|
|
|
@ -0,0 +1,497 @@
|
||||||
|
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
|
||||||
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
|
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||||
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
import { randSeedInt, randSeedShuffle } from "#app/utils";
|
||||||
|
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
|
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
|
import { Ability } from "#app/data/ability";
|
||||||
|
import { BerryModifier } from "#app/modifier/modifier";
|
||||||
|
import { BerryType } from "#enums/berry-type";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
|
||||||
|
import { MoveCategory } from "#app/data/move";
|
||||||
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
|
||||||
|
/** the i18n namespace for the encounter */
|
||||||
|
const namespace = "mysteryEncounter:clowningAround";
|
||||||
|
|
||||||
|
const RANDOM_ABILITY_POOL = [
|
||||||
|
Abilities.STURDY,
|
||||||
|
Abilities.PICKUP,
|
||||||
|
Abilities.INTIMIDATE,
|
||||||
|
Abilities.GUTS,
|
||||||
|
Abilities.DROUGHT,
|
||||||
|
Abilities.DRIZZLE,
|
||||||
|
Abilities.SNOW_WARNING,
|
||||||
|
Abilities.SAND_STREAM,
|
||||||
|
Abilities.ELECTRIC_SURGE,
|
||||||
|
Abilities.PSYCHIC_SURGE,
|
||||||
|
Abilities.GRASSY_SURGE,
|
||||||
|
Abilities.MISTY_SURGE,
|
||||||
|
Abilities.MAGICIAN,
|
||||||
|
Abilities.SHEER_FORCE,
|
||||||
|
Abilities.PRANKSTER
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clowning Around encounter.
|
||||||
|
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69}
|
||||||
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||||
|
*/
|
||||||
|
export const ClowningAroundEncounter: IMysteryEncounter =
|
||||||
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
|
||||||
|
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||||
|
.withSceneWaveRangeRequirement(80, 180)
|
||||||
|
.withAnimations(EncounterAnim.SMOKESCREEN)
|
||||||
|
.withAutoHideIntroVisuals(false)
|
||||||
|
.withIntroSpriteConfigs([
|
||||||
|
{
|
||||||
|
spriteKey: Species.MR_MIME.toString(),
|
||||||
|
fileRoot: "pokemon",
|
||||||
|
hasShadow: true,
|
||||||
|
repeat: true,
|
||||||
|
x: -25,
|
||||||
|
tint: 0.3,
|
||||||
|
y: -3,
|
||||||
|
yShadow: -3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spriteKey: Species.BLACEPHALON.toString(),
|
||||||
|
fileRoot: "pokemon/exp",
|
||||||
|
hasShadow: true,
|
||||||
|
repeat: true,
|
||||||
|
x: 25,
|
||||||
|
tint: 0.3,
|
||||||
|
y: -3,
|
||||||
|
yShadow: -3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spriteKey: "harlequin",
|
||||||
|
fileRoot: "trainer",
|
||||||
|
hasShadow: true,
|
||||||
|
x: 0,
|
||||||
|
y: 2,
|
||||||
|
yShadow: 2
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withIntroDialogue([
|
||||||
|
{
|
||||||
|
text: `${namespace}.intro`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.intro_dialogue`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withOnInit((scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
|
||||||
|
const clownTrainerType = TrainerType.HARLEQUIN;
|
||||||
|
const clownConfig = trainerConfigs[clownTrainerType].copy();
|
||||||
|
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
|
||||||
|
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||||
|
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
|
||||||
|
clownConfig.setPartyTemplates(clownPartyTemplate);
|
||||||
|
clownConfig.setDoubleOnly();
|
||||||
|
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
||||||
|
|
||||||
|
// Generate random ability for Blacephalon from pool
|
||||||
|
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
||||||
|
encounter.setDialogueToken("ability", new Ability(ability, 3).name);
|
||||||
|
encounter.misc = { ability };
|
||||||
|
|
||||||
|
encounter.enemyPartyConfigs.push({
|
||||||
|
trainerConfig: clownConfig,
|
||||||
|
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon
|
||||||
|
{
|
||||||
|
species: getPokemonSpecies(Species.MR_MIME),
|
||||||
|
isBoss: true,
|
||||||
|
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC]
|
||||||
|
},
|
||||||
|
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
|
||||||
|
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||||
|
ability: ability,
|
||||||
|
mysteryEncounterData: new MysteryEncounterPokemonData(null, null, null, [randSeedInt(18), randSeedInt(18)]),
|
||||||
|
isBoss: true,
|
||||||
|
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
doubleBattle: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load animations/sfx for start of fight moves
|
||||||
|
loadCustomMovesForEncounter(scene, [Moves.ROLE_PLAY, Moves.TAUNT]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.withTitle(`${namespace}.title`)
|
||||||
|
.withDescription(`${namespace}.description`)
|
||||||
|
.withQuery(`${namespace}.query`)
|
||||||
|
.withOption(
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}.option.1.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.1.selected`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
// Spawn battle
|
||||||
|
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||||
|
|
||||||
|
setEncounterRewards(scene, { fillRemaining: true });
|
||||||
|
|
||||||
|
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
|
||||||
|
encounter.startOfBattleEffects.push(
|
||||||
|
{ // Mr. Mime copies the Blacephalon's random ability
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
|
targets: [BattlerIndex.ENEMY_2],
|
||||||
|
move: new PokemonMove(Moves.ROLE_PLAY),
|
||||||
|
ignorePp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||||
|
targets: [BattlerIndex.PLAYER],
|
||||||
|
move: new PokemonMove(Moves.TAUNT),
|
||||||
|
ignorePp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||||
|
targets: [BattlerIndex.PLAYER_2],
|
||||||
|
move: new PokemonMove(Moves.TAUNT),
|
||||||
|
ignorePp: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
|
await initBattleWithEnemyConfig(scene, config);
|
||||||
|
})
|
||||||
|
.withPostOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||||
|
// After the battle, offer the player the opportunity to permanently swap ability
|
||||||
|
const abilityWasSwapped = await handleSwapAbility(scene);
|
||||||
|
if (abilityWasSwapped) {
|
||||||
|
await showEncounterText(scene, `${namespace}.option.1.ability_gained`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play animations once ability swap is complete
|
||||||
|
// Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: scene.currentBattle.trainer,
|
||||||
|
x: "+=16",
|
||||||
|
y: "-=16",
|
||||||
|
alpha: 0,
|
||||||
|
ease: "Sine.easeInOut",
|
||||||
|
duration: 250
|
||||||
|
});
|
||||||
|
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon());
|
||||||
|
background.playWithoutTargets(scene, 230, 40, 2);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withOption(
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}.option.2.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected_2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected_3`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Swap player's items on pokemon with the most items
|
||||||
|
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
|
||||||
|
// So Vitamins, form change items, etc. are not included
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
|
||||||
|
const party = scene.getParty();
|
||||||
|
let mostHeldItemsPokemon = party[0];
|
||||||
|
let count = mostHeldItemsPokemon.getHeldItems()
|
||||||
|
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||||
|
.reduce((v, m) => v + m.stackCount, 0);
|
||||||
|
|
||||||
|
party.forEach(pokemon => {
|
||||||
|
const nextCount = pokemon.getHeldItems()
|
||||||
|
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||||
|
.reduce((v, m) => v + m.stackCount, 0);
|
||||||
|
if (nextCount > count) {
|
||||||
|
mostHeldItemsPokemon = pokemon;
|
||||||
|
count = nextCount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
|
||||||
|
|
||||||
|
const items = mostHeldItemsPokemon.getHeldItems();
|
||||||
|
|
||||||
|
// Shuffles Berries (if they have any)
|
||||||
|
let numBerries = 0;
|
||||||
|
items.filter(m => m instanceof BerryModifier)
|
||||||
|
.forEach(m => {
|
||||||
|
numBerries += m.stackCount;
|
||||||
|
scene.removeModifier(m);
|
||||||
|
});
|
||||||
|
|
||||||
|
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
|
||||||
|
|
||||||
|
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||||
|
let numUltra = 0;
|
||||||
|
let numRogue = 0;
|
||||||
|
items.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||||
|
.forEach(m => {
|
||||||
|
const type = m.type.withTierFromPool();
|
||||||
|
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||||
|
if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) {
|
||||||
|
numUltra += m.stackCount;
|
||||||
|
scene.removeModifier(m);
|
||||||
|
} else if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||||
|
numRogue += m.stackCount;
|
||||||
|
scene.removeModifier(m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||||
|
generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
})
|
||||||
|
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Play animations
|
||||||
|
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon());
|
||||||
|
background.playWithoutTargets(scene, 230, 40, 2);
|
||||||
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withOption(
|
||||||
|
new MysteryEncounterOptionBuilder()
|
||||||
|
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}.option.3.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.3.selected`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.3.selected_2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.3.selected_3`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Swap player's types on all party pokemon
|
||||||
|
// If a Pokemon had a single type prior, they will still have a single type after
|
||||||
|
for (const pokemon of scene.getParty()) {
|
||||||
|
const originalTypes = pokemon.getTypes(false, false, true);
|
||||||
|
|
||||||
|
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
|
||||||
|
// Makes the "randomness" of the shuffle slightly less punishing
|
||||||
|
let priorityTypes = pokemon.moveset
|
||||||
|
.filter(move => !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS)
|
||||||
|
.map(move => move.getMove().type);
|
||||||
|
if (priorityTypes?.length > 0) {
|
||||||
|
priorityTypes = [...new Set(priorityTypes)];
|
||||||
|
randSeedShuffle(priorityTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
let newTypes;
|
||||||
|
if (!originalTypes || originalTypes.length < 1) {
|
||||||
|
newTypes = priorityTypes?.length > 0 ? [priorityTypes.pop()] : [(randSeedInt(18) as Type)];
|
||||||
|
} else {
|
||||||
|
newTypes = originalTypes.map(m => {
|
||||||
|
if (priorityTypes?.length > 0) {
|
||||||
|
const ret = priorityTypes.pop();
|
||||||
|
randSeedShuffle(priorityTypes);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return randSeedInt(18) as Type;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pokemon.mysteryEncounterData) {
|
||||||
|
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, null, null, newTypes);
|
||||||
|
} else {
|
||||||
|
pokemon.mysteryEncounterData.types = newTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
})
|
||||||
|
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Play animations
|
||||||
|
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon());
|
||||||
|
background.playWithoutTargets(scene, 230, 40, 2);
|
||||||
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withOutroDialogue([
|
||||||
|
{
|
||||||
|
text: `${namespace}.outro`,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
async function handleSwapAbility(scene: BattleScene) {
|
||||||
|
return new Promise<boolean>(async resolve => {
|
||||||
|
await showEncounterDialogue(scene, `${namespace}.option.1.apply_ability_dialogue`, `${namespace}.speaker`);
|
||||||
|
await showEncounterText(scene, `${namespace}.option.1.apply_ability_message`);
|
||||||
|
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
|
displayYesNoOptions(scene, resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayYesNoOptions(scene: BattleScene, resolve) {
|
||||||
|
showEncounterText(scene, `${namespace}.option.1.ability_prompt`, 500, false);
|
||||||
|
const fullOptions = [
|
||||||
|
{
|
||||||
|
label: i18next.t("menu:yes"),
|
||||||
|
handler: () => {
|
||||||
|
onYesAbilitySwap(scene, resolve);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18next.t("menu:no"),
|
||||||
|
handler: () => {
|
||||||
|
resolve(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const config: OptionSelectConfig = {
|
||||||
|
options: fullOptions,
|
||||||
|
maxOptions: 7,
|
||||||
|
yOffset: 0
|
||||||
|
};
|
||||||
|
scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onYesAbilitySwap(scene: BattleScene, resolve) {
|
||||||
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||||
|
// Do ability swap
|
||||||
|
if (!pokemon.mysteryEncounterData) {
|
||||||
|
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, Abilities.AERILATE);
|
||||||
|
}
|
||||||
|
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
|
||||||
|
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPokemonNotSelected = () => {
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
|
displayYesNoOptions(scene, resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") {
|
||||||
|
// These pools have to be defined at runtime so that modifierTypes exist
|
||||||
|
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||||
|
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||||
|
const ultraPool = [
|
||||||
|
[modifierTypes.REVIVER_SEED, 1],
|
||||||
|
[modifierTypes.GOLDEN_PUNCH, 5],
|
||||||
|
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
|
||||||
|
[modifierTypes.QUICK_CLAW, 3],
|
||||||
|
[modifierTypes.WIDE_LENS, 3],
|
||||||
|
[modifierTypes.WHITE_HERB, 2]
|
||||||
|
];
|
||||||
|
|
||||||
|
const roguePool = [
|
||||||
|
[modifierTypes.LEFTOVERS, 4],
|
||||||
|
[modifierTypes.SHELL_BELL, 4],
|
||||||
|
[modifierTypes.SOUL_DEW, 10],
|
||||||
|
[modifierTypes.SOOTHE_BELL, 3],
|
||||||
|
[modifierTypes.SCOPE_LENS, 5],
|
||||||
|
[modifierTypes.BATON, 1],
|
||||||
|
[modifierTypes.FOCUS_BAND, 5],
|
||||||
|
[modifierTypes.KINGS_ROCK, 3],
|
||||||
|
[modifierTypes.GRIP_CLAW, 5]
|
||||||
|
];
|
||||||
|
|
||||||
|
const berryPool = [
|
||||||
|
[BerryType.APICOT, 3],
|
||||||
|
[BerryType.ENIGMA, 2],
|
||||||
|
[BerryType.GANLON, 3],
|
||||||
|
[BerryType.LANSAT, 3],
|
||||||
|
[BerryType.LEPPA, 2],
|
||||||
|
[BerryType.LIECHI, 3],
|
||||||
|
[BerryType.LUM, 2],
|
||||||
|
[BerryType.PETAYA, 3],
|
||||||
|
[BerryType.SALAC, 2],
|
||||||
|
[BerryType.SITRUS, 2],
|
||||||
|
[BerryType.STARF, 3]
|
||||||
|
];
|
||||||
|
|
||||||
|
let pool: any[];
|
||||||
|
if (tier === "Berries") {
|
||||||
|
pool = berryPool;
|
||||||
|
} else {
|
||||||
|
pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < numItems; i++) {
|
||||||
|
const randIndex = randSeedInt(pool.length);
|
||||||
|
const newItemType = pool[randIndex];
|
||||||
|
let newMod;
|
||||||
|
if (tier === "Berries") {
|
||||||
|
newMod = generateModifierTypeOption(scene, modifierTypes.BERRY, [newItemType[0]]).type as PokemonHeldItemModifierType;
|
||||||
|
} else {
|
||||||
|
newMod = generateModifierTypeOption(scene, newItemType[0]).type as PokemonHeldItemModifierType;
|
||||||
|
}
|
||||||
|
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
|
||||||
|
// Decrement max stacks and remove from pool if at max
|
||||||
|
newItemType[1]--;
|
||||||
|
if (newItemType[1] <= 0) {
|
||||||
|
pool.splice(randIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -127,7 +127,7 @@ export const DarkDealEncounter: IMysteryEncounter =
|
||||||
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
|
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
|
||||||
scene.removePokemonFromPlayerParty(removedPokemon);
|
scene.removePokemonFromPlayerParty(removedPokemon);
|
||||||
|
|
||||||
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.name);
|
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
|
||||||
|
|
||||||
// Store removed pokemon types
|
// Store removed pokemon types
|
||||||
scene.currentBattle.mysteryEncounter.misc = [
|
scene.currentBattle.mysteryEncounter.misc = [
|
||||||
|
|
|
@ -95,7 +95,7 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
];
|
];
|
||||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||||
} else {
|
} else {
|
||||||
encounter.setDialogueToken("pokeName", pokemon.name);
|
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
|
||||||
encounter.setDialogueToken("move", move.getName());
|
encounter.setDialogueToken("move", move.getName());
|
||||||
encounter.options[0].dialogue.selected = [
|
encounter.options[0].dialogue.selected = [
|
||||||
{
|
{
|
||||||
|
@ -187,7 +187,7 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
];
|
];
|
||||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||||
} else {
|
} else {
|
||||||
encounter.setDialogueToken("pokeName", pokemon.name);
|
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
|
||||||
encounter.setDialogueToken("move", move.getName());
|
encounter.setDialogueToken("move", move.getName());
|
||||||
encounter.options[1].dialogue.selected = [
|
encounter.options[1].dialogue.selected = [
|
||||||
{
|
{
|
||||||
|
@ -273,7 +273,7 @@ export const FieldTripEncounter: IMysteryEncounter =
|
||||||
];
|
];
|
||||||
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
|
||||||
} else {
|
} else {
|
||||||
encounter.setDialogueToken("pokeName", pokemon.name);
|
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
|
||||||
encounter.setDialogueToken("move", move.getName());
|
encounter.setDialogueToken("move", move.getName());
|
||||||
encounter.options[2].dialogue.selected = [
|
encounter.options[2].dialogue.selected = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -191,7 +191,7 @@ export const FieryFalloutEncounter: IMysteryEncounter =
|
||||||
const chosenPokemon = burnable[roll];
|
const chosenPokemon = burnable[roll];
|
||||||
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
|
||||||
// Burn applied
|
// Burn applied
|
||||||
encounter.setDialogueToken("burnedPokemon", chosenPokemon.name);
|
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
|
||||||
queueEncounterMessage(scene, `${namespace}.option.2.target_burned`);
|
queueEncounterMessage(scene, `${namespace}.option.2.target_burned`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ function giveLeadPokemonCharcoal(scene: BattleScene) {
|
||||||
if (leadPokemon) {
|
if (leadPokemon) {
|
||||||
const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]).type as AttackTypeBoosterModifierType;
|
const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]).type as AttackTypeBoosterModifierType;
|
||||||
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal);
|
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal);
|
||||||
scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.name);
|
scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
|
||||||
queueEncounterMessage(scene, `${namespace}.found_charcoal`);
|
queueEncounterMessage(scene, `${namespace}.found_charcoal`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
||||||
);
|
);
|
||||||
koPlayerPokemon(scene, highestLevelPokemon);
|
koPlayerPokemon(scene, highestLevelPokemon);
|
||||||
|
|
||||||
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.name);
|
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||||
// Show which Pokemon was KOed, then leave encounter with no rewards
|
// Show which Pokemon was KOed, then leave encounter with no rewards
|
||||||
// Does this synchronously so that game over doesn't happen over result message
|
// Does this synchronously so that game over doesn't happen over result message
|
||||||
await showEncounterText(scene, `${namespace}.option.1.bad`).then(() => {
|
await showEncounterText(scene, `${namespace}.option.1.bad`).then(() => {
|
||||||
|
|
|
@ -92,7 +92,7 @@ export const ThePokemonSalesmanEncounter: IMysteryEncounter =
|
||||||
encounter.options[0].dialogue.buttonTooltip = `${namespace}.option.1.tooltip_shiny`;
|
encounter.options[0].dialogue.buttonTooltip = `${namespace}.option.1.tooltip_shiny`;
|
||||||
}
|
}
|
||||||
const price = scene.getWaveMoneyAmount(priceMultiplier);
|
const price = scene.getWaveMoneyAmount(priceMultiplier);
|
||||||
encounter.setDialogueToken("purchasePokemon", pokemon.name);
|
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
|
||||||
encounter.setDialogueToken("price", price.toString());
|
encounter.setDialogueToken("price", price.toString());
|
||||||
encounter.misc = {
|
encounter.misc = {
|
||||||
price: price,
|
price: price,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { BattleStat } from "#app/data/battle-stat";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { BerryType } from "#enums/berry-type";
|
import { BerryType } from "#enums/berry-type";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:theStrongStuff";
|
const namespace = "mysteryEncounter:theStrongStuff";
|
||||||
|
@ -70,7 +71,7 @@ export const TheStrongStuffEncounter: IMysteryEncounter =
|
||||||
species: getPokemonSpecies(Species.SHUCKLE),
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 5,
|
bossSegments: 5,
|
||||||
spriteScale: 1.5,
|
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
|
||||||
nature: Nature.BOLD,
|
nature: Nature.BOLD,
|
||||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||||
modifierTypes: [
|
modifierTypes: [
|
||||||
|
@ -147,7 +148,7 @@ export const TheStrongStuffEncounter: IMysteryEncounter =
|
||||||
modifyPlayerPokemonBST(pokemon, 10);
|
modifyPlayerPokemonBST(pokemon, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
encounter.setDialogueToken("highBstPokemon", highestBst.name);
|
encounter.setDialogueToken("highBstPokemon", highestBst.getNameToRender());
|
||||||
await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, true);
|
await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, true);
|
||||||
|
|
||||||
setEncounterRewards(scene, { fillRemaining: true });
|
setEncounterRewards(scene, { fillRemaining: true });
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
|
||||||
|
export class MysteryEncounterPokemonData {
|
||||||
|
public spriteScale: number;
|
||||||
|
public ability: Abilities;
|
||||||
|
public passive: Abilities;
|
||||||
|
public types: Type[] = [];
|
||||||
|
|
||||||
|
constructor(spriteScale?: number, ability?: Abilities, passive?: Abilities, types?: Type[]) {
|
||||||
|
this.spriteScale = spriteScale;
|
||||||
|
this.ability = ability;
|
||||||
|
this.passive = passive;
|
||||||
|
this.types = types;
|
||||||
|
}
|
||||||
|
}
|
|
@ -305,7 +305,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.primaryPokemon?.length > 0) {
|
if (this.primaryPokemon?.length > 0) {
|
||||||
this.setDialogueToken("primaryName", this.primaryPokemon.name);
|
this.setDialogueToken("primaryName", this.primaryPokemon.getNameToRender());
|
||||||
for (const req of this.primaryPokemonRequirements) {
|
for (const req of this.primaryPokemonRequirements) {
|
||||||
if (!req.invertQuery) {
|
if (!req.invertQuery) {
|
||||||
const value = req.getDialogueToken(scene, this.primaryPokemon);
|
const value = req.getDialogueToken(scene, this.primaryPokemon);
|
||||||
|
@ -316,7 +316,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.secondaryPokemonRequirements?.length > 0 && this.secondaryPokemon?.length > 0) {
|
if (this.secondaryPokemonRequirements?.length > 0 && this.secondaryPokemon?.length > 0) {
|
||||||
this.setDialogueToken("secondaryName", this.secondaryPokemon[0].name);
|
this.setDialogueToken("secondaryName", this.secondaryPokemon[0].getNameToRender());
|
||||||
for (const req of this.secondaryPokemonRequirements) {
|
for (const req of this.secondaryPokemonRequirements) {
|
||||||
if (!req.invertQuery) {
|
if (!req.invertQuery) {
|
||||||
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]);
|
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]);
|
||||||
|
@ -342,7 +342,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) {
|
if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) {
|
||||||
this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.name);
|
this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.getNameToRender());
|
||||||
for (const req of opt.primaryPokemonRequirements) {
|
for (const req of opt.primaryPokemonRequirements) {
|
||||||
if (!req.invertQuery) {
|
if (!req.invertQuery) {
|
||||||
const value = req.getDialogueToken(scene, opt.primaryPokemon);
|
const value = req.getDialogueToken(scene, opt.primaryPokemon);
|
||||||
|
@ -353,7 +353,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (opt.secondaryPokemonRequirements?.length > 0 && opt.secondaryPokemon?.length > 0) {
|
if (opt.secondaryPokemonRequirements?.length > 0 && opt.secondaryPokemon?.length > 0) {
|
||||||
this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].name);
|
this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].getNameToRender());
|
||||||
for (const req of opt.secondaryPokemonRequirements) {
|
for (const req of opt.secondaryPokemonRequirements) {
|
||||||
if (!req.invertQuery) {
|
if (!req.invertQuery) {
|
||||||
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);
|
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounter
|
||||||
import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter";
|
import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter";
|
||||||
import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter";
|
import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter";
|
||||||
import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter";
|
import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter";
|
||||||
|
import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter";
|
||||||
|
|
||||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
// 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 BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||||
|
@ -141,7 +142,7 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
|
||||||
MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
|
MysteryEncounterType.MYSTERIOUS_CHALLENGERS,
|
||||||
MysteryEncounterType.SHADY_VITAMIN_DEALER,
|
MysteryEncounterType.SHADY_VITAMIN_DEALER,
|
||||||
MysteryEncounterType.THE_POKEMON_SALESMAN,
|
MysteryEncounterType.THE_POKEMON_SALESMAN,
|
||||||
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE
|
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
|
||||||
];
|
];
|
||||||
|
|
||||||
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
||||||
|
@ -159,7 +160,8 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
||||||
MysteryEncounterType.DELIBIRDY,
|
MysteryEncounterType.DELIBIRDY,
|
||||||
MysteryEncounterType.A_TRAINERS_TEST,
|
MysteryEncounterType.A_TRAINERS_TEST,
|
||||||
MysteryEncounterType.TRASH_TO_TREASURE,
|
MysteryEncounterType.TRASH_TO_TREASURE,
|
||||||
MysteryEncounterType.BERRIES_ABOUND
|
MysteryEncounterType.BERRIES_ABOUND,
|
||||||
|
MysteryEncounterType.CLOWNING_AROUND
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -249,6 +251,7 @@ export function initMysteryEncounters() {
|
||||||
allMysteryEncounters[MysteryEncounterType.A_TRAINERS_TEST] = ATrainersTestEncounter;
|
allMysteryEncounters[MysteryEncounterType.A_TRAINERS_TEST] = ATrainersTestEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.TRASH_TO_TREASURE] = TrashToTreasureEncounter;
|
allMysteryEncounters[MysteryEncounterType.TRASH_TO_TREASURE] = TrashToTreasureEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.BERRIES_ABOUND] = BerriesAboundEncounter;
|
allMysteryEncounters[MysteryEncounterType.BERRIES_ABOUND] = BerriesAboundEncounter;
|
||||||
|
allMysteryEncounters[MysteryEncounterType.CLOWNING_AROUND] = ClowningAroundEncounter;
|
||||||
|
|
||||||
// Add extreme encounters to biome map
|
// Add extreme encounters to biome map
|
||||||
extremeBiomeEncounters.forEach(encounter => {
|
extremeBiomeEncounters.forEach(encounter => {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import PokemonData from "#app/system/pokemon-data";
|
||||||
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
import { isNullOrUndefined } from "#app/utils";
|
import { isNullOrUndefined } from "#app/utils";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
|
@ -19,7 +20,6 @@ import { TrainerType } from "#enums/trainer-type";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import Trainer, { TrainerVariant } from "#app/field/trainer";
|
import Trainer, { TrainerVariant } from "#app/field/trainer";
|
||||||
import * as Utils from "#app/utils";
|
|
||||||
import { Gender } from "#app/data/gender";
|
import { Gender } from "#app/data/gender";
|
||||||
import { Nature } from "#app/data/nature";
|
import { Nature } from "#app/data/nature";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
|
@ -30,13 +30,15 @@ import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-co
|
||||||
import PokemonSpecies from "#app/data/pokemon-species";
|
import PokemonSpecies from "#app/data/pokemon-species";
|
||||||
import Overrides from "#app/overrides";
|
import Overrides from "#app/overrides";
|
||||||
import { Egg, IEggOptions } from "#app/data/egg";
|
import { Egg, IEggOptions } from "#app/data/egg";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Animates exclamation sprite over trainer's head at start of encounter
|
* Animates exclamation sprite over trainer's head at start of encounter
|
||||||
* @param scene
|
* @param scene
|
||||||
*/
|
*/
|
||||||
export function doTrainerExclamation(scene: BattleScene) {
|
export function doTrainerExclamation(scene: BattleScene) {
|
||||||
const exclamationSprite = scene.addFieldSprite(0, 0, "exclaim");
|
const exclamationSprite = scene.add.sprite(0, 0, "exclaim");
|
||||||
exclamationSprite.setName("exclamation");
|
exclamationSprite.setName("exclamation");
|
||||||
scene.field.add(exclamationSprite);
|
scene.field.add(exclamationSprite);
|
||||||
scene.field.moveTo(exclamationSprite, scene.field.getAll().length - 1);
|
scene.field.moveTo(exclamationSprite, scene.field.getAll().length - 1);
|
||||||
|
@ -63,7 +65,7 @@ export interface EnemyPokemonConfig {
|
||||||
isBoss: boolean;
|
isBoss: boolean;
|
||||||
bossSegments?: number;
|
bossSegments?: number;
|
||||||
bossSegmentModifier?: number; // Additive to the determined segment number
|
bossSegmentModifier?: number; // Additive to the determined segment number
|
||||||
spriteScale?: number;
|
mysteryEncounterData?: MysteryEncounterPokemonData;
|
||||||
formIndex?: number;
|
formIndex?: number;
|
||||||
level?: number;
|
level?: number;
|
||||||
gender?: Gender;
|
gender?: Gender;
|
||||||
|
@ -71,6 +73,8 @@ export interface EnemyPokemonConfig {
|
||||||
moveSet?: Moves[];
|
moveSet?: Moves[];
|
||||||
nature?: Nature;
|
nature?: Nature;
|
||||||
ivs?: [integer, integer, integer, integer, integer, integer];
|
ivs?: [integer, integer, integer, integer, integer, integer];
|
||||||
|
ability?: Abilities;
|
||||||
|
shiny?: boolean;
|
||||||
/** Can set just the status, or pass a timer on the status turns */
|
/** Can set just the status, or pass a timer on the status turns */
|
||||||
status?: StatusEffect | [StatusEffect, number];
|
status?: StatusEffect | [StatusEffect, number];
|
||||||
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
|
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
|
||||||
|
@ -152,7 +156,17 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||||
let isBoss = false;
|
let isBoss = false;
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
if (trainerType || trainerConfig) {
|
if (trainerType || trainerConfig) {
|
||||||
|
// Allows overriding a trainer's pokemon to use specific species/data
|
||||||
|
if (e < partyConfig?.pokemonConfigs?.length) {
|
||||||
|
const config = partyConfig?.pokemonConfigs?.[e];
|
||||||
|
level = config.level ? config.level : level;
|
||||||
|
dataSource = config.dataSource;
|
||||||
|
enemySpecies = config.species;
|
||||||
|
isBoss = config.isBoss;
|
||||||
|
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, dataSource);
|
||||||
|
} else {
|
||||||
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (e < partyConfig?.pokemonConfigs?.length) {
|
if (e < partyConfig?.pokemonConfigs?.length) {
|
||||||
const config = partyConfig?.pokemonConfigs?.[e];
|
const config = partyConfig?.pokemonConfigs?.[e];
|
||||||
|
@ -200,11 +214,14 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||||
enemyPokemon.formIndex = config.formIndex;
|
enemyPokemon.formIndex = config.formIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set scale
|
// Set shiny
|
||||||
if (!isNullOrUndefined(config.spriteScale)) {
|
if (!isNullOrUndefined(config.shiny)) {
|
||||||
enemyPokemon.mysteryEncounterData = {
|
enemyPokemon.shiny = config.shiny;
|
||||||
spriteScale: config.spriteScale
|
}
|
||||||
};
|
|
||||||
|
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
|
||||||
|
if (!isNullOrUndefined(config.mysteryEncounterData)) {
|
||||||
|
enemyPokemon.mysteryEncounterData = config.mysteryEncounterData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Boss
|
// Set Boss
|
||||||
|
@ -242,6 +259,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||||
|
|
||||||
// Set summon data fields
|
// Set summon data fields
|
||||||
|
|
||||||
|
// Set ability
|
||||||
|
if (!isNullOrUndefined(config.ability)) {
|
||||||
|
enemyPokemon.summonData.ability = config.ability;
|
||||||
|
}
|
||||||
|
|
||||||
// Set gender
|
// Set gender
|
||||||
if (!isNullOrUndefined(config.gender)) {
|
if (!isNullOrUndefined(config.gender)) {
|
||||||
enemyPokemon.gender = config.gender;
|
enemyPokemon.gender = config.gender;
|
||||||
|
@ -364,14 +386,16 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
|
||||||
*/
|
*/
|
||||||
export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: (pokemon: PlayerPokemon) => string): Promise<boolean> {
|
export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: (pokemon: PlayerPokemon) => string): Promise<boolean> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
const modeToSetOnExit = scene.ui.getMode();
|
||||||
|
|
||||||
// Open party screen to choose pokemon to train
|
// Open party screen to choose pokemon to train
|
||||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: integer, option: PartyOption) => {
|
scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: integer, option: PartyOption) => {
|
||||||
if (slotIndex < scene.getParty().length) {
|
if (slotIndex < scene.getParty().length) {
|
||||||
scene.ui.setMode(Mode.MYSTERY_ENCOUNTER).then(() => {
|
scene.ui.setMode(modeToSetOnExit).then(() => {
|
||||||
const pokemon = scene.getParty()[slotIndex];
|
const pokemon = scene.getParty()[slotIndex];
|
||||||
const secondaryOptions = onPokemonSelected(pokemon);
|
const secondaryOptions = onPokemonSelected(pokemon);
|
||||||
if (!secondaryOptions) {
|
if (!secondaryOptions) {
|
||||||
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.name);
|
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||||
resolve(true);
|
resolve(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -385,7 +409,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
||||||
const onSelect = option.handler;
|
const onSelect = option.handler;
|
||||||
option.handler = () => {
|
option.handler = () => {
|
||||||
onSelect();
|
onSelect();
|
||||||
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.name);
|
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
|
||||||
resolve(true);
|
resolve(true);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -421,7 +445,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
scene.ui.setMode(Mode.MYSTERY_ENCOUNTER).then(() => {
|
scene.ui.setMode(modeToSetOnExit).then(() => {
|
||||||
if (onPokemonNotSelected) {
|
if (onPokemonNotSelected) {
|
||||||
onPokemonNotSelected();
|
onPokemonNotSelected();
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,6 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { Gender } from "#app/data/gender";
|
import { Gender } from "#app/data/gender";
|
||||||
|
|
||||||
export interface MysteryEncounterPokemonData {
|
|
||||||
spriteScale?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): { spriteKey: string, fileRoot: string } {
|
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): { spriteKey: string, fileRoot: string } {
|
||||||
const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
||||||
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
||||||
|
@ -447,7 +443,7 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number,
|
||||||
scene.currentBattle.lastUsedPokeball = pokeballType;
|
scene.currentBattle.lastUsedPokeball = pokeballType;
|
||||||
removePb(scene, pokeball);
|
removePb(scene, pokeball);
|
||||||
|
|
||||||
scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.name }), null, () => resolve(), null, true);
|
scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.getNameToRender() }), null, () => resolve(), null, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,7 +512,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
|
||||||
Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||||
if (scene.getParty().length === 6) {
|
if (scene.getParty().length === 6) {
|
||||||
const promptRelease = () => {
|
const promptRelease = () => {
|
||||||
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => {
|
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => {
|
||||||
scene.pokemonInfoContainer.makeRoomForConfirmUi();
|
scene.pokemonInfoContainer.makeRoomForConfirmUi();
|
||||||
scene.ui.setMode(Mode.CONFIRM, () => {
|
scene.ui.setMode(Mode.CONFIRM, () => {
|
||||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
|
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
|
||||||
|
@ -544,7 +540,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
|
||||||
};
|
};
|
||||||
|
|
||||||
if (showCatchObtainMessage) {
|
if (showCatchObtainMessage) {
|
||||||
scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.name }), null, doPokemonCatchMenu, 0, true);
|
scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.getNameToRender() }), null, doPokemonCatchMenu, 0, true);
|
||||||
} else {
|
} else {
|
||||||
doPokemonCatchMenu();
|
doPokemonCatchMenu();
|
||||||
}
|
}
|
||||||
|
@ -581,7 +577,7 @@ export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon):
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
pokemon.setVisible(false);
|
pokemon.setVisible(false);
|
||||||
scene.field.remove(pokemon, true);
|
scene.field.remove(pokemon, true);
|
||||||
showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.name }), 600, false)
|
showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), 600, false)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
@ -604,7 +600,7 @@ export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
pokemon.setVisible(false);
|
pokemon.setVisible(false);
|
||||||
scene.field.remove(pokemon, true);
|
scene.field.remove(pokemon, true);
|
||||||
showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.name }), 600, false)
|
showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), 600, false)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,5 +64,5 @@ export enum BattlerTagType {
|
||||||
STOCKPILING = "STOCKPILING",
|
STOCKPILING = "STOCKPILING",
|
||||||
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
|
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
|
||||||
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
|
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
|
||||||
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON" // Provides effects on post-summon for MEs
|
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", // Provides effects on post-summon for MEs
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,5 +18,6 @@ export enum MysteryEncounterType {
|
||||||
ABSOLUTE_AVARICE,
|
ABSOLUTE_AVARICE,
|
||||||
A_TRAINERS_TEST,
|
A_TRAINERS_TEST,
|
||||||
TRASH_TO_TREASURE,
|
TRASH_TO_TREASURE,
|
||||||
BERRIES_ABOUND
|
BERRIES_ABOUND,
|
||||||
|
CLOWNING_AROUND
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,9 +190,6 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load dex progress icon
|
|
||||||
this.scene.loadAtlas("encounter_radar", "mystery-encounters");
|
|
||||||
|
|
||||||
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
||||||
this.spriteConfigs.every((config) => {
|
this.spriteConfigs.every((config) => {
|
||||||
if (config.isItem) {
|
if (config.isItem) {
|
||||||
|
|
|
@ -51,7 +51,7 @@ import { Biome } from "#enums/biome";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages.js";
|
import { getPokemonNameWithAffix } from "#app/messages.js";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
|
||||||
export enum FieldPosition {
|
export enum FieldPosition {
|
||||||
CENTER,
|
CENTER,
|
||||||
|
@ -187,6 +187,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
this.fusionVariant = dataSource.fusionVariant || 0;
|
this.fusionVariant = dataSource.fusionVariant || 0;
|
||||||
this.fusionGender = dataSource.fusionGender;
|
this.fusionGender = dataSource.fusionGender;
|
||||||
this.fusionLuck = dataSource.fusionLuck;
|
this.fusionLuck = dataSource.fusionLuck;
|
||||||
|
this.mysteryEncounterData = dataSource.mysteryEncounterData;
|
||||||
} else {
|
} else {
|
||||||
this.id = Utils.randSeedInt(4294967296);
|
this.id = Utils.randSeedInt(4294967296);
|
||||||
this.ivs = ivs || Utils.getIvsFromId(this.id);
|
this.ivs = ivs || Utils.getIvsFromId(this.id);
|
||||||
|
@ -233,6 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0);
|
this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0);
|
||||||
this.fusionLuck = this.luck;
|
this.fusionLuck = this.luck;
|
||||||
|
this.mysteryEncounterData = new MysteryEncounterPokemonData();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.generateName();
|
this.generateName();
|
||||||
|
@ -927,7 +929,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!types.length || !includeTeraType) {
|
if (!types.length || !includeTeraType) {
|
||||||
if (!ignoreOverride && this.summonData?.types) {
|
if (this.mysteryEncounterData?.types?.length > 0) {
|
||||||
|
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
||||||
|
this.mysteryEncounterData.types.forEach(t => types.push(t));
|
||||||
|
} else if (!ignoreOverride && this.summonData?.types) {
|
||||||
this.summonData.types.forEach(t => types.push(t));
|
this.summonData.types.forEach(t => types.push(t));
|
||||||
} else {
|
} else {
|
||||||
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
||||||
|
@ -994,6 +999,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) {
|
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) {
|
||||||
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
||||||
}
|
}
|
||||||
|
if (this.mysteryEncounterData?.ability) {
|
||||||
|
return allAbilities[this.mysteryEncounterData.ability];
|
||||||
|
}
|
||||||
if (this.isFusion()) {
|
if (this.isFusion()) {
|
||||||
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
||||||
}
|
}
|
||||||
|
@ -1018,6 +1026,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||||
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
|
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
|
||||||
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
|
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
|
||||||
}
|
}
|
||||||
|
if (this.mysteryEncounterData?.passive) {
|
||||||
|
return allAbilities[this.mysteryEncounterData.passive];
|
||||||
|
}
|
||||||
|
|
||||||
let starterSpeciesId = this.species.speciesId;
|
let starterSpeciesId = this.species.speciesId;
|
||||||
while (pokemonPrevolutions.hasOwnProperty(starterSpeciesId)) {
|
while (pokemonPrevolutions.hasOwnProperty(starterSpeciesId)) {
|
||||||
|
@ -4059,6 +4070,7 @@ export class PokemonSummonData {
|
||||||
public speciesForm: PokemonSpeciesForm;
|
public speciesForm: PokemonSpeciesForm;
|
||||||
public fusionSpeciesForm: PokemonSpeciesForm;
|
public fusionSpeciesForm: PokemonSpeciesForm;
|
||||||
public ability: Abilities = Abilities.NONE;
|
public ability: Abilities = Abilities.NONE;
|
||||||
|
public passiveAbility: Abilities = Abilities.NONE;
|
||||||
public gender: Gender;
|
public gender: Gender;
|
||||||
public fusionGender: Gender;
|
public fusionGender: Gender;
|
||||||
public stats: integer[];
|
public stats: integer[];
|
||||||
|
|
|
@ -275,6 +275,9 @@ export class LoadingScene extends SceneBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load Mystery Encounter dex progress icon
|
||||||
|
this.loadImage("encounter_radar", "mystery-encounters");
|
||||||
|
|
||||||
this.loadAtlas("dualshock", "inputs");
|
this.loadAtlas("dualshock", "inputs");
|
||||||
this.loadAtlas("xbox", "inputs");
|
this.loadAtlas("xbox", "inputs");
|
||||||
this.loadAtlas("keyboard", "inputs");
|
this.loadAtlas("keyboard", "inputs");
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { absoluteAvariceDialogue } from "#app/locales/en/mystery-encounters/abso
|
||||||
import { aTrainersTestDialogue } from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue";
|
import { aTrainersTestDialogue } from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue";
|
||||||
import { trashToTreasureDialogue } from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue";
|
import { trashToTreasureDialogue } from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue";
|
||||||
import { berriesAboundDialogue } from "#app/locales/en/mystery-encounters/berries-abound-dialogue";
|
import { berriesAboundDialogue } from "#app/locales/en/mystery-encounters/berries-abound-dialogue";
|
||||||
|
import { clowningAroundDialogue } from "#app/locales/en/mystery-encounters/clowning-around-dialogue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patterns that can be used:
|
* Patterns that can be used:
|
||||||
|
@ -60,5 +61,6 @@ export const mysteryEncounter = {
|
||||||
absoluteAvarice: absoluteAvariceDialogue,
|
absoluteAvarice: absoluteAvariceDialogue,
|
||||||
aTrainersTest: aTrainersTestDialogue,
|
aTrainersTest: aTrainersTestDialogue,
|
||||||
trashToTreasure: trashToTreasureDialogue,
|
trashToTreasure: trashToTreasureDialogue,
|
||||||
berriesAbound: berriesAboundDialogue
|
berriesAbound: berriesAboundDialogue,
|
||||||
|
clowningAround: clowningAroundDialogue
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
export const clowningAroundDialogue = {
|
||||||
|
intro: "It's...@d{64} a clown?",
|
||||||
|
speaker: "Clown",
|
||||||
|
intro_dialogue: "Bumbling buffoon, brace for a brilliant battle!\nYou’ll be beaten by this brawling busker!",
|
||||||
|
title: "Clowning Around",
|
||||||
|
description: "Something is off about this encounter. The clown seems eager to goad you into a battle, but to what end?\n\nThe Blacephalon is especially strange, like it has @[TOOLTIP_TITLE]{weird types and ability.}",
|
||||||
|
query: "What will you do?",
|
||||||
|
option: {
|
||||||
|
1: {
|
||||||
|
label: "Battle the Clown",
|
||||||
|
tooltip: "(-) Strange Battle\n(?) Affects Pokémon Abilities",
|
||||||
|
selected: "Your pitiful Pokémon are poised for a pathetic performance!",
|
||||||
|
apply_ability_dialogue: "A sensational showcase!\nYour savvy suits a sensational skill as spoils!",
|
||||||
|
apply_ability_message: "The clown is offering to permanently Skill Swap one of your Pokémon's ability to {{ability}}!",
|
||||||
|
ability_prompt: "Would you like to permanently teach a Pokémon the {{ability}} ability?",
|
||||||
|
ability_gained: "@s{level_up_fanfare}{{chosenPokemon}} gained the {{ability}} ability!"
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
label: "Remain Unprovoked",
|
||||||
|
tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Items",
|
||||||
|
selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
|
||||||
|
selected_2: "The clown's Blacephalon uses Trick!\nAll of your {{switchPokemon}}'s items were randomly swapped!",
|
||||||
|
selected_3: "Flustered fool, fall for my flawless deception!",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
label: "Return the Insults",
|
||||||
|
tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Types",
|
||||||
|
selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
|
||||||
|
selected_2: "The clown's Blacephalon uses a strange move!\nAll of your team's types were randomly swapped!",
|
||||||
|
selected_3: "Flustered fool, fall for my flawless deception!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outro: "The clown and his cohorts\ndisappear in a puff of smoke."
|
||||||
|
};
|
|
@ -915,7 +915,7 @@ export class EncounterPhase extends BattlePhase {
|
||||||
// Load Mystery Encounter Exclamation bubble and sfx
|
// Load Mystery Encounter Exclamation bubble and sfx
|
||||||
loadEnemyAssets.push(new Promise<void>(resolve => {
|
loadEnemyAssets.push(new Promise<void>(resolve => {
|
||||||
this.scene.loadSe("GEN8- Exclaim", "battle_anims", "GEN8- Exclaim.wav");
|
this.scene.loadSe("GEN8- Exclaim", "battle_anims", "GEN8- Exclaim.wav");
|
||||||
this.scene.loadAtlas("exclaim", "mystery-encounters");
|
this.scene.loadImage("exclaim", "mystery-encounters");
|
||||||
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve());
|
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve());
|
||||||
if (!this.scene.load.isLoading()) {
|
if (!this.scene.load.isLoading()) {
|
||||||
this.scene.load.start();
|
this.scene.load.start();
|
||||||
|
@ -1369,7 +1369,7 @@ export class PostSummonPhase extends PokemonPhase {
|
||||||
}
|
}
|
||||||
this.scene.arena.applyTags(ArenaTrapTag, pokemon);
|
this.scene.arena.applyTags(ArenaTrapTag, pokemon);
|
||||||
|
|
||||||
// If this is fight or flight mystery encounter and is enemy pokemon summon phase, add enraged tag
|
// If this is mystery encounter and has post summon phase tag, apply post summon effects
|
||||||
if (pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag)) {
|
if (pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag)) {
|
||||||
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
|
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags";
|
||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
|
||||||
export default class PokemonData {
|
export default class PokemonData {
|
||||||
public id: integer;
|
public id: integer;
|
||||||
|
@ -54,6 +55,7 @@ export default class PokemonData {
|
||||||
public bossSegments?: integer;
|
public bossSegments?: integer;
|
||||||
|
|
||||||
public summonData: PokemonSummonData;
|
public summonData: PokemonSummonData;
|
||||||
|
public mysteryEncounterData: MysteryEncounterPokemonData;
|
||||||
|
|
||||||
constructor(source: Pokemon | any, forHistory: boolean = false) {
|
constructor(source: Pokemon | any, forHistory: boolean = false) {
|
||||||
const sourcePokemon = source instanceof Pokemon ? source : null;
|
const sourcePokemon = source instanceof Pokemon ? source : null;
|
||||||
|
@ -108,6 +110,7 @@ export default class PokemonData {
|
||||||
this.status = sourcePokemon.status;
|
this.status = sourcePokemon.status;
|
||||||
if (this.player) {
|
if (this.player) {
|
||||||
this.summonData = sourcePokemon.summonData;
|
this.summonData = sourcePokemon.summonData;
|
||||||
|
this.mysteryEncounterData = sourcePokemon.mysteryEncounterData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -137,6 +140,14 @@ export default class PokemonData {
|
||||||
this.summonData.tags = [];
|
this.summonData.tags = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.mysteryEncounterData = new MysteryEncounterPokemonData();
|
||||||
|
if (!forHistory && source.mysteryEncounterData) {
|
||||||
|
this.mysteryEncounterData.spriteScale = source.mysteryEncounterData.spriteScale;
|
||||||
|
this.mysteryEncounterData.ability = source.mysteryEncounterData.ability;
|
||||||
|
this.mysteryEncounterData.passive = source.mysteryEncounterData.passive;
|
||||||
|
this.mysteryEncounterData.types = source.mysteryEncounterData.types;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,9 @@ describe("A Trainer's Test - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||||
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||||
|
|
|
@ -33,9 +33,9 @@ describe("Absolute Avarice - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
@ -81,6 +81,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not spawn outside of proper biomes", async () => {
|
it("should not spawn outside of proper biomes", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
|
||||||
game.override.startingBiome(Biome.VOLCANO);
|
game.override.startingBiome(Biome.VOLCANO);
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
@ -96,6 +97,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should spawn if player has enough berries", async () => {
|
it("should spawn if player has enough berries", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
|
||||||
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
|
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
|
||||||
|
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter();
|
||||||
|
@ -106,7 +108,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
|
||||||
it("should remove all player's berries at the start of the encounter", async () => {
|
it("should remove all player's berries at the start of the encounter", async () => {
|
||||||
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
|
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
|
||||||
|
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
|
||||||
|
|
||||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
|
expect(scene.currentBattle?.mysteryEncounter?.encounterType).toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
|
||||||
expect(scene.modifiers?.length).toBe(0);
|
expect(scene.modifiers?.length).toBe(0);
|
||||||
|
|
|
@ -36,9 +36,9 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||||
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||||
|
@ -72,6 +72,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
|
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
|
||||||
game.override.startingBiome(Biome.VOLCANO);
|
game.override.startingBiome(Biome.VOLCANO);
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,9 @@ describe("Berries Abound - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
|
|
@ -0,0 +1,374 @@
|
||||||
|
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
import { Biome } from "#app/enums/biome";
|
||||||
|
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||||
|
import { Species } from "#app/enums/species";
|
||||||
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
|
import * as BattleAnims from "#app/data/battle-anims";
|
||||||
|
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||||
|
import { CommandPhase, MovePhase, NewBattlePhase, SelectModifierPhase } from "#app/phases";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import Pokemon, { PokemonMove } from "#app/field/pokemon";
|
||||||
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||||
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
|
import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter";
|
||||||
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||||
|
import { Button } from "#enums/buttons";
|
||||||
|
import PartyUiHandler from "#app/ui/party-ui-handler";
|
||||||
|
import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
|
||||||
|
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
|
import { BerryType } from "#enums/berry-type";
|
||||||
|
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
|
||||||
|
const namespace = "mysteryEncounter:clowningAround";
|
||||||
|
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||||
|
const defaultBiome = Biome.CAVE;
|
||||||
|
const defaultWave = 45;
|
||||||
|
|
||||||
|
describe("Clowning Around - 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(true);
|
||||||
|
|
||||||
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
[Biome.CAVE, [MysteryEncounterType.CLOWNING_AROUND]],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the correct properties", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||||
|
|
||||||
|
expect(ClowningAroundEncounter.encounterType).toBe(MysteryEncounterType.CLOWNING_AROUND);
|
||||||
|
expect(ClowningAroundEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA);
|
||||||
|
expect(ClowningAroundEncounter.dialogue).toBeDefined();
|
||||||
|
expect(ClowningAroundEncounter.dialogue.intro).toStrictEqual([
|
||||||
|
{ text: `${namespace}.intro` },
|
||||||
|
{
|
||||||
|
speaker: `${namespace}.speaker`,
|
||||||
|
text: `${namespace}.intro_dialogue`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(ClowningAroundEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
|
||||||
|
expect(ClowningAroundEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
|
||||||
|
expect(ClowningAroundEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
|
||||||
|
expect(ClowningAroundEncounter.options.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not run below wave 80", async () => {
|
||||||
|
game.override.startingWave(79);
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.CLOWNING_AROUND);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not run above wave 179", async () => {
|
||||||
|
game.override.startingWave(181);
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize fully", async () => {
|
||||||
|
initSceneWithoutEncounterPhase(scene, defaultParty);
|
||||||
|
scene.currentBattle.mysteryEncounter = ClowningAroundEncounter;
|
||||||
|
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
|
||||||
|
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||||
|
|
||||||
|
const { onInit } = ClowningAroundEncounter;
|
||||||
|
|
||||||
|
expect(ClowningAroundEncounter.onInit).toBeDefined();
|
||||||
|
|
||||||
|
ClowningAroundEncounter.populateDialogueTokensFromRequirements(scene);
|
||||||
|
const onInitResult = onInit(scene);
|
||||||
|
const config = ClowningAroundEncounter.enemyPartyConfigs[0];
|
||||||
|
|
||||||
|
expect(config.doubleBattle).toBe(true);
|
||||||
|
expect(config.trainerConfig.trainerType).toBe(TrainerType.HARLEQUIN);
|
||||||
|
expect(config.pokemonConfigs[0]).toEqual({
|
||||||
|
species: getPokemonSpecies(Species.MR_MIME),
|
||||||
|
isBoss: true,
|
||||||
|
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC]
|
||||||
|
});
|
||||||
|
expect(config.pokemonConfigs[1]).toEqual({
|
||||||
|
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||||
|
ability: expect.any(Number),
|
||||||
|
mysteryEncounterData: expect.anything(),
|
||||||
|
isBoss: true,
|
||||||
|
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
|
||||||
|
});
|
||||||
|
expect(config.pokemonConfigs[1].mysteryEncounterData.types.length).toBe(2);
|
||||||
|
expect([
|
||||||
|
Abilities.STURDY,
|
||||||
|
Abilities.PICKUP,
|
||||||
|
Abilities.INTIMIDATE,
|
||||||
|
Abilities.GUTS,
|
||||||
|
Abilities.DROUGHT,
|
||||||
|
Abilities.DRIZZLE,
|
||||||
|
Abilities.SNOW_WARNING,
|
||||||
|
Abilities.SAND_STREAM,
|
||||||
|
Abilities.ELECTRIC_SURGE,
|
||||||
|
Abilities.PSYCHIC_SURGE,
|
||||||
|
Abilities.GRASSY_SURGE,
|
||||||
|
Abilities.MISTY_SURGE,
|
||||||
|
Abilities.MAGICIAN,
|
||||||
|
Abilities.SHEER_FORCE,
|
||||||
|
Abilities.PRANKSTER
|
||||||
|
]).toContain(config.pokemonConfigs[1].ability);
|
||||||
|
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].ability);
|
||||||
|
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
|
||||||
|
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
|
||||||
|
expect(onInitResult).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 1 - Battle the Clown", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = ClowningAroundEncounter.options[0];
|
||||||
|
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||||
|
expect(option.dialogue).toBeDefined();
|
||||||
|
expect(option.dialogue).toStrictEqual({
|
||||||
|
buttonLabel: `${namespace}.option.1.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
speaker: `${namespace}.speaker`,
|
||||||
|
text: `${namespace}.option.1.selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start double battle against the clown", async () => {
|
||||||
|
const phaseSpy = vi.spyOn(scene, "pushPhase");
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||||
|
|
||||||
|
const enemyField = scene.getEnemyField();
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||||
|
expect(enemyField.length).toBe(2);
|
||||||
|
expect(enemyField[0].species.speciesId).toBe(Species.MR_MIME);
|
||||||
|
expect(enemyField[0].moveset).toEqual([new PokemonMove(Moves.TEETER_DANCE), new PokemonMove(Moves.ALLY_SWITCH), new PokemonMove(Moves.DAZZLING_GLEAM), new PokemonMove(Moves.PSYCHIC)]);
|
||||||
|
expect(enemyField[1].species.speciesId).toBe(Species.BLACEPHALON);
|
||||||
|
expect(enemyField[1].moveset).toEqual([new PokemonMove(Moves.TRICK), new PokemonMove(Moves.HYPNOSIS), new PokemonMove(Moves.SHADOW_BALL), new PokemonMove(Moves.MIND_BLOWN)]);
|
||||||
|
|
||||||
|
// Should have used moves pre-battle
|
||||||
|
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
|
||||||
|
expect(movePhases.length).toBe(3);
|
||||||
|
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.ROLE_PLAY).length).toBe(1);
|
||||||
|
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.TAUNT).length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should let the player gain the ability after battle completion", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||||
|
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||||
|
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||||
|
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||||
|
const abilityToTrain = scene.currentBattle.mysteryEncounter.misc.ability;
|
||||||
|
|
||||||
|
game.onNextPrompt("PostMysteryEncounterPhase", Mode.MESSAGE, () => {
|
||||||
|
game.scene.ui.getHandler().processInput(Button.ACTION);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run to ability train option selection
|
||||||
|
const optionSelectUiHandler = game.scene.ui.handlers[Mode.OPTION_SELECT] as OptionSelectUiHandler;
|
||||||
|
vi.spyOn(optionSelectUiHandler, "show");
|
||||||
|
const partyUiHandler = game.scene.ui.handlers[Mode.PARTY] as PartyUiHandler;
|
||||||
|
vi.spyOn(partyUiHandler, "show");
|
||||||
|
game.endPhase();
|
||||||
|
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(PostMysteryEncounterPhase.name);
|
||||||
|
|
||||||
|
// Wait for Yes/No confirmation to appear
|
||||||
|
await vi.waitFor(() => expect(optionSelectUiHandler.show).toHaveBeenCalled());
|
||||||
|
// Select "Yes" on train ability
|
||||||
|
optionSelectUiHandler.processInput(Button.ACTION);
|
||||||
|
// Select first pokemon in party to train
|
||||||
|
await vi.waitFor(() => expect(partyUiHandler.show).toHaveBeenCalled());
|
||||||
|
partyUiHandler.processInput(Button.ACTION);
|
||||||
|
// Click "Select" on Pokemon
|
||||||
|
partyUiHandler.processInput(Button.ACTION);
|
||||||
|
// Stop next battle before it runs
|
||||||
|
await game.phaseInterceptor.to(NewBattlePhase, false);
|
||||||
|
|
||||||
|
const leadPokemon = scene.getParty()[0];
|
||||||
|
expect(leadPokemon.mysteryEncounterData.ability).toBe(abilityToTrain);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 2 - Remain Unprovoked", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = ClowningAroundEncounter.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: [
|
||||||
|
{
|
||||||
|
speaker: `${namespace}.speaker`,
|
||||||
|
text: `${namespace}.option.2.selected`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected_2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speaker: `${namespace}.speaker`,
|
||||||
|
text: `${namespace}.option.2.selected_3`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should randomize held items of the Pokemon with the most items, and not the held items of other pokemon", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||||
|
|
||||||
|
// 2 Sitrus Berries on lead
|
||||||
|
scene.modifiers = [];
|
||||||
|
let itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType;
|
||||||
|
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
|
||||||
|
// 2 Ganlon Berries on lead
|
||||||
|
itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.GANLON]).type as PokemonHeldItemModifierType;
|
||||||
|
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
|
||||||
|
// 5 Golden Punch on lead (ultra)
|
||||||
|
itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_PUNCH).type as PokemonHeldItemModifierType;
|
||||||
|
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
|
||||||
|
// 5 Lucky Egg on lead (ultra)
|
||||||
|
itemType = generateModifierTypeOption(scene, modifierTypes.LUCKY_EGG).type as PokemonHeldItemModifierType;
|
||||||
|
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
|
||||||
|
// 5 Soul Dew on lead (rogue)
|
||||||
|
itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType;
|
||||||
|
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
|
||||||
|
// 2 Golden Egg on lead (rogue)
|
||||||
|
itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_EGG).type as PokemonHeldItemModifierType;
|
||||||
|
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
|
||||||
|
|
||||||
|
// 5 Soul Dew on second party pokemon (these should not change)
|
||||||
|
itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType;
|
||||||
|
await addItemToPokemon(scene, scene.getParty()[1], 5, itemType);
|
||||||
|
|
||||||
|
await runMysteryEncounterToEnd(game, 2);
|
||||||
|
|
||||||
|
const leadItemsAfter = scene.getParty()[0].getHeldItems();
|
||||||
|
const ultraCountAfter = leadItemsAfter
|
||||||
|
.filter(m => m.type.tier === ModifierTier.ULTRA)
|
||||||
|
.reduce((a, b) => a + b.stackCount, 0);
|
||||||
|
const rogueCountAfter = leadItemsAfter
|
||||||
|
.filter(m => m.type.tier === ModifierTier.ROGUE)
|
||||||
|
.reduce((a, b) => a + b.stackCount, 0);
|
||||||
|
expect(ultraCountAfter).toBe(10);
|
||||||
|
expect(rogueCountAfter).toBe(7);
|
||||||
|
|
||||||
|
const secondItemsAfter = scene.getParty()[1].getHeldItems();
|
||||||
|
expect(secondItemsAfter.length).toBe(1);
|
||||||
|
expect(secondItemsAfter[0].type.id).toBe("SOUL_DEW");
|
||||||
|
expect(secondItemsAfter[0].stackCount).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave encounter without battle", async () => {
|
||||||
|
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 2);
|
||||||
|
|
||||||
|
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 3 - Return the Insults", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = ClowningAroundEncounter.options[2];
|
||||||
|
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||||
|
expect(option.dialogue).toBeDefined();
|
||||||
|
expect(option.dialogue).toStrictEqual({
|
||||||
|
buttonLabel: `${namespace}.option.3.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
speaker: `${namespace}.speaker`,
|
||||||
|
text: `${namespace}.option.3.selected`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.3.selected_2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speaker: `${namespace}.speaker`,
|
||||||
|
text: `${namespace}.option.3.selected_3`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should randomize the pokemon types of the party", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||||
|
|
||||||
|
// Same type moves on lead
|
||||||
|
scene.getParty()[0].moveset = [new PokemonMove(Moves.ICE_BEAM), new PokemonMove(Moves.SURF)];
|
||||||
|
// Different type moves on second
|
||||||
|
scene.getParty()[1].moveset = [new PokemonMove(Moves.GRASS_KNOT), new PokemonMove(Moves.ELECTRO_BALL)];
|
||||||
|
// No moves on third
|
||||||
|
scene.getParty()[2].moveset = [];
|
||||||
|
await runMysteryEncounterToEnd(game, 3);
|
||||||
|
|
||||||
|
const leadTypesAfter = scene.getParty()[0].mysteryEncounterData.types;
|
||||||
|
const secondaryTypesAfter = scene.getParty()[1].mysteryEncounterData.types;
|
||||||
|
const thirdTypesAfter = scene.getParty()[2].mysteryEncounterData.types;
|
||||||
|
|
||||||
|
expect(leadTypesAfter.length).toBe(2);
|
||||||
|
expect(leadTypesAfter).not.toBe([Type.ICE, Type.WATER]);
|
||||||
|
expect(secondaryTypesAfter.length).toBe(2);
|
||||||
|
expect(secondaryTypesAfter.includes(Type.GRASS)).toBeTruthy();
|
||||||
|
expect(secondaryTypesAfter.includes(Type.ELECTRIC)).toBeTruthy();
|
||||||
|
expect(thirdTypesAfter.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave encounter without battle", async () => {
|
||||||
|
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 3);
|
||||||
|
|
||||||
|
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function addItemToPokemon(scene: BattleScene, pokemon: Pokemon, stackCount: integer, itemType: PokemonHeldItemModifierType) {
|
||||||
|
const itemMod = itemType.newModifier(pokemon) as PokemonHeldItemModifier;
|
||||||
|
itemMod.stackCount = stackCount;
|
||||||
|
await scene.addModifier(itemMod, true, false, false, true);
|
||||||
|
await scene.updateModifiers(true);
|
||||||
|
}
|
|
@ -35,9 +35,9 @@ describe("Delibird-y - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
|
|
@ -33,7 +33,6 @@ describe("Department Store Sale - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
game.override.disableTrainerWaves(true);
|
game.override.disableTrainerWaves(true);
|
||||||
|
@ -73,6 +72,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => {
|
it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||||
game.override.startingBiome(Biome.VOLCANO);
|
game.override.startingBiome(Biome.VOLCANO);
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
|
|
@ -40,9 +40,9 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
|
|
@ -38,9 +38,9 @@ describe("Fight or Flight - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
|
|
@ -36,6 +36,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
@ -65,6 +66,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not spawn outside of sea biome", async () => {
|
it("should not spawn outside of sea biome", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||||
game.override.startingBiome(Biome.MOUNTAIN);
|
game.override.startingBiome(Biome.MOUNTAIN);
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
import { Biome } from "#app/enums/biome";
|
||||||
|
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||||
|
import { Species } from "#app/enums/species";
|
||||||
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||||
|
import { CommandPhase, SelectModifierPhase } from "#app/phases";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||||
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
|
import { MysteriousChallengersEncounter } from "#app/data/mystery-encounters/encounters/mysterious-challengers-encounter";
|
||||||
|
import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyTemplate } from "#app/data/trainer-config";
|
||||||
|
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||||
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
|
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||||
|
import IMysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
|
|
||||||
|
const namespace = "mysteryEncounter:mysteriousChallengers";
|
||||||
|
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||||
|
const defaultBiome = Biome.CAVE;
|
||||||
|
const defaultWave = 45;
|
||||||
|
|
||||||
|
describe("Mysterious Challengers - 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(true);
|
||||||
|
|
||||||
|
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
[Biome.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]],
|
||||||
|
]);
|
||||||
|
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
|
||||||
|
biomeMap.set(biome, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]);
|
||||||
|
});
|
||||||
|
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.MYSTERIOUS_CHALLENGERS, defaultParty);
|
||||||
|
|
||||||
|
expect(MysteriousChallengersEncounter.encounterType).toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||||
|
expect(MysteriousChallengersEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
|
||||||
|
expect(MysteriousChallengersEncounter.dialogue).toBeDefined();
|
||||||
|
expect(MysteriousChallengersEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
|
||||||
|
expect(MysteriousChallengersEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
|
||||||
|
expect(MysteriousChallengersEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
|
||||||
|
expect(MysteriousChallengersEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
|
||||||
|
expect(MysteriousChallengersEncounter.options.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
|
||||||
|
game.override.startingBiome(Biome.VOLCANO);
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not run below wave 10", async () => {
|
||||||
|
game.override.startingWave(9);
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not run above wave 179", async () => {
|
||||||
|
game.override.startingWave(181);
|
||||||
|
|
||||||
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize fully", async () => {
|
||||||
|
initSceneWithoutEncounterPhase(scene, defaultParty);
|
||||||
|
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(MysteriousChallengersEncounter);
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
scene.currentBattle.waveIndex = defaultWave;
|
||||||
|
|
||||||
|
const { onInit } = encounter;
|
||||||
|
|
||||||
|
expect(encounter.onInit).toBeDefined();
|
||||||
|
|
||||||
|
encounter.populateDialogueTokensFromRequirements(scene);
|
||||||
|
const onInitResult = onInit(scene);
|
||||||
|
|
||||||
|
expect(encounter.enemyPartyConfigs).toBeDefined();
|
||||||
|
expect(encounter.enemyPartyConfigs.length).toBe(3);
|
||||||
|
expect(encounter.enemyPartyConfigs).toEqual([
|
||||||
|
{
|
||||||
|
trainerConfig: expect.any(TrainerConfig),
|
||||||
|
female: expect.any(Boolean),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trainerConfig: expect.any(TrainerConfig),
|
||||||
|
levelAdditiveMultiplier: 0.5,
|
||||||
|
female: expect.any(Boolean),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trainerConfig: expect.any(TrainerConfig),
|
||||||
|
levelAdditiveMultiplier: 1,
|
||||||
|
female: expect.any(Boolean),
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
expect(encounter.enemyPartyConfigs[1].trainerConfig.partyTemplates[0]).toEqual(new TrainerPartyCompoundTemplate(
|
||||||
|
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true),
|
||||||
|
new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE, false, true)
|
||||||
|
));
|
||||||
|
expect(encounter.enemyPartyConfigs[2].trainerConfig.partyTemplates[0]).toEqual(new TrainerPartyCompoundTemplate(
|
||||||
|
new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE),
|
||||||
|
new TrainerPartyTemplate(3, PartyMemberStrength.STRONG),
|
||||||
|
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER))
|
||||||
|
);
|
||||||
|
expect(encounter.spriteConfigs).toBeDefined();
|
||||||
|
expect(encounter.spriteConfigs.length).toBe(3);
|
||||||
|
expect(onInitResult).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 1 - Normal Battle", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = MysteriousChallengersEncounter.options[0];
|
||||||
|
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||||
|
expect(option.dialogue).toBeDefined();
|
||||||
|
expect(option.dialogue).toStrictEqual({
|
||||||
|
buttonLabel: `${namespace}.option.1.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start battle against the trainer", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||||
|
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||||
|
expect(scene.currentBattle.trainer).toBeDefined();
|
||||||
|
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have normal trainer rewards after battle", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||||
|
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||||
|
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||||
|
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||||
|
|
||||||
|
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||||
|
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||||
|
expect(modifierSelectHandler.options.length).toEqual(3);
|
||||||
|
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toContain("TM_COMMON");
|
||||||
|
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toContain("TM_GREAT");
|
||||||
|
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toContain("MEMORY_MUSHROOM");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 2 - Hard Battle", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = MysteriousChallengersEncounter.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.selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start battle against the trainer", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 2, null, true);
|
||||||
|
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||||
|
expect(scene.currentBattle.trainer).toBeDefined();
|
||||||
|
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have hard trainer rewards after battle", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 2, null, true);
|
||||||
|
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||||
|
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||||
|
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||||
|
|
||||||
|
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||||
|
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||||
|
expect(modifierSelectHandler.options.length).toEqual(4);
|
||||||
|
expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount).toBe(ModifierTier.ULTRA);
|
||||||
|
expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount).toBe(ModifierTier.ULTRA);
|
||||||
|
expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount).toBe(ModifierTier.GREAT);
|
||||||
|
expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount).toBe(ModifierTier.GREAT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Option 3 - Brutal Battle", () => {
|
||||||
|
it("should have the correct properties", () => {
|
||||||
|
const option = MysteriousChallengersEncounter.options[2];
|
||||||
|
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||||
|
expect(option.dialogue).toBeDefined();
|
||||||
|
expect(option.dialogue).toStrictEqual({
|
||||||
|
buttonLabel: `${namespace}.option.3.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start battle against the trainer", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 3, null, true);
|
||||||
|
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||||
|
expect(scene.currentBattle.trainer).toBeDefined();
|
||||||
|
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have brutal trainer rewards after battle", async () => {
|
||||||
|
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
|
||||||
|
await runMysteryEncounterToEnd(game, 3, null, true);
|
||||||
|
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||||
|
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||||
|
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||||
|
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||||
|
|
||||||
|
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||||
|
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||||
|
expect(modifierSelectHandler.options.length).toEqual(4);
|
||||||
|
expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount).toBe(ModifierTier.ROGUE);
|
||||||
|
expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount).toBe(ModifierTier.ROGUE);
|
||||||
|
expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount).toBe(ModifierTier.ULTRA);
|
||||||
|
expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount).toBe(ModifierTier.GREAT);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -33,9 +33,9 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||||
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||||
|
@ -69,6 +69,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
|
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.ULTRA);
|
||||||
game.override.startingBiome(Biome.VOLCANO);
|
game.override.startingBiome(Biome.VOLCANO);
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { PokemonBaseStatTotalModifier } from "#app/modifier/modifier";
|
||||||
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 { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
|
||||||
const namespace = "mysteryEncounter:theStrongStuff";
|
const namespace = "mysteryEncounter:theStrongStuff";
|
||||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||||
|
@ -42,9 +43,9 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
@ -74,6 +75,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not spawn outside of CAVE biome", async () => {
|
it("should not spawn outside of CAVE biome", async () => {
|
||||||
|
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||||
game.override.startingBiome(Biome.MOUNTAIN);
|
game.override.startingBiome(Biome.MOUNTAIN);
|
||||||
await game.runToMysteryEncounter();
|
await game.runToMysteryEncounter();
|
||||||
|
|
||||||
|
@ -118,7 +120,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||||
species: getPokemonSpecies(Species.SHUCKLE),
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 5,
|
bossSegments: 5,
|
||||||
spriteScale: 1.5,
|
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
|
||||||
nature: Nature.BOLD,
|
nature: Nature.BOLD,
|
||||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||||
modifierTypes: expect.any(Array),
|
modifierTypes: expect.any(Array),
|
||||||
|
|
|
@ -39,9 +39,9 @@ describe("Trash to Treasure - Mystery Encounter", () => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
scene = game.scene;
|
scene = game.scene;
|
||||||
game.override.mysteryEncounterChance(100);
|
game.override.mysteryEncounterChance(100);
|
||||||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
|
||||||
game.override.startingWave(defaultWave);
|
game.override.startingWave(defaultWave);
|
||||||
game.override.startingBiome(defaultBiome);
|
game.override.startingBiome(defaultBiome);
|
||||||
|
game.override.disableTrainerWaves(true);
|
||||||
|
|
||||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||||
new Map<Biome, MysteryEncounterType[]>([
|
new Map<Biome, MysteryEncounterType[]>([
|
||||||
|
|
|
@ -81,8 +81,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||||
this.rarityBall.setScale(0.75);
|
this.rarityBall.setScale(0.75);
|
||||||
this.descriptionContainer.add(this.rarityBall);
|
this.descriptionContainer.add(this.rarityBall);
|
||||||
|
|
||||||
const dexProgressIndicator = this.scene.add.sprite(12, 9, "encounter_radar");
|
const dexProgressIndicator = this.scene.add.sprite(12, 10, "encounter_radar");
|
||||||
dexProgressIndicator.setScale(0.85);
|
dexProgressIndicator.setScale(0.80);
|
||||||
this.dexProgressContainer.add(dexProgressIndicator);
|
this.dexProgressContainer.add(dexProgressIndicator);
|
||||||
this.dexProgressContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 24, 28), Phaser.Geom.Rectangle.Contains);
|
this.dexProgressContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 24, 28), Phaser.Geom.Rectangle.Contains);
|
||||||
this.dexProgressContainer.on("pointerover", () => {
|
this.dexProgressContainer.on("pointerover", () => {
|
||||||
|
|
Loading…
Reference in New Issue