fix ME null checks and unit tests with beta update

This commit is contained in:
ImperialSympathizer 2024-08-22 13:03:05 -04:00
parent 0b698a04a2
commit 39c6c375f8
80 changed files with 4874 additions and 4746 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -2474,34 +2474,33 @@ export default class BattleScene extends SceneBase {
modifier.stackCount = stackCount;
// TODO: set isTransferable
// modifier.setIsTransferable(isTransferable);
this.addEnemyModifier(modifier, false, true);
this.addEnemyModifier(modifier, true);
});
}
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss);
let upgradeChance = 32;
if (isBoss) {
upgradeChance /= 2;
}
if (isFinalBoss) {
upgradeChance /= 8;
}
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss);
let pokemonModifierChance = modifierChance;
if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer)
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
let count = 0;
for (let c = 0; c < chances; c++) {
if (!Utils.randSeedInt(modifierChance)) {
count++;
} else {
const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss);
let upgradeChance = 32;
if (isBoss) {
upgradeChance /= 2;
}
if (isFinalBoss) {
upgradeChance /= 8;
}
const modifierChance = this.gameMode.getEnemyModifierChance(isBoss);
let pokemonModifierChance = modifierChance;
if (this.currentBattle.battleType === BattleType.TRAINER && this.currentBattle.trainer)
pokemonModifierChance = Math.ceil(pokemonModifierChance * this.currentBattle.trainer.getPartyMemberModifierChanceMultiplier(i)); // eslint-disable-line
let count = 0;
for (let c = 0; c < chances; c++) {
if (!Utils.randSeedInt(modifierChance)) {
count++;
}
}
if (isBoss) {
count = Math.max(count, Math.floor(chances / 2));
}
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
}
if (isBoss) {
count = Math.max(count, Math.floor(chances / 2));
}
getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance)
.map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
return true;
});
this.updateModifiers(false).then(() => resolve());
@ -2786,7 +2785,7 @@ export default class BattleScene extends SceneBase {
}
// Check for queued encounters first
if (!encounter && this.mysteryEncounterData?.nextEncounterQueue?.length > 0) {
if (!encounter && this.mysteryEncounterData?.nextEncounterQueue && this.mysteryEncounterData.nextEncounterQueue.length > 0) {
let i = 0;
while (i < this.mysteryEncounterData.nextEncounterQueue.length && !!encounter) {
const candidate = this.mysteryEncounterData.nextEncounterQueue[i];
@ -2801,7 +2800,7 @@ export default class BattleScene extends SceneBase {
if (encounter) {
encounter = new MysteryEncounter(encounter);
encounter.populateDialogueTokensFromRequirements!(this);
encounter.populateDialogueTokensFromRequirements(this);
return encounter;
}

View File

@ -440,7 +440,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
scene.field.add(moveAnim.bgSprite);
const fieldPokemon = scene.getEnemyPokemon() || scene.getPlayerPokemon();
if (!isNullOrUndefined(priority)) {
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority);
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!);
} else if (fieldPokemon?.isOnField()) {
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
}
@ -540,7 +540,7 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
const encounterAnimFetches = [];
const encounterAnimFetches: Promise<Map<EncounterAnim, AnimConfig>>[] = [];
for (const anim of anims) {
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
continue;
@ -806,7 +806,7 @@ export abstract class BattleAnim {
play(scene: BattleScene, callback?: Function) {
const isOppAnim = this.isOppAnim();
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
const target = !isOppAnim ? this.target : this.user;
const target = !isOppAnim ? this.target! : this.user!;
if (!target?.isOnField() && !this.playOnEmptyField) {
if (callback) {
@ -1052,7 +1052,7 @@ export abstract class BattleAnim {
y += targetInitialY;
const angle = -frame.angle;
const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++;
ret.get(frame.target).set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle });
ret.get(frame.target)?.set(key, { x: x, y: y, scaleX: scaleX, scaleY: scaleY, angle: angle });
}
return ret;
@ -1102,17 +1102,17 @@ export abstract class BattleAnim {
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
this.dstLine = [ 150, 75, targetInitialX, targetInitialY ];
let r = anim.frames.length;
let r = anim!.frames.length;
let f = 0;
const existingFieldSprites = [...scene.field.getAll()];
scene.tweens.addCounter({
duration: Utils.getFrameMs(3) * frameTimeMult,
repeat: anim.frames.length,
repeat: anim!.frames.length,
onRepeat: () => {
const spriteFrames = anim.frames[f];
const frameData = this.getGraphicFrameDataWithoutTarget(anim.frames[f], targetInitialX, targetInitialY);
const spriteFrames = anim!.frames[f];
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[f], targetInitialX, targetInitialY);
const u = 0;
const t = 0;
let g = 0;
@ -1124,7 +1124,7 @@ export abstract class BattleAnim {
const sprites = spriteCache[AnimFrameTarget.GRAPHIC];
if (g === sprites.length) {
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim.graphic, 1);
const newSprite: Phaser.GameObjects.Sprite = scene.addFieldSprite(0, 0, anim!.graphic, 1);
sprites.push(newSprite);
scene.field.add(newSprite);
spritePriorities.push(1);
@ -1147,17 +1147,19 @@ export abstract class BattleAnim {
}
moveSprite.setFrame(frame.graphicFrame);
const graphicFrameData = frameData.get(frame.target).get(graphicIndex);
moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y);
moveSprite.setAngle(graphicFrameData.angle);
moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY);
const graphicFrameData = frameData.get(frame.target)?.get(graphicIndex);
if (graphicFrameData) {
moveSprite.setPosition(graphicFrameData.x, graphicFrameData.y);
moveSprite.setAngle(graphicFrameData.angle);
moveSprite.setScale(graphicFrameData.scaleX, graphicFrameData.scaleY);
moveSprite.setAlpha(frame.opacity / 255);
moveSprite.setVisible(frame.visible);
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
moveSprite.setAlpha(frame.opacity / 255);
moveSprite.setVisible(frame.visible);
moveSprite.setBlendMode(frame.blendType === AnimBlendType.NORMAL ? Phaser.BlendModes.NORMAL : frame.blendType === AnimBlendType.ADD ? Phaser.BlendModes.ADD : Phaser.BlendModes.DIFFERENCE);
}
}
if (anim.frameTimedEvents.has(f)) {
for (const event of anim.frameTimedEvents.get(f)) {
if (anim?.frameTimedEvents.get(f)) {
for (const event of anim.frameTimedEvents.get(f)!) {
r = Math.max((anim.frames.length - f) + event.execute(scene, this, frameTimedEventPriority), r);
}
}
@ -1277,8 +1279,8 @@ export class EncounterBattleAnim extends BattleAnim {
this.oppAnim = oppAnim ?? false;
}
getAnim(): AnimConfig {
return encounterAnims.get(this.encounterAnim);
getAnim(): AnimConfig | null {
return encounterAnims.get(this.encounterAnim) ?? null;
}
isOppAnim(): boolean {

View File

@ -86,7 +86,7 @@ export class Egg {
private _overrideHiddenAbility: boolean;
private _eventEggTypeDescriptor: string;
private _eventEggTypeDescriptor?: string;
////
// #endregion
@ -186,7 +186,7 @@ export class Egg {
this.addEggToGameData(eggOptions.scene!); // TODO: is this bang correct?
}
this._eventEggTypeDescriptor = eggOptions.eventEggTypeDescriptor ?? null;
this._eventEggTypeDescriptor = eggOptions?.eventEggTypeDescriptor;
}
////

View File

@ -76,13 +76,13 @@ export const ATrainersTestEncounter: MysteryEncounter =
text: `${namespace}.${trainerNameKey}.intro_dialogue`
}
];
encounter.options[0].dialogue.selected = [
encounter.options[0].dialogue!.selected = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.accept`
}
];
encounter.options[1].dialogue.selected = [
encounter.options[1].dialogue!.selected = [
{
speaker: `trainerNames:${trainerNameKey}`,
text: `${namespace}.${trainerNameKey}.decline`

View File

@ -22,6 +22,7 @@ import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPo
import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#app/data/pokeball";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { BerryType } from "#enums/berry-type";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:absoluteAvarice";
@ -39,8 +40,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
.withIntroSpriteConfigs([
{
// This sprite has the shadow
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.GREEDENT,
hasShadow: true,
alpha: 0.001,
@ -48,8 +49,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
x: -5
},
{
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.GREEDENT,
hasShadow: false,
repeat: true,
@ -254,7 +255,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
queueEncounterMessage(scene, `${namespace}.option.1.food_stash`);
};
setEncounterRewards(scene, { fillRemaining: true }, null, givePartyPokemonReviverSeeds);
setEncounterRewards(scene, { fillRemaining: true }, undefined, givePartyPokemonReviverSeeds);
encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
@ -287,7 +288,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const party = scene.getParty();
party.forEach(pokemon => {
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
const berryTypesAsArray = [];
const berryTypesAsArray: BerryType[] = [];
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
const returnedBerryCount = Math.floor((berryTypesAsArray.length ?? 0) * 2 / 5);
@ -330,7 +331,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Let it have the food
// Greedent joins the team, level equal to 2 below highest party member
const level = getHighestLevelPlayerPokemon(scene).level - 2;
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false, null);
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false);
greedent.moveset = [new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF)];
greedent.passive = true;
@ -346,7 +347,7 @@ function doGreedentSpriteSteal(scene: BattleScene) {
const shakeDelay = 50;
const slideDelay = 500;
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals.getSpriteAtIndex(1);
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals?.getSpriteAtIndex(1);
scene.playSound("Follow Me");
scene.tweens.chain({
@ -420,7 +421,7 @@ function doGreedentSpriteSteal(scene: BattleScene) {
}
function doGreedentEatBerries(scene: BattleScene) {
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals.getSpriteAtIndex(1);
const greedentSprites = scene.currentBattle.mysteryEncounter.introVisuals?.getSpriteAtIndex(1);
let index = 1;
scene.tweens.add({
targets: greedentSprites,
@ -455,7 +456,12 @@ function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
const encounter = scene.currentBattle.mysteryEncounter;
animationOrder.forEach((berry, i) => {
const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry));
const [ sprite, tintSprite ] = encounter.introVisuals.getSpriteAtIndex(introVisualsIndex);
let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite;
const sprites = encounter.introVisuals?.getSpriteAtIndex(introVisualsIndex);
if (sprites) {
sprite = sprites[0];
tintSprite = sprites[1];
}
scene.time.delayedCall(berryAddDelay * i + 400, () => {
if (sprite) {
sprite.setVisible(!isEat);

View File

@ -136,7 +136,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
setEncounterExp(scene, encounter.options[1].primaryPokemon.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
setEncounterExp(scene, encounter.options[1].primaryPokemon!.id, getPokemonSpecies(Species.LIEPARD).baseExp, true);
leaveEncounterWithoutBattle(scene, true);
})

View File

@ -146,7 +146,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
// Calculate boss mon
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, scene.currentBattle.waveIndex, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, true, null);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
@ -222,7 +222,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
shopOptions.push(generateModifierTypeOption(scene, modifierTypes.BERRY));
}
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, null, doBerryRewards);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
}
)
@ -262,12 +262,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
};
const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
config.pokemonConfigs[0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs[0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
};
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, null, doBerryRewards);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
await initBattleWithEnemyConfig(scene, config);
return;
@ -287,8 +287,8 @@ export const BerriesAboundEncounter: MysteryEncounter =
}
};
setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, null, doFasterBerryRewards);
setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doFasterBerryRewards);
await showEncounterText(scene, `${namespace}.option.2.selected`);
leaveEncounterWithoutBattle(scene);
}

View File

@ -1,4 +1,4 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EnemyPartyConfig, generateModifierType, 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";
@ -28,7 +28,6 @@ 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";
@ -129,7 +128,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
},
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
species: getPokemonSpecies(Species.BLACEPHALON),
mysteryEncounterData: new MysteryEncounterPokemonData(null, ability, null, [randSeedInt(18), randSeedInt(18)]),
mysteryEncounterData: new MysteryEncounterPokemonData(undefined, ability, undefined, [randSeedInt(18), randSeedInt(18)]),
isBoss: true,
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
},
@ -206,7 +205,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
ease: "Sine.easeInOut",
duration: 250
});
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon());
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
return true;
})
@ -292,7 +291,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon());
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene);
})
@ -327,8 +326,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
// 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);
.filter(move => 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);
@ -350,7 +349,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
}
if (!pokemon.mysteryEncounterData) {
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, null, null, newTypes);
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes);
} else {
pokemon.mysteryEncounterData.types = newTypes;
}
@ -361,7 +360,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon());
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 230, 40, 2);
await transitionMysteryEncounterIntroVisuals(scene);
})
@ -441,8 +440,7 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem
[modifierTypes.GOLDEN_PUNCH, 5],
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
[modifierTypes.QUICK_CLAW, 3],
[modifierTypes.WIDE_LENS, 3],
[modifierTypes.WHITE_HERB, 2]
[modifierTypes.WIDE_LENS, 3]
];
const roguePool = [
@ -450,7 +448,7 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem
[modifierTypes.SHELL_BELL, 4],
[modifierTypes.SOUL_DEW, 10],
[modifierTypes.SOOTHE_BELL, 3],
[modifierTypes.SCOPE_LENS, 5],
[modifierTypes.SCOPE_LENS, 1],
[modifierTypes.BATON, 1],
[modifierTypes.FOCUS_BAND, 5],
[modifierTypes.KINGS_ROCK, 3],

View File

@ -87,7 +87,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withAutoHideIntroVisuals(false)
.withCatchAllowed(true)
.withOnVisualsStart((scene: BattleScene) => {
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon(), scene.getPlayerPokemon());
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
danceAnim.play(scene);
return true;
@ -104,8 +104,8 @@ export const DancingLessonsEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter;
const species = getPokemonSpecies(Species.ORICORIO);
const enemyPokemon = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels[0], TrainerSlot.NONE, false);
if (!enemyPokemon.moveset.some(m => m.getMove().id === Moves.REVELATION_DANCE)) {
const enemyPokemon = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false);
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
if (enemyPokemon.moveset.length < 4) {
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE));
} else {
@ -206,7 +206,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
scene.unshiftPhase(new LearnMovePhase(scene, scene.getParty().indexOf(pokemon), Moves.REVELATION_DANCE));
// Play animation again to "learn" the dance
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon(), scene.getPlayerPokemon());
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
danceAnim.play(scene);
};
@ -239,7 +239,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
return pokemon.moveset
.filter(move => DANCING_MOVES.includes(move.getMove().id))
.filter(move => move && DANCING_MOVES.includes(move.getMove().id))
.map((move: PokemonMove) => {
const option: OptionSelectItem = {
label: move.getName(),
@ -261,13 +261,13 @@ export const DancingLessonsEncounter: MysteryEncounter =
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Show the Oricorio a dance, and recruit it

View File

@ -47,8 +47,8 @@ export const DelibirdyEncounter: MysteryEncounter =
))
.withIntroSpriteConfigs([
{
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.DELIBIRD,
hasShadow: true,
repeat: true,
@ -56,16 +56,16 @@ export const DelibirdyEncounter: MysteryEncounter =
scale: 0.94
},
{
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.DELIBIRD,
hasShadow: true,
repeat: true,
scale: 1.06
},
{
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.DELIBIRD,
hasShadow: true,
repeat: true,
@ -116,7 +116,7 @@ export const DelibirdyEncounter: MysteryEncounter =
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
}
@ -169,13 +169,13 @@ export const DelibirdyEncounter: MysteryEncounter =
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
@ -191,7 +191,7 @@ export const DelibirdyEncounter: MysteryEncounter =
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
}
@ -204,7 +204,7 @@ export const DelibirdyEncounter: MysteryEncounter =
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
}
@ -264,13 +264,13 @@ export const DelibirdyEncounter: MysteryEncounter =
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
@ -284,7 +284,7 @@ export const DelibirdyEncounter: MysteryEncounter =
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), undefined, true);
} else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
}

View File

@ -2,7 +2,7 @@ import {
leaveEncounterWithoutBattle,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierTypeFunc, modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
@ -32,8 +32,8 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
x: -20,
},
{
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.FURFROU,
hasShadow: true,
repeat: true,
@ -60,7 +60,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Choose TMs
const modifiers = [];
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 4) {
// 2/2/1 weight on TM rarity
@ -86,7 +86,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Choose Vitamins
const modifiers = [];
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 3) {
// 2/1 weight on base stat booster vs PP Up
@ -110,7 +110,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Choose X Items
const modifiers = [];
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 5) {
// 4/1 weight on base stat booster vs Dire Hit
@ -134,7 +134,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Choose Pokeballs
const modifiers = [];
const modifiers: ModifierTypeFunc[] = [];
let i = 0;
while (i < 4) {
// 10/30/20/5 weight on pokeballs

View File

@ -78,7 +78,7 @@ export const FieldTripEncounter: MysteryEncounter =
const correctMove = move.getMove().category === MoveCategory.PHYSICAL;
encounter.setDialogueToken("moveCategory", "Physical");
if (!correctMove) {
encounter.options[0].dialogue.selected = [
encounter.options[0].dialogue!.selected = [
{
text: `${namespace}.option.incorrect`,
speaker: `${namespace}.speaker`,
@ -97,7 +97,7 @@ export const FieldTripEncounter: MysteryEncounter =
} else {
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName());
encounter.options[0].dialogue.selected = [
encounter.options[0].dialogue!.selected = [
{
text: `${namespace}.option.selected`,
},
@ -164,7 +164,7 @@ export const FieldTripEncounter: MysteryEncounter =
const correctMove = move.getMove().category === MoveCategory.SPECIAL;
encounter.setDialogueToken("moveCategory", "Special");
if (!correctMove) {
encounter.options[1].dialogue.selected = [
encounter.options[1].dialogue!.selected = [
{
text: `${namespace}.option.incorrect`,
speaker: `${namespace}.speaker`,
@ -189,7 +189,7 @@ export const FieldTripEncounter: MysteryEncounter =
} else {
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName());
encounter.options[1].dialogue.selected = [
encounter.options[1].dialogue!.selected = [
{
text: `${namespace}.option.selected`,
},
@ -256,7 +256,7 @@ export const FieldTripEncounter: MysteryEncounter =
const correctMove = move.getMove().category === MoveCategory.STATUS;
encounter.setDialogueToken("moveCategory", "Status");
if (!correctMove) {
encounter.options[2].dialogue.selected = [
encounter.options[2].dialogue!.selected = [
{
text: `${namespace}.option.incorrect`,
speaker: `${namespace}.speaker`,
@ -275,7 +275,7 @@ export const FieldTripEncounter: MysteryEncounter =
} else {
encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName());
encounter.options[2].dialogue.selected = [
encounter.options[2].dialogue!.selected = [
{
text: `${namespace}.option.selected`,
},

View File

@ -75,8 +75,8 @@ export const FieryFalloutEncounter: MysteryEncounter =
// Load hidden Volcarona sprites
encounter.spriteConfigs = [
{
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.VOLCARONA,
repeat: true,
hidden: true,
@ -85,8 +85,8 @@ export const FieryFalloutEncounter: MysteryEncounter =
startFrame: 20
},
{
spriteKey: null,
fileRoot: null,
spriteKey: "",
fileRoot: "",
species: Species.VOLCARONA,
repeat: true,
hidden: true,
@ -104,9 +104,9 @@ export const FieryFalloutEncounter: MysteryEncounter =
})
.withOnVisualsStart((scene: BattleScene) => {
// Play animations
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, scene.getPlayerPokemon(), scene.getPlayerPokemon());
const background = new EncounterBattleAnim(EncounterAnim.MAGMA_BG, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
background.playWithoutTargets(scene, 200, 70, 2, 3);
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, scene.getPlayerPokemon(), scene.getPlayerPokemon());
const animation = new EncounterBattleAnim(EncounterAnim.MAGMA_SPOUT, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
animation.playWithoutTargets(scene, 80, 100, 2);
scene.time.delayedCall(600, () => {
animation.playWithoutTargets(scene, -20, 100, 2);
@ -133,7 +133,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { fillRemaining: true }, null, () => giveLeadPokemonCharcoal(scene));
setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonCharcoal(scene));
encounter.startOfBattleEffects.push(
{
@ -185,7 +185,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
}
// Burn random member
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status?.effect === StatusEffect.BURN);
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.BURN);
if (burnable?.length > 0) {
const roll = randSeedInt(burnable.length);
const chosenPokemon = burnable[roll];
@ -224,13 +224,13 @@ export const FieryFalloutEncounter: MysteryEncounter =
transitionMysteryEncounterIntroVisuals(scene);
setEncounterRewards(scene,
{ fillRemaining: true },
null,
undefined,
() => {
giveLeadPokemonCharcoal(scene);
});
const primary = encounter.options[2].primaryPokemon;
const secondary = encounter.options[2].secondaryPokemon[0];
const primary = encounter.options[2].primaryPokemon!;
const secondary = encounter.options[2].secondaryPokemon![0];
setEncounterExp(scene, [primary.id, secondary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2);
leaveEncounterWithoutBattle(scene);

View File

@ -50,7 +50,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
// Calculate boss mon
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, scene.currentBattle.waveIndex, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, true, null);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, true);
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [{
@ -72,7 +72,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
? ModifierTier.ULTRA
: ModifierTier.GREAT;
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
let item: ModifierTypeOption;
let item: ModifierTypeOption | null = null;
// TMs excluded from possible rewards as they're too swingy in value for a singular item reward
while (!item || item.type.id.includes("TM_")) {
item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier], allowLuckUpgrades: false })[0];
@ -147,8 +147,8 @@ export const FightOrFlightEncounter: MysteryEncounter =
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
// Use primaryPokemon to execute the thievery
const primaryPokemon = encounter.options[1].primaryPokemon;
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp);
const primaryPokemon = encounter.options[1].primaryPokemon!;
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
leaveEncounterWithoutBattle(scene);
})
.build()

View File

@ -87,6 +87,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
const e4Template = trainerPartyTemplates.ELITE_FOUR;
const brutalConfig = trainerConfigs[brutalTrainerType].copy();
brutalConfig.setPartyTemplates(e4Template);
// @ts-ignore
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func
female = false;
if (brutalConfig.hasGenders) {

View File

@ -57,7 +57,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
.withPreOptionPhase(async (scene: BattleScene) => {
// Play animation
const introVisuals =
scene.currentBattle.mysteryEncounter.introVisuals;
scene.currentBattle.mysteryEncounter.introVisuals!;
introVisuals.spriteConfigs[0].disableAnimation = false;
introVisuals.playAnim();
})

View File

@ -100,8 +100,10 @@ export const PartTimerEncounter: MysteryEncounter =
// Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
setEncounterExp(scene, pokemon.id, 100);
@ -115,13 +117,13 @@ export const PartTimerEncounter: MysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick Deliveries
@ -179,8 +181,10 @@ export const PartTimerEncounter: MysteryEncounter =
// Reduce all PP to 2 (if they started at greater than 2)
pokemon.moveset.forEach(move => {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
setEncounterExp(scene, pokemon.id, 100);
@ -194,13 +198,13 @@ export const PartTimerEncounter: MysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick Move Warehouse items
@ -241,13 +245,15 @@ export const PartTimerEncounter: MysteryEncounter =
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const selectedPokemon = encounter.selectedOption.primaryPokemon;
const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());
// Reduce all PP to 2 (if they started at greater than 2)
selectedPokemon.moveset.forEach(move => {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
if (move) {
const newPpUsed = move.getMovePp() - 2;
move.ppUsed = move.ppUsed < newPpUsed ? newPpUsed : move.ppUsed;
}
});
setEncounterExp(scene, selectedPokemon.id, 100);

View File

@ -172,9 +172,9 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// 80% chance to increase flee stage +1
const fleeChangeResult = tryChangeFleeStage(scene, 1, 8);
if (!fleeChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.busy_eating`), 1000, false );
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.busy_eating`) ?? "", 1000, false );
} else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.eating`), 1000, false);
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.eating`) ?? "", 1000, false);
}
await doEndTurn(scene, 1);
@ -201,9 +201,9 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
// 80% chance to decrease catch stage -1
const catchChangeResult = tryChangeCatchStage(scene, -1, 8);
if (!catchChangeResult) {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.beside_itself_angry`), 1000, false );
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.beside_itself_angry`) ?? "", 1000, false );
} else {
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.angry`), 1000, false );
await showEncounterText(scene, getEncounterText(scene, `${namespace}.safari.angry`) ?? "", 1000, false );
}
await doEndTurn(scene, 2);
@ -239,7 +239,7 @@ async function summonSafariPokemon(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
// Message pokemon remaining
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.remaining_count`), null, true);
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.remaining_count`) ?? "", null, true);
// Generate pokemon using safariPokemonRemaining so they are always the same pokemon no matter how many turns are taken
// Safari pokemon roll twice on shiny and HA chances, but are otherwise normal
@ -288,7 +288,7 @@ async function summonSafariPokemon(scene: BattleScene) {
scene.unshiftPhase(new SummonPhase(scene, 0, false));
encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon));
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared"), 1500, false)
showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", 1500, false)
.then(() => {
const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
@ -494,7 +494,7 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) {
leaveEncounterWithoutBattle(scene, true);
}
} else {
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.watching`), 0, null, 1000);
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.watching`) ?? "", 0, null, 1000);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
}
}

View File

@ -95,13 +95,13 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Choose Cheap Option
@ -178,13 +178,13 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Choose Expensive Option

View File

@ -140,7 +140,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
const instance = scene.currentBattle.mysteryEncounter;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: false });
// Snorlax exp to Pokemon that did the stealing
setEncounterExp(scene, instance.primaryPokemon.id, getPokemonSpecies(Species.SNORLAX).baseExp);
setEncounterExp(scene, instance.primaryPokemon!.id, getPokemonSpecies(Species.SNORLAX).baseExp);
leaveEncounterWithoutBattle(scene);
})
.build()

View File

@ -29,7 +29,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new MoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
.withSceneRequirement(new MoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
{
@ -66,10 +66,10 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
// If no HA mon found or you roll 1%, give shiny Magikarp
species = getPokemonSpecies(Species.MAGIKARP);
const hiddenIndex = species.ability2 ? 2 : 1;
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, null, true, null, null, null, null);
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true);
} else {
const hiddenIndex = species.ability2 ? 2 : 1;
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, null, null, null, null, null, null);
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
}
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
@ -88,8 +88,8 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
// Always max price for shiny (flip HA back to normal), and add special messaging
priceMultiplier = MAX_POKEMON_PRICE_MULTIPLIER;
pokemon.abilityIndex = 0;
encounter.dialogue.encounterOptionsDialogue.description = `${namespace}.description_shiny`;
encounter.options[0].dialogue.buttonTooltip = `${namespace}.option.1.tooltip_shiny`;
encounter.dialogue.encounterOptionsDialogue!.description = `${namespace}.description_shiny`;
encounter.options[0].dialogue!.buttonTooltip = `${namespace}.option.1.tooltip_shiny`;
}
const price = scene.getWaveMoneyAmount(priceMultiplier);
encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
@ -107,7 +107,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withHasDexProgress(true)
.withSceneMoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withSceneMoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,

View File

@ -127,7 +127,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
// -20 to all base stats of highest BST, +10 to all base stats of rest of party
// Get highest BST mon
const party = scene.getParty();
let highestBst: PlayerPokemon = null;
let highestBst: PlayerPokemon | null = null;
let statTotal = 0;
for (const pokemon of party) {
if (!highestBst) {
@ -157,7 +157,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
}
encounter.setDialogueToken("highBstPokemon", highestBst.getNameToRender());
await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, true);
await showEncounterText(scene, `${namespace}.option.1.selected_2`, undefined, true);
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene, true);

View File

@ -140,7 +140,7 @@ async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
await transitionMysteryEncounterIntroVisuals(scene, false, false);
await showEncounterDialogue(scene, `${namespace}.victory`, `${namespace}.speaker`);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE], fillRemaining: false });
encounter.doContinueEncounter = null;
encounter.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, false, MysteryEncounterMode.TRAINER_BATTLE);
} else {
await initBattleWithEnemyConfig(scene, nextConfig);
@ -151,17 +151,21 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
return new Promise(async resolve => {
if (scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length === 0) {
// Battle is over
scene.tweens.add({
targets: scene.currentBattle.trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(scene.currentBattle.trainer, true);
}
});
const trainer = scene.currentBattle.trainer;
if (trainer) {
scene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
}
});
}
await spawnNextTrainerOrEndEncounter(scene);
resolve(); // Wait for all dialogue/post battle stuff to complete before resolving
} else {
@ -186,18 +190,20 @@ function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
// Unassign previous trainer from battle so it isn't destroyed before animation completes
scene.currentBattle.trainer = null;
await spawnNextTrainerOrEndEncounter(scene);
scene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
resolve();
}
});
if (trainer) {
scene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
resolve();
}
});
}
}
});
}

View File

@ -6,11 +6,10 @@ import { Stat } from "#app/data/pokemon-stat";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { pokemonInfo } from "#app/locales/en/pokemon-info";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { AbilityAttr } from "#app/system/game-data";
import PokemonData from "#app/system/pokemon-data";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { randSeedShuffle } from "#app/utils";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
@ -19,6 +18,7 @@ import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
/** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:trainingSession";
@ -77,13 +77,13 @@ export const TrainingSessionEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
const meetsReqs = pokemon.isAllowedInBattle();
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
@ -125,7 +125,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
encounter.setDialogueToken("stat1", "-");
encounter.setDialogueToken("stat2", "-");
// Add the pokemon back to party with IV boost
const ivIndexes = [];
const ivIndexes: any[] = [];
playerPokemon.ivs.forEach((iv, index) => {
if (iv < 31) {
ivIndexes.push({ iv: iv, index: index });
@ -147,12 +147,12 @@ export const TrainingSessionEncounter: MysteryEncounter =
if (improvedCount === 0) {
encounter.setDialogueToken(
"stat1",
getIvName(ivToChange.index)
getIvName(ivToChange.index) ?? ""
);
} else {
encounter.setDialogueToken(
"stat2",
getIvName(ivToChange.index)
getIvName(ivToChange.index) ?? ""
);
}
@ -185,12 +185,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
queueEncounterMessage(scene, `${namespace}.option.1.finished`);
};
setEncounterRewards(
scene,
{ fillRemaining: true },
null,
onBeforeRewardsPhase
);
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
@ -237,13 +232,13 @@ export const TrainingSessionEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
const meetsReqs = pokemon.isAllowedInBattle();
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
@ -278,12 +273,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
scene.updateModifiers(true);
};
setEncounterRewards(
scene,
{ fillRemaining: true },
null,
onBeforeRewardsPhase
);
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
@ -339,13 +329,13 @@ export const TrainingSessionEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
const meetsReqs = pokemon.isAllowedInBattle();
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
@ -354,18 +344,10 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Spawn hard training session with chosen pokemon
// Every 30 waves, add +1 boss segment, capping at 6
// Also starts with +1 to all stats
const segments = Math.min(
2 + Math.floor(scene.currentBattle.waveIndex / 30),
6
);
const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 30), 6);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(
scene,
playerPokemon,
segments,
modifiers
);
config.pokemonConfigs[0].tags = [
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
config.pokemonConfigs![0].tags = [
BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON,
];
scene.removePokemonFromPlayerParty(playerPokemon, false);
@ -376,15 +358,10 @@ export const TrainingSessionEncounter: MysteryEncounter =
const abilityIndex = encounter.misc.abilityIndex;
if (!!playerPokemon.getFusionSpeciesForm()) {
playerPokemon.fusionAbilityIndex = abilityIndex;
if (
speciesStarters.hasOwnProperty(
playerPokemon.fusionSpecies.speciesId
)
) {
scene.gameData.starterData[
playerPokemon.fusionSpecies.speciesId
].abilityAttr |=
abilityIndex !== 1 || playerPokemon.fusionSpecies.ability2
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies!.speciesId)) {
scene.gameData.starterData[playerPokemon.fusionSpecies!.speciesId]
.abilityAttr |=
abilityIndex !== 1 || playerPokemon.fusionSpecies!.ability2
? Math.pow(2, playerPokemon.fusionAbilityIndex)
: AbilityAttr.ABILITY_HIDDEN;
}
@ -414,12 +391,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
scene.updateModifiers(true);
};
setEncounterRewards(
scene,
{ fillRemaining: true },
null,
onBeforeRewardsPhase
);
setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase);
return initBattleWithEnemyConfig(scene, config);
})
@ -432,7 +404,11 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon,segment
// Passes modifiers by reference
modifiers.value = scene.findModifiers((m) => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === playerPokemon.id) as PokemonHeldItemModifier[];
const modifierTypes = modifiers.value.map((mod) => mod.type) as PokemonHeldItemModifierType[];
const modifierConfigs = modifiers.value.map((mod) => {
return {
modifierType: mod.type
};
}) as HeldModifierConfig[];
const data = new PokemonData(playerPokemon);
return {
@ -444,7 +420,7 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon,segment
formIndex: playerPokemon.formIndex,
level: playerPokemon.level,
dataSource: data,
modifierConfigs: modifierTypes,
modifierConfigs: modifierConfigs,
},
],
};

View File

@ -178,7 +178,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), null, true);
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), undefined, true);
// First Shell bell
for (const pokemon of party) {
@ -205,7 +205,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
}
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), null, true);
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), undefined, true);
}
async function doGarbageDig(scene: BattleScene) {

View File

@ -8,7 +8,7 @@ import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/enco
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { IntegerHolder, randSeedInt, randSeedShuffle } from "#app/utils";
import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { HiddenAbilityRateBoosterModifier, PokemonBaseStatTotalModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv";
@ -362,7 +362,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
}
newTypes.push(newType);
if (!newPokemon.mysteryEncounterData) {
newPokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, null, null, newTypes);
newPokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes);
} else {
newPokemon.mysteryEncounterData.types = newTypes;
}
@ -381,9 +381,11 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
// Def or SpDef
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
// const mod = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().newModifier(newPokemon, 20, stats);
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(null, [20, stats]);
const modifier = modType.newModifier(newPokemon);
scene.addModifier(modifier);
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(scene.getParty(), [20, stats]);
const modifier = modType?.newModifier(newPokemon);
if (modifier) {
scene.addModifier(modifier);
}
}
// Enable passive if previous had it
@ -422,8 +424,8 @@ function getOriginalBst(scene: BattleScene, pokemon: Pokemon) {
}
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
let newSpecies: PokemonSpecies;
while (!newSpecies) {
let newSpecies: PokemonSpecies | undefined;
while (isNullOrUndefined(newSpecies)) {
const bstCap = originalBst + bstSearchRange[1];
const bstMin = Math.max(originalBst + bstSearchRange[0], 0);
@ -442,7 +444,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
if (validSpecies?.length > 20) {
validSpecies = randSeedShuffle(validSpecies);
newSpecies = validSpecies.pop();
while (alreadyUsedSpecies.includes(newSpecies)) {
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
newSpecies = validSpecies.pop();
}
} else {
@ -452,7 +454,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
}
}
return newSpecies;
return newSpecies!;
}
function doShowDreamBackground(scene: BattleScene) {
@ -503,7 +505,7 @@ function doHideDreamBackground(scene: BattleScene) {
function doSideBySideTransformations(scene: BattleScene, transformations: PokemonTransformation[]) {
return new Promise<void>(resolve => {
const allTransformationPromises = [];
const allTransformationPromises: Promise<void>[] = [];
for (let i = 0; i < 3; i++) {
const delay = i * 4000;
scene.time.delayedCall(delay, () => {

View File

@ -8,16 +8,32 @@ import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequiremen
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
// export default interface MysteryEncounterOption {
//
// }
/**
* Used by {@link MysteryEncounterOptionBuilder} class to define required/optional properties on the {@link MysteryEncounterOption} class when building.
*
* Should ONLY contain properties that are necessary for {@link MysteryEncounterOption} construction.
* Post-construct and flag data properties are defined in the {@link MysteryEncounterOption} class itself.
*/
export interface IMysteryEncounterOption {
optionMode: MysteryEncounterOptionMode;
hasDexProgress: boolean;
requirements: EncounterSceneRequirement[];
primaryPokemonRequirements: EncounterPokemonRequirement[];
secondaryPokemonRequirements: EncounterPokemonRequirement[];
excludePrimaryFromSecondaryRequirements: boolean;
export default class MysteryEncounterOption {
dialogue?: OptionTextDisplay;
onPreOptionPhase?: OptionPhaseCallback;
onOptionPhase: OptionPhaseCallback;
onPostOptionPhase?: OptionPhaseCallback;
}
export default class MysteryEncounterOption implements IMysteryEncounterOption {
optionMode: MysteryEncounterOptionMode;
hasDexProgress: boolean;
requirements: EncounterSceneRequirement[];
@ -40,12 +56,14 @@ export default class MysteryEncounterOption {
/** Executes after the encounter is over. Usually this will be for calculating dialogueTokens or performing data updates */
onPostOptionPhase?: OptionPhaseCallback;
constructor(option: MysteryEncounterOption | null) {
Object.assign(this, option);
this.hasDexProgress = !isNullOrUndefined(this.hasDexProgress) ? this.hasDexProgress : false;
this.requirements = this.requirements ? this.requirements : [];
this.primaryPokemonRequirements = this.primaryPokemonRequirements ? this.primaryPokemonRequirements : [];
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ? this.secondaryPokemonRequirements : [];
constructor(option: IMysteryEncounterOption | null) {
if (!isNullOrUndefined(option)) {
Object.assign(this, option);
}
this.hasDexProgress = this.hasDexProgress ?? false;
this.requirements = this.requirements ?? [];
this.primaryPokemonRequirements = this.primaryPokemonRequirements ?? [];
this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ?? [];
}
hasRequirements() {
@ -70,7 +88,8 @@ export default class MysteryEncounterOption {
for (const req of this.primaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
if (req instanceof EncounterPokemonRequirement) {
qualified = qualified.filter(pkmn => req.queryParty(scene.getParty()).includes(pkmn));
const queryParty = req.queryParty(scene.getParty());
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
}
} else {
this.primaryPokemon = undefined;
@ -125,8 +144,8 @@ export default class MysteryEncounterOption {
for (const req of this.secondaryPokemonRequirements) {
if (req.meetsRequirement(scene)) {
if (req instanceof EncounterPokemonRequirement) {
qualified = qualified.filter(pkmn => req.queryParty(scene.getParty()).includes(pkmn));
const queryParty = req.queryParty(scene.getParty());
qualified = qualified.filter(pkmn => queryParty.includes(pkmn));
}
} else {
this.secondaryPokemon = [];
@ -138,7 +157,7 @@ export default class MysteryEncounterOption {
}
}
export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOption> {
export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterOption> {
optionMode: MysteryEncounterOptionMode = MysteryEncounterOptionMode.DEFAULT;
requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
@ -146,26 +165,17 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
excludePrimaryFromSecondaryRequirements: boolean = false;
isDisabledOnRequirementsNotMet: boolean = true;
hasDexProgress: boolean = false;
onPreOptionPhase?: OptionPhaseCallback;
onOptionPhase: OptionPhaseCallback;
onPostOptionPhase?: OptionPhaseCallback;
dialogue: OptionTextDisplay;
dialogue?: OptionTextDisplay;
hasRequirements = MysteryEncounter.prototype["hasRequirements"];
meetsRequirements = MysteryEncounter.prototype["meetsRequirements"];
pokemonMeetsPrimaryRequirements = MysteryEncounter.prototype["pokemonMeetsPrimaryRequirements"];
meetsPrimaryRequirementAndPrimaryPokemonSelected = MysteryEncounter.prototype["meetsPrimaryRequirementAndPrimaryPokemonSelected"];
meetsSupportingRequirementAndSupportingPokemonSelected = MysteryEncounter.prototype["meetsSupportingRequirementAndSupportingPokemonSelected"];
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<MysteryEncounterOption, "optionMode"> {
static newOptionWithMode(optionMode: MysteryEncounterOptionMode): MysteryEncounterOptionBuilder & Pick<IMysteryEncounterOption, "optionMode"> {
return Object.assign(new MysteryEncounterOptionBuilder(), { optionMode });
}
withHasDexProgress(hasDexProgress: boolean): this & Required<Pick<MysteryEncounterOption, "hasDexProgress">> {
withHasDexProgress(hasDexProgress: boolean): this & Required<Pick<IMysteryEncounterOption, "hasDexProgress">> {
return Object.assign(this, { hasDexProgress: hasDexProgress });
}
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounterOption, "requirements">> {
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounterOption, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement.");
}
@ -174,23 +184,23 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
return Object.assign(this, { requirements: this.requirements });
}
withSceneMoneyRequirement(requiredMoney: number, scalingMultiplier?: number) {
withSceneMoneyRequirement(requiredMoney?: number, scalingMultiplier?: number) {
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
}
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<MysteryEncounterOption, "onPreOptionPhase">> {
withPreOptionPhase(onPreOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPreOptionPhase">> {
return Object.assign(this, { onPreOptionPhase: onPreOptionPhase });
}
withOptionPhase(onOptionPhase: OptionPhaseCallback): this & Required<Pick<MysteryEncounterOption, "onOptionPhase">> {
withOptionPhase(onOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onOptionPhase">> {
return Object.assign(this, { onOptionPhase: onOptionPhase });
}
withPostOptionPhase(onPostOptionPhase: OptionPhaseCallback): this & Required<Pick<MysteryEncounterOption, "onPostOptionPhase">> {
withPostOptionPhase(onPostOptionPhase: OptionPhaseCallback): this & Required<Pick<IMysteryEncounterOption, "onPostOptionPhase">> {
return Object.assign(this, { onPostOptionPhase: onPostOptionPhase });
}
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<MysteryEncounterOption, "primaryPokemonRequirements">> {
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounterOption, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
@ -223,7 +233,7 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
}
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<MysteryEncounterOption, "secondaryPokemonRequirements">> {
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<IMysteryEncounterOption, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
@ -244,7 +254,7 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
return this;
}
build(this: MysteryEncounterOption) {
build(this: IMysteryEncounterOption) {
return new MysteryEncounterOption(this);
}
}

View File

@ -275,10 +275,10 @@ export class MoneyRequirement extends EncounterSceneRequirement {
requiredMoney: number; // Static value
scalingMultiplier: number; // Calculates required money based off wave index
constructor(requiredMoney: number, scalingMultiplier?: number) {
constructor(requiredMoney?: number, scalingMultiplier?: number) {
super();
this.requiredMoney = requiredMoney;
this.scalingMultiplier = scalingMultiplier ? scalingMultiplier : 0;
this.requiredMoney = requiredMoney ?? 0;
this.scalingMultiplier = scalingMultiplier ?? 0;
}
meetsRequirement(scene: BattleScene): boolean {
@ -287,14 +287,14 @@ export class MoneyRequirement extends EncounterSceneRequirement {
return false;
}
if (this?.scalingMultiplier > 0) {
if (this.scalingMultiplier > 0) {
this.requiredMoney = scene.getWaveMoneyAmount(this.scalingMultiplier);
}
return !(this?.requiredMoney > 0 && this.requiredMoney > money);
return !(this.requiredMoney > 0 && this.requiredMoney > money);
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const value = this?.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
const value = this.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
return ["money", value];
}
}
@ -367,10 +367,10 @@ export class NatureRequirement extends EncounterPokemonRequirement {
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (this.requiredNature.includes(pokemon.nature)) {
return ["nature", Nature[pokemon.nature]];
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon!.nature)) {
return ["nature", Nature[pokemon!.nature]];
}
return null;
return ["nature", ""];
}
}
@ -537,9 +537,9 @@ export class EvolutionTargetSpeciesRequirement extends EncounterPokemonRequireme
getMatchingDialogueToken(str:string, pokemon: PlayerPokemon): [RegExp, string] {
const evos = this.requiredEvolutionTargetSpecies.filter((evolutionTargetSpecies) => pokemon.getEvolution().speciesId === evolutionTargetSpecies);
if (evos.length > 0) {
return ["Evolution", Species[evos[0]]];
return ["evolution", Species[evos[0]]];
}
return null;
return ["evolution", ""];
}
}*/
@ -622,7 +622,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
return !this.requiredStatusEffect.some((statusEffect) => {
if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status?.effect === statusEffect;
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect;
} else {
return pokemon.status?.effect === statusEffect;
}
@ -634,9 +634,9 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const reqStatus = this.requiredStatusEffect.filter((a) => {
if (a === StatusEffect.NONE) {
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status?.effect === a;
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon!.status!.effect) || pokemon!.status!.effect === a;
}
return pokemon.status?.effect === a;
return pokemon!.status?.effect === a;
});
if (reqStatus.length > 0) {
return ["status", StatusEffect[reqStatus[0]]];
@ -698,7 +698,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
if (requiredItems.length > 0) {
return ["formChangeItem", FormChangeItem[requiredItems[0]]];
}
return null;
return ["formChangeItem", ""];
}
}
@ -748,7 +748,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
if (requiredItems.length > 0) {
return ["evolutionItem", EvolutionItem[requiredItems[0]]];
}
return null;
return ["evolutionItem", ""];
}
}
@ -789,18 +789,18 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon.getHeldItems().filter((it) => {
const requiredItems = pokemon?.getHeldItems().filter((it) => {
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
});
if (requiredItems.length > 0) {
if (requiredItems && requiredItems.length > 0) {
return ["heldItem", requiredItems[0].type.name];
}
return null;
return ["heldItem", ""];
}
}
export class LevelRequirement extends EncounterPokemonRequirement {
requiredLevelRange?: [number, number];
requiredLevelRange: [number, number];
minNumberOfPokemon: number;
invertQuery: boolean;
@ -809,12 +809,11 @@ export class LevelRequirement extends EncounterPokemonRequirement {
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredLevelRange = requiredLevelRange;
}
meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required level range
if (!isNullOrUndefined(this?.requiredLevelRange) && this.requiredLevelRange?.[0] <= this.requiredLevelRange?.[1]) {
if (!isNullOrUndefined(this.requiredLevelRange) && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) {
const partyPokemon = scene.getParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
@ -834,12 +833,12 @@ export class LevelRequirement extends EncounterPokemonRequirement {
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["level", pokemon.level.toString()];
return ["level", pokemon?.level.toString() ?? ""];
}
}
export class FriendshipRequirement extends EncounterPokemonRequirement {
requiredFriendshipRange?: [number, number];
requiredFriendshipRange: [number, number];
minNumberOfPokemon: number;
invertQuery: boolean;
@ -852,7 +851,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required friendship range
if (!isNullOrUndefined(this?.requiredFriendshipRange) && this.requiredFriendshipRange?.[0] <= this.requiredFriendshipRange?.[1]) {
if (!isNullOrUndefined(this.requiredFriendshipRange) && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]) {
const partyPokemon = scene.getParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
@ -872,7 +871,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["friendship", pokemon.friendship.toString()];
return ["friendship", pokemon?.friendship.toString() ?? ""];
}
}
@ -882,7 +881,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
* 1 -> 100% hp
*/
export class HealthRatioRequirement extends EncounterPokemonRequirement {
requiredHealthRange?: [number, number];
requiredHealthRange: [number, number];
minNumberOfPokemon: number;
invertQuery: boolean;
@ -895,7 +894,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required level range
if (!isNullOrUndefined(this?.requiredHealthRange) && this.requiredHealthRange?.[0] <= this.requiredHealthRange?.[1]) {
if (!isNullOrUndefined(this.requiredHealthRange) && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) {
const partyPokemon = scene.getParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
@ -917,12 +916,15 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["healthRatio", Math.floor(pokemon.getHpRatio() * 100).toString() + "%"];
if (!isNullOrUndefined(pokemon?.getHpRatio())) {
return ["healthRatio", Math.floor(pokemon!.getHpRatio() * 100).toString() + "%"];
}
return ["healthRatio", ""];
}
}
export class WeightRequirement extends EncounterPokemonRequirement {
requiredWeightRange?: [number, number];
requiredWeightRange: [number, number];
minNumberOfPokemon: number;
invertQuery: boolean;
@ -935,7 +937,7 @@ export class WeightRequirement extends EncounterPokemonRequirement {
meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required friendship range
if (!isNullOrUndefined(this?.requiredWeightRange) && this.requiredWeightRange?.[0] <= this.requiredWeightRange?.[1]) {
if (!isNullOrUndefined(this.requiredWeightRange) && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) {
const partyPokemon = scene.getParty();
const pokemonInRange = this.queryParty(partyPokemon);
if (pokemonInRange.length < this.minNumberOfPokemon) {
@ -955,7 +957,7 @@ export class WeightRequirement extends EncounterPokemonRequirement {
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["weight", pokemon.getWeight().toString()];
return ["weight", pokemon?.getWeight().toString() ?? ""];
}
}

View File

@ -24,14 +24,41 @@ export interface EncounterStartOfBattleEffect {
followUp?: boolean;
}
interface IMysteryEncounter {
meetsRequirements(scene: BattleScene): boolean;
pokemonMeetsPrimaryRequirements(scene: BattleScene, pokemon: Pokemon): boolean;
initIntroVisuals(scene: BattleScene): void;
populateDialogueTokensFromRequirements(scene: BattleScene): void;
setDialogueToken(key: string, value: string): void;
getSeedOffset(): number;
updateSeedOffset(scene: BattleScene): void;
/**
* Used by {@link MysteryEncounterBuilder} class to define required/optional properties on the {@link MysteryEncounter} class when building.
*
* Should ONLY contain properties that are necessary for {@link MysteryEncounter} construction.
* Post-construct and flag data properties are defined in the {@link MysteryEncounter} class itself.
*/
export interface IMysteryEncounter {
encounterType: MysteryEncounterType;
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
spriteConfigs: MysteryEncounterSpriteConfig[];
encounterTier: MysteryEncounterTier;
encounterAnimations?: EncounterAnim[];
hideBattleIntroMessage: boolean;
autoHideIntroVisuals: boolean;
enterIntroVisualsFromRight: boolean;
catchAllowed: boolean;
continuousEncounter: boolean;
maxAllowedEncounters: number;
onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean;
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterRewards?: (scene: BattleScene) => boolean;
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
requirements: EncounterSceneRequirement[];
primaryPokemonRequirements: EncounterPokemonRequirement[];
secondaryPokemonRequirements: EncounterPokemonRequirement[];
excludePrimaryFromSupportRequirements: boolean;
dialogue: MysteryEncounterDialogue;
enemyPartyConfigs: EnemyPartyConfig[];
dialogueTokens: Record<string, string>;
expMultiplier: number;
}
/**
@ -147,7 +174,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
* Can be set for uses programatic dialogue during an encounter (storing the name of one of the party's pokemon, etc.)
* Example use: see MYSTERIOUS_CHEST
*/
dialogueTokens: Map<string, string>;
dialogueTokens: Record<string, string>;
/**
* Should be set depending upon option selected as part of an encounter
* For example, if there is no battle as part of the encounter/selected option, should be set to NO_BATTLE
@ -171,7 +198,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
/**
* Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases
*/
startOfBattleEffects?: EncounterStartOfBattleEffect[];
startOfBattleEffects: EncounterStartOfBattleEffect[] = [];
/**
* Can be set higher or lower based on the type of battle or exp gained for an option/encounter
* Defaults to 1
@ -188,7 +215,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
*/
private seedOffset?: any;
constructor(encounter: MysteryEncounter | null) {
constructor(encounter: IMysteryEncounter | null) {
if (!isNullOrUndefined(encounter)) {
Object.assign(this, encounter);
}
@ -207,9 +234,9 @@ export default class MysteryEncounter implements IMysteryEncounter {
// Reset any dirty flags or encounter data
this.startOfBattleEffectsComplete = false;
this.lockEncounterRewardTiers = true;
this.dialogueTokens = new Map<string, string>();
this.dialogueTokens = {};
this.enemyPartyConfigs = [];
this.startOfBattleEffects = [];
// this.startOfBattleEffects = [];
this.introVisuals = undefined;
this.misc = null;
this.expMultiplier = 1;
@ -361,7 +388,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
const opt = this.options[i];
opt.meetsRequirements(scene);
const j = i + 1;
if (opt.requirements?.length > 0) {
if (opt.requirements.length > 0) {
for (const req of opt.requirements) {
const dialogueToken = req.getDialogueToken(scene);
if (dialogueToken?.length === 2) {
@ -369,7 +396,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
}
}
}
if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon && opt.primaryPokemon.length > 0) {
if (opt.primaryPokemonRequirements.length > 0 && opt.primaryPokemon) {
this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.getNameToRender());
for (const req of opt.primaryPokemonRequirements) {
if (!req.invertQuery) {
@ -424,26 +451,16 @@ export default class MysteryEncounter implements IMysteryEncounter {
* Builder class for creating a MysteryEncounter
* must call `build()` at the end after specifying all params for the MysteryEncounter
*/
export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
encounterType: MysteryEncounterType;
encounterMode: MysteryEncounterMode;
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [new MysteryEncounterOption(null), new MysteryEncounterOption(null)];
spriteConfigs: MysteryEncounterSpriteConfig[];
export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
enemyPartyConfigs: EnemyPartyConfig[] = [];
dialogue: MysteryEncounterDialogue = {};
encounterTier: MysteryEncounterTier;
encounterAnimations: EncounterAnim[];
requirements: EncounterSceneRequirement[] = [];
primaryPokemonRequirements: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements: EncounterPokemonRequirement[] = [];
excludePrimaryFromSupportRequirements: boolean;
dialogueTokens: Map<string, string> = new Map<string, string>();
doEncounterExp?: (scene: BattleScene) => boolean;
doEncounterRewards?: (scene: BattleScene) => boolean;
onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean;
excludePrimaryFromSupportRequirements: boolean = true;
dialogueTokens: Record<string, string> = {};
hideBattleIntroMessage: boolean = false;
autoHideIntroVisuals: boolean = true;
@ -455,21 +472,6 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
maxAllowedEncounters: number = 3;
expMultiplier: number = 1;
/**
* Builder class has to re-declare the {@link MysteryEncounter} class functions so
* the compiler does not yell about user not defining a non-optional property
*/
meetsRequirements = MysteryEncounter.prototype["meetsRequirements"];
pokemonMeetsPrimaryRequirements = MysteryEncounter.prototype["pokemonMeetsPrimaryRequirements"];
initIntroVisuals = MysteryEncounter.prototype["initIntroVisuals"];
populateDialogueTokensFromRequirements = MysteryEncounter.prototype["populateDialogueTokensFromRequirements"];
setDialogueToken = MysteryEncounter.prototype["setDialogueToken"];
getSeedOffset = MysteryEncounter.prototype["getSeedOffset"];
updateSeedOffset = MysteryEncounter.prototype["updateSeedOffset"];
meetsPrimaryRequirementAndPrimaryPokemonSelected = MysteryEncounter.prototype["meetsPrimaryRequirementAndPrimaryPokemonSelected"];
meetsSecondaryRequirementAndSecondaryPokemonSelected = MysteryEncounter.prototype["meetsSecondaryRequirementAndSecondaryPokemonSelected"];
/**
* REQUIRED
*/
@ -480,7 +482,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param encounterType
* @returns this
*/
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<MysteryEncounter, "encounterType"> {
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
return Object.assign(new MysteryEncounterBuilder(), { encounterType });
}
@ -492,14 +494,13 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
* @returns
*/
withOption(option: MysteryEncounterOption): this & Pick<MysteryEncounter, "options"> {
if (this.options[0] === null) {
return Object.assign(this, { options: [option, this.options[0]] });
} else if (this.options[1] === null) {
return Object.assign(this, { options: [this.options[0], option] });
withOption(option: MysteryEncounterOption): this & Pick<IMysteryEncounter, "options"> {
if (!this.options) {
const options = [option];
return Object.assign(this, { options });
} else {
this.options.push(option);
return Object.assign(this, { options: this.options });
return this;
}
}
@ -514,7 +515,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param callback - {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<MysteryEncounter, "options"> {
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
}
@ -528,7 +529,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param callback - {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<MysteryEncounter, "options"> {
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
@ -542,7 +543,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param spriteConfigs
* @returns
*/
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<MysteryEncounter, "spriteConfigs"> {
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<IMysteryEncounter, "spriteConfigs"> {
return Object.assign(this, { spriteConfigs: spriteConfigs });
}
@ -571,7 +572,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param encounterTier
* @returns
*/
withEncounterTier(encounterTier: MysteryEncounterTier): this & Pick<MysteryEncounter, "encounterTier"> {
withEncounterTier(encounterTier: MysteryEncounterTier): this & Pick<IMysteryEncounter, "encounterTier"> {
return Object.assign(this, { encounterTier: encounterTier });
}
@ -582,7 +583,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param encounterAnimations
* @returns
*/
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<MysteryEncounter, "encounterAnimations">> {
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
return Object.assign(this, { encounterAnimations: animations });
}
@ -593,7 +594,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* Default false
* @param continuousEncounter
*/
withContinuousEncounter(continuousEncounter: boolean): this & Required<Pick<MysteryEncounter, "continuousEncounter">> {
withContinuousEncounter(continuousEncounter: boolean): this & Required<Pick<IMysteryEncounter, "continuousEncounter">> {
return Object.assign(this, { continuousEncounter: continuousEncounter });
}
@ -602,7 +603,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param maxAllowedEncounters
* @returns
*/
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<MysteryEncounter, "maxAllowedEncounters">> {
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters });
}
@ -613,12 +614,12 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param requirement
* @returns
*/
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounter, "requirements">> {
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounter, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement.");
}
this.requirements.push(requirement);
return Object.assign(this, { requirements: this.requirements });
return this;
}
/**
@ -628,7 +629,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param max optional max wave. If not given, defaults to min => exact wave
* @returns
*/
withSceneWaveRangeRequirement(min: number, max?: number): this & Required<Pick<MysteryEncounter, "requirements">> {
withSceneWaveRangeRequirement(min: number, max?: number): this & Required<Pick<IMysteryEncounter, "requirements">> {
return this.withSceneRequirement(new WaveRangeRequirement([min, max ?? min]));
}
@ -640,7 +641,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param excludeFainted - if true, only counts unfainted mons
* @returns
*/
withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required<Pick<MysteryEncounter, "requirements">> {
withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
}
@ -650,7 +651,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param requirement {@linkcode EncounterPokemonRequirement}
* @returns
*/
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<MysteryEncounter, "primaryPokemonRequirements">> {
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
@ -667,7 +668,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param invertQuery if true will invert the query
* @returns
*/
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<MysteryEncounter, "primaryPokemonRequirements">> {
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery));
}
@ -679,14 +680,14 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param invertQuery if true will invert the query
* @returns
*/
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<MysteryEncounter, "primaryPokemonRequirements">> {
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery));
}
// TODO: Maybe add an optional parameter for excluding primary pokemon from the support cast?
// ex. if your only grass type pokemon, a snivy, is chosen as primary, if the support pokemon requires a grass type, the event won't trigger because
// it's already been
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<MysteryEncounter, "secondaryPokemonRequirements">> {
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
@ -706,7 +707,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "doEncounterRewards">> {
withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
return Object.assign(this, { doEncounterRewards: doEncounterRewards });
}
@ -720,7 +721,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "doEncounterExp">> {
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
return Object.assign(this, { doEncounterExp: doEncounterExp });
}
@ -731,8 +732,8 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param onInit - synchronous callback function to perform as soon as the encounter is selected for the next phase
* @returns
*/
withOnInit(onInit: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "onInit">> {
return Object.assign(this, { onInit: onInit });
withOnInit(onInit: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onInit">> {
return Object.assign(this, { onInit });
}
/**
@ -741,7 +742,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in
* @returns
*/
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "onVisualsStart">> {
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onVisualsStart">> {
return Object.assign(this, { onVisualsStart: onVisualsStart });
}
@ -751,7 +752,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param catchAllowed - if true, allows enemy pokemon to be caught during the encounter
* @returns
*/
withCatchAllowed(catchAllowed: boolean): this & Required<Pick<MysteryEncounter, "catchAllowed">> {
withCatchAllowed(catchAllowed: boolean): this & Required<Pick<IMysteryEncounter, "catchAllowed">> {
return Object.assign(this, { catchAllowed: catchAllowed });
}
@ -759,7 +760,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param hideBattleIntroMessage - if true, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
* @returns
*/
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<MysteryEncounter, "hideBattleIntroMessage">> {
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
return Object.assign(this, { hideBattleIntroMessage: hideBattleIntroMessage });
}
@ -767,7 +768,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* @param autoHideIntroVisuals - if false, will not hide the intro visuals that are displayed at the beginning of encounter
* @returns
*/
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<MysteryEncounter, "autoHideIntroVisuals">> {
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals });
}
@ -776,7 +777,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
* Default false
* @returns
*/
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<MysteryEncounter, "enterIntroVisualsFromRight">> {
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> {
return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight });
}
@ -856,7 +857,7 @@ export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
*
* @returns
*/
build(this: MysteryEncounter): MysteryEncounter {
build(this: IMysteryEncounter): MysteryEncounter {
return new MysteryEncounter(this);
}
}

View File

@ -278,7 +278,7 @@ export function initMysteryEncounters() {
extremeBiomeEncounters.forEach(encounter => {
EXTREME_ENCOUNTER_BIOMES.forEach(biome => {
const encountersForBiome = mysteryEncountersByBiome.get(biome);
if (!encountersForBiome.includes(encounter)) {
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
encountersForBiome.push(encounter);
}
});
@ -287,7 +287,7 @@ export function initMysteryEncounters() {
nonExtremeBiomeEncounters.forEach(encounter => {
NON_EXTREME_ENCOUNTER_BIOMES.forEach(biome => {
const encountersForBiome = mysteryEncountersByBiome.get(biome);
if (!encountersForBiome.includes(encounter)) {
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
encountersForBiome.push(encounter);
}
});
@ -296,7 +296,7 @@ export function initMysteryEncounters() {
humanTransitableBiomeEncounters.forEach(encounter => {
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
const encountersForBiome = mysteryEncountersByBiome.get(biome);
if (!encountersForBiome.includes(encounter)) {
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
encountersForBiome.push(encounter);
}
});
@ -305,7 +305,7 @@ export function initMysteryEncounters() {
civilizationBiomeEncounters.forEach(encounter => {
CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => {
const encountersForBiome = mysteryEncountersByBiome.get(biome);
if (!encountersForBiome.includes(encounter)) {
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
encountersForBiome.push(encounter);
}
});

View File

@ -1,6 +1,6 @@
import BattleScene from "#app/battle-scene";
import { Moves } from "#app/enums/moves";
import { PlayerPokemon } from "#app/field/pokemon";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils";
import { EncounterPokemonRequirement } from "../mystery-encounter-requirements";
@ -37,7 +37,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
this.excludeEggMoves = excludeEggMoves ?? false;
this.includeFainted = includeFainted ?? false;
this.minNumberOfPokemon = minNumberOfPokemon ?? 1;
this.invertQuery = invertQuery;
this.invertQuery = invertQuery ?? false;
}
override meetsRequirement(scene: BattleScene): boolean {
@ -66,7 +66,7 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement {
}
override getDialogueToken(_scene: BattleScene, _pokemon?: PlayerPokemon): [string, string] {
return ["requiredMoves", this.requiredMoves.join(", ")];
return ["requiredMoves", this.requiredMoves.map(m => new PokemonMove(m).getName()).join(", ")];
}
private getPokemonLevelMoves(pkm: PlayerPokemon): Moves[] {

View File

@ -4,34 +4,37 @@ import { UiTheme } from "#enums/ui-theme";
import { isNullOrUndefined } from "#app/utils";
import i18next from "i18next";
export function getEncounterText(scene: BattleScene, keyOrString: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
export function getEncounterText(scene: BattleScene, keyOrString?: string, primaryStyle?: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string | null {
if (isNullOrUndefined(keyOrString)) {
return null;
}
let textString: string = getTextWithDialogueTokens(scene, keyOrString);
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString);
// Can only color the text if a Primary Style is defined
// primaryStyle is applied to all text that does not have its own specified style
if (primaryStyle) {
if (primaryStyle && textString) {
textString = getTextWithColors(textString, primaryStyle, uiTheme);
}
return textString;
}
function getTextWithDialogueTokens(scene: BattleScene, keyOrString: string): string {
function getTextWithDialogueTokens(scene: BattleScene, keyOrString?: string): string | null {
if (isNullOrUndefined(keyOrString)) {
return null;
}
if (i18next.exists(keyOrString, scene.currentBattle?.mysteryEncounter?.dialogueTokens)) {
const tokens = scene.currentBattle?.mysteryEncounter?.dialogueTokens;
// @ts-ignore
if (i18next.exists(keyOrString, tokens)) {
const stringArray = [`${keyOrString}`] as any;
stringArray.raw = [`${keyOrString}`];
return i18next.t(stringArray, scene.currentBattle?.mysteryEncounter?.dialogueTokens);
// @ts-ignore
return i18next.t(stringArray, tokens) as string;
}
return keyOrString;
return keyOrString ?? null;
}
/**
@ -40,8 +43,8 @@ function getTextWithDialogueTokens(scene: BattleScene, keyOrString: string): str
* @param contentKey
*/
export function queueEncounterMessage(scene: BattleScene, contentKey: string): void {
const text: string = getEncounterText(scene, contentKey);
scene.queueMessage(text, null, true);
const text: string | null = getEncounterText(scene, contentKey);
scene.queueMessage(text ?? "", null, true);
}
/**
@ -53,8 +56,8 @@ export function queueEncounterMessage(scene: BattleScene, contentKey: string): v
*/
export function showEncounterText(scene: BattleScene, contentKey: string, callbackDelay: number = 0, prompt: boolean = true): Promise<void> {
return new Promise<void>(resolve => {
const text: string = getEncounterText(scene, contentKey);
scene.ui.showText(text, null, () => resolve(), callbackDelay, prompt);
const text: string | null = getEncounterText(scene, contentKey);
scene.ui.showText(text ?? "", null, () => resolve(), callbackDelay, prompt);
});
}
@ -67,8 +70,8 @@ export function showEncounterText(scene: BattleScene, contentKey: string, callba
*/
export function showEncounterDialogue(scene: BattleScene, textContentKey: string, speakerContentKey: string, callbackDelay: number = 0): Promise<void> {
return new Promise<void>(resolve => {
const text: string = getEncounterText(scene, textContentKey);
const speaker: string = getEncounterText(scene, speakerContentKey);
scene.ui.showDialogue(text, speaker, null, () => resolve(), callbackDelay);
const text: string | null = getEncounterText(scene, textContentKey);
const speaker: string | null = getEncounterText(scene, speakerContentKey);
scene.ui.showDialogue(text ?? "", speaker ?? "", null, () => resolve(), callbackDelay);
});
}

View File

@ -1,4 +1,4 @@
import { BattlerIndex, BattleType } from "#app/battle";
import Battle, { BattlerIndex, BattleType } from "#app/battle";
import { biomeLinks, BiomePoolTier } from "#app/data/biomes";
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
@ -10,7 +10,7 @@ import { BattleEndPhase, EggLapsePhase, ExpPhase, GameOverPhase, MovePhase, Sele
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
import PokemonData from "#app/system/pokemon-data";
import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
import { PartyOption, PartyUiMode, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import { Mode } from "#app/ui/ui";
import * as Utils from "#app/utils";
import { isNullOrUndefined } from "#app/utils";
@ -101,29 +101,30 @@ export interface EnemyPartyConfig {
* @param partyConfig - Can pass various customizable attributes for the enemy party, see EnemyPartyConfig
*/
export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: EnemyPartyConfig): Promise<void> {
const loaded = false;
const loadEnemyAssets = [];
const loaded: boolean = false;
const loadEnemyAssets: Promise<void>[] = [];
const battle = scene.currentBattle;
const battle: Battle = scene.currentBattle;
let doubleBattle = partyConfig?.doubleBattle;
let doubleBattle: boolean = partyConfig?.doubleBattle ?? false;
// Trainer
const trainerType = partyConfig?.trainerType;
let trainerConfig = partyConfig?.trainerConfig;
if (trainerType || trainerConfig) {
const partyTrainerConfig = partyConfig?.trainerConfig;
let trainerConfig: TrainerConfig;
if (!isNullOrUndefined(trainerType) || partyTrainerConfig) {
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.TRAINER_BATTLE;
if (scene.currentBattle.trainer) {
scene.currentBattle.trainer.setVisible(false);
scene.currentBattle.trainer.destroy();
}
trainerConfig = partyConfig?.trainerConfig ? partyConfig?.trainerConfig : trainerConfigs[trainerType];
trainerConfig = partyConfig?.trainerConfig ? partyConfig?.trainerConfig : trainerConfigs[trainerType!];
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && partyConfig.doubleBattle);
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
doubleBattle = doubleTrainer;
const trainerFemale = isNullOrUndefined(partyConfig.female) ? !!(Utils.randSeedInt(2)) : partyConfig.female;
const newTrainer = new Trainer(scene, trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, null, null, null, trainerConfig);
const newTrainer = new Trainer(scene, trainerConfig.trainerType, doubleTrainer ? TrainerVariant.DOUBLE : trainerFemale ? TrainerVariant.FEMALE : TrainerVariant.DEFAULT, undefined, undefined, undefined, trainerConfig);
newTrainer.x += 300;
newTrainer.setVisible(false);
scene.field.add(newTrainer);
@ -134,7 +135,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
} else {
// Wild
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.WILD_BATTLE;
battle.enemyLevels = new Array(partyConfig?.pokemonConfigs?.length > 0 ? partyConfig?.pokemonConfigs?.length : doubleBattle ? 2 : 1).fill(null).map(() => scene.currentBattle.getLevelForWave());
const numEnemies = partyConfig?.pokemonConfigs && partyConfig.pokemonConfigs.length > 0 ? partyConfig?.pokemonConfigs?.length : doubleBattle ? 2 : 1;
battle.enemyLevels = new Array(numEnemies).fill(null).map(() => scene.currentBattle.getLevelForWave());
}
scene.getEnemyParty().forEach(enemyPokemon => enemyPokemon.destroy());
@ -146,7 +148,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// This can be amplified or counteracted by setting levelAdditiveMultiplier in config
// levelAdditiveMultiplier value of 0.5 will halve the modifier scaling, 2 will double it, etc.
// Leaving null/undefined will disable level scaling
const mult = !isNullOrUndefined(partyConfig.levelAdditiveMultiplier) ? partyConfig.levelAdditiveMultiplier : 0;
const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveMultiplier) ? partyConfig.levelAdditiveMultiplier! : 0;
const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0);
battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
@ -155,10 +157,10 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
let dataSource;
let isBoss = false;
if (!loaded) {
if (trainerType || trainerConfig) {
if ((!isNullOrUndefined(trainerType) || trainerConfig) && battle.trainer) {
// Allows overriding a trainer's pokemon to use specific species/data
if (e < partyConfig?.pokemonConfigs?.length) {
const config = partyConfig?.pokemonConfigs?.[e];
if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) {
const config = partyConfig.pokemonConfigs[e];
level = config.level ? config.level : level;
dataSource = config.dataSource;
enemySpecies = config.species;
@ -168,8 +170,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
}
} else {
if (e < partyConfig?.pokemonConfigs?.length) {
const config = partyConfig?.pokemonConfigs?.[e];
if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) {
const config = partyConfig.pokemonConfigs[e];
level = config.level ? config.level : level;
dataSource = config.dataSource;
enemySpecies = config.species;
@ -201,8 +203,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
scene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig));
}
if (e < partyConfig?.pokemonConfigs?.length) {
const config = partyConfig?.pokemonConfigs?.[e];
if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) {
const config = partyConfig.pokemonConfigs[e];
// Generate new id, reset status and HP in case using data source
if (config.dataSource) {
@ -211,24 +213,24 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// Set form
if (!isNullOrUndefined(config.formIndex)) {
enemyPokemon.formIndex = config.formIndex;
enemyPokemon.formIndex = config.formIndex!;
}
// Set shiny
if (!isNullOrUndefined(config.shiny)) {
enemyPokemon.shiny = config.shiny;
enemyPokemon.shiny = config.shiny!;
}
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
if (!isNullOrUndefined(config.mysteryEncounterData)) {
enemyPokemon.mysteryEncounterData = config.mysteryEncounterData;
enemyPokemon.mysteryEncounterData = config.mysteryEncounterData!;
}
// Set Boss
if (config.isBoss) {
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true);
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true);
if (!isNullOrUndefined(config.bossSegmentModifier)) {
segments += config.bossSegmentModifier;
segments += config.bossSegmentModifier!;
}
enemyPokemon.setBoss(true, segments);
}
@ -253,7 +255,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
if (statusEffects) {
// Default to cureturn 3 for sleep
const status = Array.isArray(statusEffects) ? statusEffects[0] : statusEffects;
const cureTurn = Array.isArray(statusEffects) ? statusEffects[1] : statusEffects === StatusEffect.SLEEP ? 3 : null;
const cureTurn = Array.isArray(statusEffects) ? statusEffects[1] : statusEffects === StatusEffect.SLEEP ? 3 : undefined;
enemyPokemon.status = new Status(status, 0, cureTurn);
}
@ -264,24 +266,24 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// Set ability
if (!isNullOrUndefined(config.abilityIndex)) {
enemyPokemon.abilityIndex = config.abilityIndex;
enemyPokemon.abilityIndex = config.abilityIndex!;
}
// Set gender
if (!isNullOrUndefined(config.gender)) {
enemyPokemon.gender = config.gender;
enemyPokemon.summonData.gender = config.gender;
enemyPokemon.gender = config.gender!;
enemyPokemon.summonData.gender = config.gender!;
}
// Set moves
if (config?.moveSet?.length > 0) {
if (config?.moveSet && config.moveSet.length > 0) {
const moves = config.moveSet.map(m => new PokemonMove(m));
enemyPokemon.moveset = moves;
enemyPokemon.summonData.moveset = moves;
}
// Set tags
if (config.tags?.length > 0) {
if (config.tags && config.tags.length > 0) {
const tags = config.tags;
tags.forEach(tag => enemyPokemon.addTag(tag));
}
@ -319,8 +321,12 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
});
if (!loaded) {
regenerateModifierPoolThresholds(scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
const customModifierTypes = partyConfig?.pokemonConfigs?.map(config => config?.modifierConfigs);
scene.generateEnemyModifiers(customModifierTypes);
const customModifierTypes = partyConfig?.pokemonConfigs
?.filter(config => config?.modifierConfigs)
.map(config => config.modifierConfigs!);
if (customModifierTypes) {
scene.generateEnemyModifiers(customModifierTypes);
}
}
}
@ -368,7 +374,7 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
*/
export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType {
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier)!;
let result: ModifierType = modifierTypes[modifierId]?.();
// Populates item id and tier (order matters)
@ -376,8 +382,8 @@ export function generateModifierType(scene: BattleScene, modifier: () => Modifie
.withIdFromFunc(modifierTypes[modifierId])
.withTierFromPool();
result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result;
return result;
const generatedResult = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result;
return generatedResult ?? result;
}
/**
@ -399,7 +405,7 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
* @param onPokemonNotSelected - Any logic that needs to be performed if no Pokemon is chosen
* @param selectablePokemonFilter
*/
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?: PokemonSelectFilter): Promise<boolean> {
return new Promise(resolve => {
const modeToSetOnExit = scene.ui.getMode();
@ -451,7 +457,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
};
const textPromptKey = scene.currentBattle.mysteryEncounter.selectedOption.dialogue.secondOptionPrompt;
const textPromptKey = scene.currentBattle.mysteryEncounter?.selectedOption?.dialogue?.secondOptionPrompt;
if (!textPromptKey) {
displayOptions();
} else {
@ -486,7 +492,7 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
}
if (customShopRewards) {
scene.unshiftPhase(new SelectModifierPhase(scene, 0, null, customShopRewards));
scene.unshiftPhase(new SelectModifierPhase(scene, 0, undefined, customShopRewards));
} else {
scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
}
@ -530,7 +536,7 @@ export function setEncounterExp(scene: BattleScene, participantId: integer | int
const multipleParticipantExpBonusModifier = scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier;
const nonFaintedPartyMembers = party.filter(p => p.hp);
const expPartyMembers = nonFaintedPartyMembers.filter(p => p.level < scene.getMaxExpLevel());
const partyMemberExp = [];
const partyMemberExp: number[] = [];
// EXP value calculation is based off Pokemon.getExpValue
let expValue = Math.floor(baseExpValue * (useWaveIndex ? scene.currentBattle.waveIndex : 1) / 5 + 1);
@ -581,7 +587,7 @@ export function setEncounterExp(scene: BattleScene, participantId: integer | int
const medianLevel = Math.floor(totalLevel / expPartyMembers.length);
const recipientExpPartyMemberIndexes = [];
const recipientExpPartyMemberIndexes: number[] = [];
expPartyMembers.forEach((expPartyMember, epm) => {
if (expPartyMember.level <= medianLevel) {
recipientExpPartyMemberIndexes.push(epm);
@ -720,7 +726,7 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide:
scene.field.remove(pokemon, true);
});
scene.currentBattle.mysteryEncounter.introVisuals = null;
scene.currentBattle.mysteryEncounter.introVisuals = undefined;
}
resolve(true);
}
@ -808,11 +814,13 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
})
.map(b => !Array.isArray(b) ? b : b[0]);
}, i * 100);
const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b));
if (specialBiomes.length > 0) {
currentBiome = specialBiomes[Utils.randSeedInt(specialBiomes.length)];
} else {
currentBiome = biomes[Utils.randSeedInt(biomes.length)];
if (biomes! && biomes.length > 0) {
const specialBiomes = biomes.filter(b => alwaysPickTheseBiomes.includes(b));
if (specialBiomes.length > 0) {
currentBiome = specialBiomes[Utils.randSeedInt(specialBiomes.length)];
} else {
currentBiome = biomes[Utils.randSeedInt(biomes.length)];
}
}
} else if (biomeLinks.hasOwnProperty(currentBiome)) {
currentBiome = (biomeLinks[currentBiome] as Biome);
@ -840,7 +848,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
// Otherwise, roll encounter
const roll = Utils.randSeedInt(256);
validMEfloorsByBiome.set(Biome[currentBiome], validMEfloorsByBiome.get(Biome[currentBiome]) + 1);
validMEfloorsByBiome.set(Biome[currentBiome], (validMEfloorsByBiome.get(Biome[currentBiome]) ?? 0) + 1);
// If total number of encounters is lower than expected for the run, slightly favor a new encounter
// Do the reverse as well
@ -866,7 +874,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
const rareThreshold = totalWeight - tierWeights[0] - tierWeights[1] - tierWeights[2]; // 64 - 32 - 16 - 10 = 6
tierValue > commonThreshold ? ++numEncounters[0] : tierValue > uncommonThreshold ? ++numEncounters[1] : tierValue > rareThreshold ? ++numEncounters[2] : ++numEncounters[3];
encountersByBiome.set(Biome[currentBiome], encountersByBiome.get(Biome[currentBiome]) + 1);
encountersByBiome.set(Biome[currentBiome], (encountersByBiome.get(Biome[currentBiome]) ?? 0) + 1);
} else {
encounterRate += WEIGHT_INCREMENT_ON_SPAWN_MISS;
}
@ -899,7 +907,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
const encountersPerRunPerBiome = encountersByBiomeRuns.reduce((a, b) => {
for (const biome of a.keys()) {
a.set(biome, a.get(biome) + b.get(biome));
a.set(biome, a.get(biome)! + b.get(biome)!);
}
return a;
});
@ -910,7 +918,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
const validMEFloorsPerRunPerBiome = validFloorsByBiome.reduce((a, b) => {
for (const biome of a.keys()) {
a.set(biome, a.get(biome) + b.get(biome));
a.set(biome, a.get(biome)! + b.get(biome)!);
}
return a;
});

View File

@ -52,13 +52,13 @@ export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: bo
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
chosenIndex = randSeedInt(faintedMons.length);
chosenPokemon = faintedMons.at(chosenIndex);
chosenPokemon = faintedMons[chosenIndex];
} else if (isAllowedInBattle) {
chosenIndex = randSeedInt(unfaintedMons.length);
chosenPokemon = unfaintedMons.at(chosenIndex);
chosenPokemon = unfaintedMons[chosenIndex];
} else {
chosenIndex = randSeedInt(party.length);
chosenPokemon = party.at(chosenIndex);
chosenPokemon = party[chosenIndex];
}
return chosenPokemon;
@ -72,17 +72,17 @@ export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: bo
*/
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon;
party.every(p => {
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
return true;
continue;
}
pokemon = pokemon ? pokemon?.level < p?.level ? p : pokemon : p;
return true;
});
}
return pokemon;
return pokemon!;
}
/**
@ -94,17 +94,17 @@ export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: bool
*/
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: Stat, unfainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon;
party.every(p => {
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
return true;
continue;
}
pokemon = pokemon ? pokemon.getStat(stat) < p?.getStat(stat) ? p : pokemon : p;
return true;
});
}
return pokemon;
return pokemon!;
}
/**
@ -115,17 +115,17 @@ export function getHighestStatPlayerPokemon(scene: BattleScene, stat: Stat, unfa
*/
export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon;
party.every(p => {
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
return true;
continue;
}
pokemon = pokemon ? pokemon?.level > p?.level ? p : pokemon : p;
return true;
});
}
return pokemon;
return pokemon!;
}
/**
@ -136,17 +136,17 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boole
*/
export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
const party = scene.getParty();
let pokemon: PlayerPokemon;
party.every(p => {
let pokemon: PlayerPokemon | null = null;
for (const p of party) {
if (unfainted && p.isFainted()) {
return true;
continue;
}
pokemon = pokemon ? pokemon?.stats.reduce((a, b) => a + b) < p?.stats.reduce((a, b) => a + b) ? p : pokemon : p;
return true;
});
}
return pokemon;
return pokemon!;
}
/**
@ -166,8 +166,8 @@ export function getRandomSpeciesByStarterTier(starterTiers: number | [number, nu
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
.map(s => [getPokemonSpecies(s[0]), s[1]]);
if (!isNullOrUndefined(types) && types.length > 0) {
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || types.includes(s[0].type2));
if (types && types.length > 0) {
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2!)));
}
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
@ -260,10 +260,12 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h
* @param value
*/
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE().generateType(null, [value]);
const modifier = modType.newModifier(pokemon);
await pokemon.scene.addModifier(modifier, false, false, false, true);
pokemon.calculateStats();
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE().generateType(pokemon.scene.getParty(), [value]);
const modifier = modType?.newModifier(pokemon);
if (modifier) {
await pokemon.scene.addModifier(modifier, false, false, false, true);
pokemon.calculateStats();
}
}
/**
@ -479,7 +481,7 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number,
* @param showCatchObtainMessage
* @param isObtain
*/
export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise<void> {
export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite | null, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise<void> {
scene.unshiftPhase(new VictoryPhase(scene, BattlerIndex.ENEMY, true));
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
@ -508,7 +510,9 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
const doPokemonCatchMenu = () => {
const end = () => {
scene.pokemonInfoContainer.hide();
removePb(scene, pokeball);
if (pokeball) {
removePb(scene, pokeball);
}
resolve();
};
const removePokemon = () => {

View File

@ -146,7 +146,7 @@ export class Arena {
return this.randomSpecies(waveIndex, level, (attempt || 0) + 1);
}
const newSpeciesId = ret.getWildSpeciesForLevel(level, true, isBoss, this.scene.gameMode);
const newSpeciesId = ret.getWildSpeciesForLevel(level, true, !!isBoss, this.scene.gameMode);
if (newSpeciesId !== ret.speciesId) {
console.log("Replaced", Species[ret.speciesId], "with", Species[newSpeciesId]);
ret = getPokemonSpecies(newSpeciesId);

View File

@ -4,6 +4,7 @@ import MysteryEncounter from "../data/mystery-encounters/mystery-encounter";
import { Species } from "#enums/species";
import { isNullOrUndefined } from "#app/utils";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig;
type KnownFileRoot =
| "arenas"
@ -85,7 +86,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
};
if (!isNullOrUndefined(result.species)) {
const keys = getSpriteKeysFromSpecies(result.species);
const keys = getSpriteKeysFromSpecies(result.species!);
result.spriteKey = keys.spriteKey;
result.fileRoot = keys.fileRoot;
result.isPokemon = true;
@ -164,7 +165,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
}
}
if (!isNaN(alpha)) {
if (!isNullOrUndefined(alpha)) {
sprite.setAlpha(alpha);
tintSprite.setAlpha(alpha);
}
@ -290,7 +291,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
const tintSprites = this.getTintSprites();
this.spriteConfigs.forEach((config, i) => {
if (!config.disableAnimation) {
const trainerAnimConfig = {
const trainerAnimConfig: PlayAnimationConfig = {
key: config.spriteKey,
repeat: config?.repeat ? -1 : 0,
startFrame: config?.startFrame ?? 0
@ -307,7 +308,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
*/
getSpriteAtIndex(index: number): Phaser.GameObjects.Sprite[] {
if (!this.spriteConfigs) {
return;
return [];
}
const ret: Phaser.GameObjects.Sprite[] = [];
@ -319,7 +320,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
getSprites(): Phaser.GameObjects.Sprite[] {
if (!this.spriteConfigs) {
return;
return [];
}
const ret: Phaser.GameObjects.Sprite[] = [];
@ -331,7 +332,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
getTintSprites(): Phaser.GameObjects.Sprite[] {
if (!this.spriteConfigs) {
return;
return [];
}
const ret: Phaser.GameObjects.Sprite[] = [];

View File

@ -108,7 +108,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public mysteryEncounterData: MysteryEncounterPokemonData;
/** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */
public mysteryEncounterBattleEffects: (pokemon: Pokemon) => void = null;
public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
public fieldPosition: FieldPosition;

View File

@ -1,6 +1,6 @@
export const delibirdyDialogue = {
intro: "A pack of Delibird have appeared!",
title: "Delibird-y",
title: "Delibir-dy",
description: "The Delibirds are looking at you expectantly, as if they want something. Perhaps giving them an item or some money would satisfy them?",
query: "What will you give them?",
invalid_selection: "Pokémon doesn't have that kind of item.",

View File

@ -28,7 +28,6 @@ import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages.js";
import { isNullOrUndefined } from "../utils";
const outputModifierData = false;
const useMaxWeightForOutput = false;
@ -120,26 +119,6 @@ export class ModifierType {
return this;
}
/**
* Populates the tier field by performing a reverse lookup on the modifier pool specified by {@linkcode poolType} using the
* {@linkcode ModifierType}'s id.
* @param poolType the {@linkcode ModifierPoolType} to look into to derive the item's tier; defaults to {@linkcode ModifierPoolType.PLAYER}
*/
withTierFromPool(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierType {
for (const tier of Object.values(getModifierPoolForType(poolType))) {
for (const modifier of tier) {
if (this.id === modifier.modifierType.id) {
this.tier = modifier.modifierType.tier;
break;
}
}
if (this.tier) {
break;
}
}
return this;
}
/**
* Populates item tier for ModifierType instance
* Tier is a necessary field for items that appear in player shop (determines the Pokeball visual they use)
@ -2113,10 +2092,10 @@ function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], r
allowLuckUpgrades = allowLuckUpgrades ?? true;
let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades);
let r = 0;
while (existingOptions.length && ++r < retryCount && existingOptions.filter(o => o.type.name === candidate.type.name || o.type.group === candidate.type.group).length) {
candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount, 0, allowLuckUpgrades);
while (existingOptions.length && ++r < retryCount && existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length) {
candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate?.type.tier ?? tier, candidate?.upgradeCount, 0, allowLuckUpgrades);
}
return candidate;
return candidate!;
}
/**

View File

@ -826,7 +826,7 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
args[1].forEach((v, i) => {
const isHp = i === 0;
let mult = 1;
if (this.stackCount === this.getMaxHeldItemCount(null)) {
if (this.stackCount === this.getMaxHeldItemCount()) {
mult = isHp ? 1.05 : 1.1;
}
const newVal = Math.floor((v + this.stackCount * (isHp ? 1 : 2)) * mult);
@ -840,7 +840,7 @@ export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
return 1.2;
}
getMaxHeldItemCount(pokemon: Pokemon): integer {
getMaxHeldItemCount(pokemon?: Pokemon): integer {
return 50;
}
}

View File

@ -5,7 +5,7 @@ import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMov
import { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat";
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, PokemonResetNegativeStatStageModifier } from "./modifier/modifier";
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, PokemonResetNegativeStatStageModifier, PokemonIncrementingStatModifier } from "./modifier/modifier";
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
import { CommonAnim, CommonBattleAnim, initEncounterAnims, initMoveAnim, loadEncounterAnimAssets, loadMoveAnimAssets, MoveAnim } from "./data/battle-anims";
@ -19,7 +19,7 @@ import { biomeLinks, getBiomeName } from "./data/biomes";
import { ModifierTier } from "./modifier/modifier-tier";
import { CustomModifierSettings, FusePokemonModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, modifierTypes, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, regenerateModifierPoolThresholds, RememberMoveModifierType, TmModifierType } from "./modifier/modifier-type";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, ProtectedTag, SemiInvulnerableTag, TrappedTag } from "./data/battler-tags";
import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, MysteryEncounterPostSummonTag, ProtectedTag, SemiInvulnerableTag, TrappedTag } from "./data/battler-tags";
import { getPokemonNameWithAffix } from "./messages";
import { Starter } from "./ui/starter-select-ui-handler";
import { Gender } from "./data/gender";
@ -70,6 +70,7 @@ import { doTrainerExclamation, handleMysteryEncounterBattleStartEffects, handleM
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { isNullOrUndefined } from "./utils";
const { t } = i18next;
@ -865,7 +866,7 @@ export class EncounterPhase extends BattlePhase {
// Add intro visuals for mystery encounter
mysteryEncounter.initIntroVisuals(this.scene);
this.scene.field.add(mysteryEncounter.introVisuals);
this.scene.field.add(mysteryEncounter.introVisuals!);
}
let totalBst = 0;
@ -930,7 +931,7 @@ export class EncounterPhase extends BattlePhase {
const newEncounter = this.scene.getMysteryEncounter(mysteryEncounter);
battle.mysteryEncounter = newEncounter;
}
loadEnemyAssets.push(battle.mysteryEncounter.introVisuals.loadAssets().then(() => battle.mysteryEncounter.introVisuals.initSprite()));
loadEnemyAssets.push(battle.mysteryEncounter.introVisuals!.loadAssets().then(() => battle.mysteryEncounter.introVisuals!.initSprite()));
// Load Mystery Encounter Exclamation bubble and sfx
loadEnemyAssets.push(new Promise<void>(resolve => {
this.scene.loadSe("GEN8- Exclaim", "battle_anims", "GEN8- Exclaim.wav");
@ -1130,7 +1131,7 @@ export class EncounterPhase extends BattlePhase {
}
}
} else if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER) {
const introVisuals = this.scene.currentBattle.mysteryEncounter.introVisuals;
const introVisuals = this.scene.currentBattle.mysteryEncounter.introVisuals!;
introVisuals.playAnim();
if (this.scene.currentBattle.mysteryEncounter.onVisualsStart) {
@ -1148,23 +1149,27 @@ export class EncounterPhase extends BattlePhase {
if (showEncounterMessage) {
const introDialogue = this.scene.currentBattle.mysteryEncounter.dialogue.intro;
const FIRST_DIALOGUE_PROMPT_DELAY = 750;
let i = 0;
const showNextDialogue = () => {
const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue;
const dialogue = introDialogue[i];
const title = getEncounterText(this.scene, dialogue.speaker);
const text = getEncounterText(this.scene, dialogue.text);
i++;
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
};
if (!introDialogue) {
doShowEncounterOptions();
} else {
const FIRST_DIALOGUE_PROMPT_DELAY = 750;
let i = 0;
const showNextDialogue = () => {
const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue;
const dialogue = introDialogue[i];
const title = getEncounterText(this.scene, dialogue?.speaker);
const text = getEncounterText(this.scene, dialogue.text)!;
i++;
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
};
if (introDialogue.length > 0) {
showNextDialogue();
if (introDialogue.length > 0) {
showNextDialogue();
}
}
} else {
doShowEncounterOptions();
@ -1330,7 +1335,7 @@ export class NextEncounterPhase extends EncounterPhase {
}
if (lastEncounterVisuals) {
this.scene.field.remove(lastEncounterVisuals, true);
this.scene.lastMysteryEncounter.introVisuals = null;
this.scene.lastMysteryEncounter.introVisuals = undefined;
}
if (!this.tryOverrideForBattleSpec()) {
@ -4378,7 +4383,7 @@ export class VictoryPhase extends PokemonPhase {
if (participated) {
partyMember.addFriendship(2);
const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier);
if (!isNullOrUndefined(machoBraceModifier) && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this.scene)) {
if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this.scene)) {
machoBraceModifier.stackCount++;
this.scene.updateModifiers(true, true);
partyMember.updateInfo();
@ -5857,7 +5862,7 @@ export class SelectModifierPhase extends BattlePhase {
} else {
baseValue = 250;
}
const multiplier = !isNullOrUndefined(this.customModifierSettings?.rerollMultiplier) ? this.customModifierSettings.rerollMultiplier : 1;
const multiplier = !isNullOrUndefined(this.customModifierSettings?.rerollMultiplier) ? this.customModifierSettings!.rerollMultiplier! : 1;
return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER);
}

View File

@ -27,7 +27,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
*/
export class MysteryEncounterPhase extends Phase {
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300;
optionSelectSettings: OptionSelectSettings;
optionSelectSettings?: OptionSelectSettings;
/**
*
@ -72,7 +72,7 @@ export class MysteryEncounterPhase extends Phase {
if (option.onPreOptionPhase) {
this.scene.executeWithSeedOffset(async () => {
return await option.onPreOptionPhase(this.scene)
return await option.onPreOptionPhase!(this.scene)
.then((result) => {
if (isNullOrUndefined(result) || result) {
this.continueEncounter();
@ -93,7 +93,7 @@ export class MysteryEncounterPhase extends Phase {
};
const optionSelectDialogue = this.scene.currentBattle?.mysteryEncounter?.selectedOption?.dialogue;
if (optionSelectDialogue?.selected?.length > 0) {
if (optionSelectDialogue?.selected && optionSelectDialogue.selected.length > 0) {
// Handle intermediate dialogue (between player selection event and the onOptionSelect logic)
this.scene.ui.setMode(Mode.MESSAGE);
const selectedDialogue = optionSelectDialogue.selected;
@ -101,17 +101,17 @@ export class MysteryEncounterPhase extends Phase {
const showNextDialogue = () => {
const nextAction = i === selectedDialogue.length - 1 ? endDialogueAndContinueEncounter : showNextDialogue;
const dialogue = selectedDialogue[i];
let title: string = null;
const text: string = getEncounterText(this.scene, dialogue.text);
let title: string | null = null;
const text: string | null = getEncounterText(this.scene, dialogue.text);
if (dialogue.speaker) {
title = getEncounterText(this.scene, dialogue.speaker);
}
i++;
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
this.scene.ui.showDialogue(text ?? "", title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
this.scene.ui.showText(text ?? "", null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
};
@ -142,7 +142,7 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
constructor(scene: BattleScene) {
super(scene);
this.onOptionSelect = this.scene.currentBattle.mysteryEncounter.selectedOption.onOptionPhase;
this.onOptionSelect = this.scene.currentBattle.mysteryEncounter.selectedOption!.onOptionPhase;
}
start() {
@ -223,10 +223,10 @@ export class MysteryEncounterBattlePhase extends Phase {
if (encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (scene.currentBattle.double) {
return i18next.t("battle:trainerAppearedDouble", { trainerName: scene.currentBattle.trainer.getName(TrainerSlot.NONE, true) });
return i18next.t("battle:trainerAppearedDouble", { trainerName: scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) });
} else {
return i18next.t("battle:trainerAppeared", { trainerName: scene.currentBattle.trainer.getName(TrainerSlot.NONE, true) });
return i18next.t("battle:trainerAppeared", { trainerName: scene.currentBattle.trainer?.getName(TrainerSlot.NONE, true) });
}
}
@ -276,22 +276,22 @@ export class MysteryEncounterBattlePhase extends Phase {
}
};
const encounterMessages = scene.currentBattle.trainer.getEncounterMessages();
const encounterMessages = scene.currentBattle.trainer?.getEncounterMessages();
if (!encounterMessages?.length) {
if (!encounterMessages || !encounterMessages.length) {
doSummon();
} else {
const trainer = this.scene.currentBattle.trainer;
let message: string;
scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.mysteryEncounter.getSeedOffset());
message = message!; // tell TS compiler it's defined now
const showDialogueAndSummon = () => {
scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE, true), null, () => {
scene.ui.showDialogue(message, trainer?.getName(TrainerSlot.NONE, true), null, () => {
scene.charSprite.hide().then(() => scene.hideFieldOverlay(250).then(() => doSummon()));
});
};
if (scene.currentBattle.trainer.config.hasCharSprite && !scene.ui.shouldSkipDialogue(message)) {
scene.showFieldOverlay(500).then(() => scene.charSprite.showCharacter(trainer.getKey(), getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon()));
if (this.scene.currentBattle.trainer?.config.hasCharSprite && !this.scene.ui.shouldSkipDialogue(message)) {
this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer?.getKey()!, getCharVariantFromDialogue(encounterMessages[0])).then(() => showDialogueAndSummon())); // TODO: is this bang correct?
} else {
showDialogueAndSummon();
}
@ -349,6 +349,9 @@ export class MysteryEncounterBattlePhase extends Phase {
showEnemyTrainer(): void {
// Show enemy trainer
const trainer = this.scene.currentBattle.trainer;
if (!trainer) {
return;
}
trainer.alpha = 0;
trainer.x += 16;
trainer.y -= 16;
@ -416,7 +419,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
this.scene.currentBattle.mysteryEncounter.doEncounterRewards(this.scene);
} else if (this.addHealPhase) {
this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 }));
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, undefined, { fillRemaining: false, rerollMultiplier: 0 }));
}
// Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave)
}, this.scene.currentBattle.waveIndex * 1000);
@ -436,11 +439,11 @@ export class MysteryEncounterRewardsPhase extends Phase {
*/
export class PostMysteryEncounterPhase extends Phase {
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750;
onPostOptionSelect: OptionPhaseCallback;
onPostOptionSelect?: OptionPhaseCallback;
constructor(scene: BattleScene) {
super(scene);
this.onPostOptionSelect = this.scene.currentBattle.mysteryEncounter.selectedOption.onPostOptionPhase;
this.onPostOptionSelect = this.scene.currentBattle.mysteryEncounter.selectedOption?.onPostOptionPhase;
}
start() {
@ -448,7 +451,7 @@ export class PostMysteryEncounterPhase extends Phase {
if (this.onPostOptionSelect) {
this.scene.executeWithSeedOffset(async () => {
return await this.onPostOptionSelect(this.scene)
return await this.onPostOptionSelect!(this.scene)
.then((result) => {
if (isNullOrUndefined(result) || result) {
this.continueEncounter();
@ -467,13 +470,13 @@ export class PostMysteryEncounterPhase extends Phase {
};
const outroDialogue = this.scene.currentBattle?.mysteryEncounter?.dialogue?.outro;
if (outroDialogue?.length > 0) {
if (outroDialogue && outroDialogue.length > 0) {
let i = 0;
const showNextDialogue = () => {
const nextAction = i === outroDialogue.length - 1 ? endPhase : showNextDialogue;
const dialogue = outroDialogue[i];
let title: string = null;
const text: string = getEncounterText(this.scene, dialogue.text);
let title: string | null = null;
const text: string | null = getEncounterText(this.scene, dialogue.text);
if (dialogue.speaker) {
title = getEncounterText(this.scene, dialogue.speaker);
}
@ -481,9 +484,9 @@ export class PostMysteryEncounterPhase extends Phase {
i++;
this.scene.ui.setMode(Mode.MESSAGE);
if (title) {
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
this.scene.ui.showDialogue(text ?? "", title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
} else {
this.scene.ui.showText(text, null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
this.scene.ui.showText(text ?? "", null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
}
};

View File

@ -10,6 +10,7 @@ import { expect, vi } from "vitest";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import PartyUiHandler from "#app/ui/party-ui-handler";
import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
import { isNullOrUndefined } from "#app/utils";
/**
* Runs a MysteryEncounter to either the start of a battle, or to the MysteryEncounterRewardsPhase, depending on the option selected
@ -18,7 +19,7 @@ import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
* @param secondaryOptionSelect -
* @param isBattle - if selecting option should lead to battle, set to true
*/
export async function runMysteryEncounterToEnd(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo?: number } = null, isBattle: boolean = false) {
export async function runMysteryEncounterToEnd(game: GameManager, optionNo: number, secondaryOptionSelect?: { pokemonNo: number, optionNo?: number }, isBattle: boolean = false) {
vi.spyOn(EncounterPhaseUtils, "selectPokemonForOption");
await runSelectMysteryEncounterOption(game, optionNo, secondaryOptionSelect);
@ -65,7 +66,7 @@ export async function runMysteryEncounterToEnd(game: GameManager, optionNo: numb
}
}
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo?: number } = null) {
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, secondaryOptionSelect?: { pokemonNo: number, optionNo?: number }) {
// Handle any eventual queued messages (e.g. weather phase, etc.)
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
@ -107,8 +108,8 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
uiHandler.processInput(Button.ACTION);
if (!isNaN(secondaryOptionSelect?.pokemonNo)) {
await handleSecondaryOptionSelect(game, secondaryOptionSelect.pokemonNo, secondaryOptionSelect.optionNo);
if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) {
await handleSecondaryOptionSelect(game, secondaryOptionSelect!.pokemonNo, secondaryOptionSelect!.optionNo);
}
}
@ -128,14 +129,14 @@ async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number,
partyUiHandler.processInput(Button.ACTION);
// If there is a second choice to make after selecting a Pokemon
if (!isNaN(optionNo)) {
if (!isNullOrUndefined(optionNo)) {
// Wait for Summary menu to close and second options to spawn
const secondOptionUiHandler = game.scene.ui.handlers[Mode.OPTION_SELECT] as OptionSelectUiHandler;
vi.spyOn(secondOptionUiHandler, "show");
await vi.waitFor(() => expect(secondOptionUiHandler.show).toHaveBeenCalled());
// Navigate down to the correct option
for (let i = 1; i < optionNo; i++) {
for (let i = 1; i < optionNo!; i++) {
secondOptionUiHandler.processInput(Button.DOWN);
}

View File

@ -35,7 +35,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
@ -59,11 +59,11 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(ATrainersTestEncounter.encounterTier).toBe(MysteryEncounterTier.ROGUE);
expect(ATrainersTestEncounter.dialogue).toBeDefined();
expect(ATrainersTestEncounter.dialogue.intro).toBeDefined();
expect(ATrainersTestEncounter.dialogue.intro[0].speaker).toBeDefined();
expect(ATrainersTestEncounter.dialogue.intro[0].text).toBeDefined();
expect(ATrainersTestEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(ATrainersTestEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(ATrainersTestEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(ATrainersTestEncounter.dialogue.intro?.[0].speaker).toBeDefined();
expect(ATrainersTestEncounter.dialogue.intro?.[0].text).toBeDefined();
expect(ATrainersTestEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(ATrainersTestEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(ATrainersTestEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(ATrainersTestEncounter.options.length).toBe(2);
});
@ -92,14 +92,14 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(ATrainersTestEncounter.onInit).toBeDefined();
ATrainersTestEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(ATrainersTestEncounter.dialogueTokens?.statTrainerName).toBeDefined();
expect(ATrainersTestEncounter.misc.trainerType).toBeDefined();
expect(ATrainersTestEncounter.misc.trainerNameKey).toBeDefined();
expect(ATrainersTestEncounter.misc.trainerEggDescription).toBeDefined();
expect(ATrainersTestEncounter.dialogue.intro).toBeDefined();
expect(ATrainersTestEncounter.options[1].dialogue.selected).toBeDefined();
expect(ATrainersTestEncounter.options[1].dialogue?.selected).toBeDefined();
expect(onInitResult).toBe(true);
});
@ -108,19 +108,19 @@ describe("A Trainer's Test - Mystery Encounter", () => {
const option = ATrainersTestEncounter.options[0];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue.buttonLabel).toStrictEqual(`${namespace}.option.1.label`);
expect(option.dialogue.buttonTooltip).toStrictEqual(`${namespace}.option.1.tooltip`);
expect(option.dialogue!.buttonLabel).toStrictEqual(`${namespace}.option.1.label`);
expect(option.dialogue!.buttonTooltip).toStrictEqual(`${namespace}.option.1.tooltip`);
});
it("Should start battle against the trainer", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.A_TRAINERS_TEST, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(scene.currentBattle.trainer).toBeDefined();
expect(["buck", "cheryl", "marley", "mira", "riley"].includes(scene.currentBattle.trainer.config.name.toLowerCase())).toBeTruthy();
expect(["buck", "cheryl", "marley", "mira", "riley"].includes(scene.currentBattle.trainer!.config.name.toLowerCase())).toBeTruthy();
expect(enemyField[0]).toBeDefined();
});
@ -131,10 +131,10 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(eggsBefore).toBeDefined();
const eggsBeforeLength = eggsBefore.length;
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const eggsAfter = scene.gameData.eggs;
expect(eggsAfter).toBeDefined();
@ -151,7 +151,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
return {
totalDuration: 1,
destroy: () => null
} as Phaser.Sound.NoAudioSound;
} as any;
});
});
@ -159,8 +159,8 @@ describe("A Trainer's Test - Mystery Encounter", () => {
const option = ATrainersTestEncounter.options[1];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue.buttonLabel).toStrictEqual(`${namespace}.option.2.label`);
expect(option.dialogue.buttonTooltip).toStrictEqual(`${namespace}.option.2.tooltip`);
expect(option.dialogue?.buttonLabel).toStrictEqual(`${namespace}.option.2.label`);
expect(option.dialogue?.buttonTooltip).toStrictEqual(`${namespace}.option.2.tooltip`);
});
it("Should fully heal the party", async () => {
@ -182,7 +182,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const eggsAfter = scene.gameData.eggs;
expect(eggsAfter).toBeDefined();

View File

@ -35,7 +35,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -58,9 +58,9 @@ describe("Absolute Avarice - Mystery Encounter", () => {
expect(AbsoluteAvariceEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
expect(AbsoluteAvariceEncounter.dialogue).toBeDefined();
expect(AbsoluteAvariceEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(AbsoluteAvariceEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(AbsoluteAvariceEncounter.options.length).toBe(3);
});
@ -98,7 +98,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
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.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
await game.runToMysteryEncounter();
@ -106,7 +106,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
});
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.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
@ -134,13 +134,13 @@ describe("Absolute Avarice - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(Species.GREEDENT);
const moveset = enemyField[0].moveset.map(m => m.moveId);
const moveset = enemyField[0].moveset.map(m => m?.moveId);
expect(moveset?.length).toBe(4);
expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF]);
@ -151,10 +151,10 @@ describe("Absolute Avarice - Mystery Encounter", () => {
it("should give reviver seed to each pokemon after battle", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
for (const partyPokemon of scene.getParty()) {
const pokemonId = partyPokemon.id;
@ -162,7 +162,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
&& (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[];
const revSeed = pokemonItems.find(i => i.type.name === "Reviver Seed");
expect(revSeed).toBeDefined;
expect(revSeed.stackCount).toBe(1);
expect(revSeed?.stackCount).toBe(1);
}
});
});
@ -184,7 +184,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
});
it("Should return 3 (2/5ths floored) berries if 8 were stolen", async () => {
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 3, type: BerryType.APICOT}]);
game.override.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 3, type: BerryType.APICOT}]);
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
@ -200,7 +200,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
});
it("Should return 2 (2/5ths floored) berries if 7 were stolen", async () => {
game.override.starterHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 2, type: BerryType.APICOT}]);
game.override.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}, {name: "BERRY", count: 2, type: BerryType.APICOT}]);
await game.runToMysteryEncounter(MysteryEncounterType.ABSOLUTE_AVARICE, defaultParty);
@ -251,7 +251,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
expect(partyCountBefore + 1).toBe(partyCountAfter);
const greedent = scene.getParty()[scene.getParty().length - 1];
expect(greedent.species.speciesId).toBe(Species.GREEDENT);
const moveset = greedent.moveset.map(m => m.moveId);
const moveset = greedent.moveset.map(m => m?.moveId);
expect(moveset?.length).toBe(4);
expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF]);
});

View File

@ -38,7 +38,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
@ -65,9 +65,9 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
{ text: `${namespace}.intro` },
{ speaker: `${namespace}.speaker`, text: `${namespace}.intro_dialogue` }
]);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(AnOfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(AnOfferYouCantRefuseEncounter.options.length).toBe(3);
});
@ -104,7 +104,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
expect(AnOfferYouCantRefuseEncounter.onInit).toBeDefined();
AnOfferYouCantRefuseEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined();
expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined();
@ -153,7 +153,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
const itemModifier = scene.findModifier(m => m instanceof ShinyRateBoosterModifier) as ShinyRateBoosterModifier;
expect(itemModifier).toBeDefined();
expect(itemModifier.stackCount).toBe(1);
expect(itemModifier?.stackCount).toBe(1);
});
it("Should remove the Pokemon from the party", async () => {
@ -199,7 +199,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
it("should award EXP to a pokemon with an ability in EXTORTION_ABILITIES", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
const party = scene.getParty();
const gyarados = party.find((pkm) => pkm.species.speciesId === Species.GYARADOS);
const gyarados = party.find((pkm) => pkm.species.speciesId === Species.GYARADOS)!;
const expBefore = gyarados.exp;
await runMysteryEncounterToEnd(game, 2);
@ -210,7 +210,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
it("should award EXP to a pokemon with a move in EXTORTION_MOVES", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, [Species.ABRA]);
const party = scene.getParty();
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA)!;
abra.moveset = [new PokemonMove(Moves.BEAT_UP)];
const expBefore = abra.exp;

View File

@ -37,7 +37,7 @@ describe("Berries Abound - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -59,9 +59,9 @@ describe("Berries Abound - Mystery Encounter", () => {
expect(BerriesAboundEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(BerriesAboundEncounter.dialogue).toBeDefined();
expect(BerriesAboundEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(BerriesAboundEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(BerriesAboundEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(BerriesAboundEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(BerriesAboundEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(BerriesAboundEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(BerriesAboundEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(BerriesAboundEncounter.options.length).toBe(3);
});
@ -90,12 +90,12 @@ describe("Berries Abound - Mystery Encounter", () => {
expect(BerriesAboundEncounter.onInit).toBeDefined();
BerriesAboundEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
const config = BerriesAboundEncounter.enemyPartyConfigs[0];
expect(config).toBeDefined();
expect(config.levelAdditiveMultiplier).toBe(1);
expect(config.pokemonConfigs[0].isBoss).toBe(true);
expect(config.pokemonConfigs?.[0].isBoss).toBe(true);
expect(onInitResult).toBe(true);
});
@ -119,12 +119,12 @@ describe("Berries Abound - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs[0].species.speciesId;
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
});
@ -133,11 +133,12 @@ describe("Berries Abound - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const numBerries = game.scene.currentBattle.mysteryEncounter.misc.numBerries;
scene.modifiers = [];
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const berriesAfter = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
const berriesAfterCount = berriesAfter.reduce((a, b) => a + b.stackCount, 0);
@ -147,10 +148,10 @@ describe("Berries Abound - Mystery Encounter", () => {
it("should spawn a shop with 5 berries", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -178,14 +179,14 @@ describe("Berries Abound - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs[0].species.speciesId;
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
// Setting enemy's level arbitrarily high to outspeed
config.pokemonConfigs[0].dataSource.level = 1000;
config.pokemonConfigs![0].dataSource!.level = 1000;
await runMysteryEncounterToEnd(game, 2, null, true);
await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
@ -207,7 +208,7 @@ describe("Berries Abound - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -50,7 +50,7 @@ describe("Clowning Around - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -78,9 +78,9 @@ describe("Clowning Around - Mystery Encounter", () => {
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.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);
});
@ -111,23 +111,23 @@ describe("Clowning Around - Mystery Encounter", () => {
expect(ClowningAroundEncounter.onInit).toBeDefined();
ClowningAroundEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(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({
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({
expect(config.pokemonConfigs?.[1]).toEqual({
species: getPokemonSpecies(Species.BLACEPHALON),
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(config.pokemonConfigs?.[1].mysteryEncounterData?.types.length).toBe(2);
expect([
Abilities.STURDY,
Abilities.PICKUP,
@ -144,8 +144,8 @@ describe("Clowning Around - Mystery Encounter", () => {
Abilities.MAGICIAN,
Abilities.SHEER_FORCE,
Abilities.PRANKSTER
]).toContain(config.pokemonConfigs[1].mysteryEncounterData.ability);
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].mysteryEncounterData.ability);
]).toContain(config.pokemonConfigs?.[1].mysteryEncounterData?.ability);
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].mysteryEncounterData?.ability);
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
expect(onInitResult).toBe(true);
@ -172,10 +172,10 @@ describe("Clowning Around - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
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)]);
@ -191,10 +191,10 @@ describe("Clowning Around - Mystery Encounter", () => {
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 runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
const abilityToTrain = scene.currentBattle.mysteryEncounter.misc.ability;
@ -209,7 +209,7 @@ describe("Clowning Around - Mystery Encounter", () => {
vi.spyOn(partyUiHandler, "show");
game.endPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
expect(scene.getCurrentPhase().constructor.name).toBe(PostMysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(PostMysteryEncounterPhase.name);
// Wait for Yes/No confirmation to appear
await vi.waitFor(() => expect(optionSelectUiHandler.show).toHaveBeenCalled());
@ -224,7 +224,7 @@ describe("Clowning Around - Mystery Encounter", () => {
await game.phaseInterceptor.to(NewBattlePhase, false);
const leadPokemon = scene.getParty()[0];
expect(leadPokemon.mysteryEncounterData.ability).toBe(abilityToTrain);
expect(leadPokemon.mysteryEncounterData?.ability).toBe(abilityToTrain);
});
});
@ -255,6 +255,9 @@ describe("Clowning Around - Mystery Encounter", () => {
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);
// Set some moves on party for attack type booster generation
scene.getParty()[0].moveset = [new PokemonMove(Moves.TACKLE), new PokemonMove(Moves.THIEF)];
// 2 Sitrus Berries on lead
scene.modifiers = [];
let itemType = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType;
@ -294,8 +297,8 @@ describe("Clowning Around - Mystery Encounter", () => {
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);
});
expect(secondItemsAfter[0]?.stackCount).toBe(5);
}, 2000000);
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
@ -342,9 +345,9 @@ describe("Clowning Around - Mystery Encounter", () => {
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;
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]);

View File

@ -37,7 +37,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -60,9 +60,9 @@ describe("Dancing Lessons - Mystery Encounter", () => {
expect(DancingLessonsEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
expect(DancingLessonsEncounter.dialogue).toBeDefined();
expect(DancingLessonsEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(DancingLessonsEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(DancingLessonsEncounter.options.length).toBe(3);
});
@ -110,14 +110,14 @@ describe("Dancing Lessons - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(Species.ORICORIO);
expect(enemyField[0].summonData.battleStats).toEqual([1, 1, 1, 1, 1, 0, 0]);
const moveset = enemyField[0].moveset.map(m => m.moveId);
const moveset = enemyField[0].moveset.map(m => m?.moveId);
expect(moveset.some(m => m === Moves.REVELATION_DANCE)).toBeTruthy();
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
@ -127,10 +127,10 @@ describe("Dancing Lessons - Mystery Encounter", () => {
it("should have a Baton in the rewards after battle", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DANCING_LESSONS, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -205,7 +205,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
expect(partyCountBefore + 1).toBe(partyCountAfter);
const oricorio = scene.getParty()[scene.getParty().length - 1];
expect(oricorio.species.speciesId).toBe(Species.ORICORIO);
const moveset = oricorio.moveset.map(m => m.moveId);
const moveset = oricorio.moveset.map(m => m?.moveId);
expect(moveset?.some(m => m === Moves.REVELATION_DANCE)).toBeTruthy();
expect(moveset?.some(m => m === Moves.DRAGON_DANCE)).toBeTruthy();
});
@ -217,7 +217,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -226,7 +226,7 @@ describe("Dancing Lessons - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3);
const partyCountAfter = scene.getParty().length;
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -37,7 +37,7 @@ describe("Delibird-y - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -60,9 +60,9 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(DelibirdyEncounter.dialogue).toBeDefined();
expect(DelibirdyEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(DelibirdyEncounter.dialogue.outro).toStrictEqual([{ text: `${namespace}.outro` }]);
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(DelibirdyEncounter.options.length).toBe(3);
});
@ -128,7 +128,7 @@ describe("Delibird-y - Mystery Encounter", () => {
const itemModifier = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier;
expect(itemModifier).toBeDefined();
expect(itemModifier.stackCount).toBe(1);
expect(itemModifier?.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => {
@ -148,9 +148,9 @@ describe("Delibird-y - Mystery Encounter", () => {
const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier);
expect(abilityCharmAfter).toBeDefined();
expect(abilityCharmAfter.stackCount).toBe(4);
expect(abilityCharmAfter?.stackCount).toBe(4);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
expect(shellBellAfter?.stackCount).toBe(1);
});
it("should be disabled if player does not have enough money", async () => {
@ -159,7 +159,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -167,7 +167,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -217,9 +217,9 @@ describe("Delibird-y - Mystery Encounter", () => {
const sitrusAfter = scene.findModifier(m => m instanceof BerryModifier);
const candyJarAfter = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier);
expect(sitrusAfter.stackCount).toBe(1);
expect(sitrusAfter?.stackCount).toBe(1);
expect(candyJarAfter).toBeDefined();
expect(candyJarAfter.stackCount).toBe(1);
expect(candyJarAfter?.stackCount).toBe(1);
});
it("Should remove Reviver Seed and give the player a Healing Charm", async () => {
@ -240,7 +240,7 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(reviverSeedAfter).toBeUndefined();
expect(healingCharmAfter).toBeDefined();
expect(healingCharmAfter.stackCount).toBe(1);
expect(healingCharmAfter?.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Candy Jars", async () => {
@ -265,11 +265,11 @@ describe("Delibird-y - Mystery Encounter", () => {
const candyJarAfter = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier);
const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier);
expect(sitrusAfter.stackCount).toBe(1);
expect(sitrusAfter?.stackCount).toBe(1);
expect(candyJarAfter).toBeDefined();
expect(candyJarAfter.stackCount).toBe(99);
expect(candyJarAfter?.stackCount).toBe(99);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
expect(shellBellAfter?.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Healing Charms", async () => {
@ -296,9 +296,9 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(reviverSeedAfter).toBeUndefined();
expect(healingCharmAfter).toBeDefined();
expect(healingCharmAfter.stackCount).toBe(5);
expect(healingCharmAfter?.stackCount).toBe(5);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
expect(shellBellAfter?.stackCount).toBe(1);
});
it("should be disabled if player does not have any proper items", async () => {
@ -314,7 +314,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -322,7 +322,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -379,9 +379,9 @@ describe("Delibird-y - Mystery Encounter", () => {
const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier);
const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier);
expect(soulDewAfter.stackCount).toBe(1);
expect(soulDewAfter?.stackCount).toBe(1);
expect(berryPouchAfter).toBeDefined();
expect(berryPouchAfter.stackCount).toBe(1);
expect(berryPouchAfter?.stackCount).toBe(1);
});
it("Should remove held item and give the player a Berry Pouch", async () => {
@ -402,7 +402,7 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(soulDewAfter).toBeUndefined();
expect(berryPouchAfter).toBeDefined();
expect(berryPouchAfter.stackCount).toBe(1);
expect(berryPouchAfter?.stackCount).toBe(1);
});
it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => {
@ -429,9 +429,9 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(soulDewAfter).toBeUndefined();
expect(berryPouchAfter).toBeDefined();
expect(berryPouchAfter.stackCount).toBe(3);
expect(berryPouchAfter?.stackCount).toBe(3);
expect(shellBellAfter).toBeDefined();
expect(shellBellAfter.stackCount).toBe(1);
expect(shellBellAfter?.stackCount).toBe(1);
});
it("should be disabled if player does not have any proper items", async () => {
@ -447,7 +447,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -455,7 +455,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -35,7 +35,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
@ -65,9 +65,9 @@ describe("Department Store Sale - Mystery Encounter", () => {
text: `${namespace}.intro_dialogue`,
}
]);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(DepartmentStoreSaleEncounter.options.length).toBe(4);
});
@ -109,7 +109,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only TMs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -144,7 +144,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Vitamins", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -180,7 +180,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only X Items", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -216,7 +216,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Pokeballs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runMysteryEncounterToEnd(game, 4);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -42,7 +42,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -65,9 +65,9 @@ describe("Fiery Fallout - Mystery Encounter", () => {
expect(FieryFalloutEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(FieryFalloutEncounter.dialogue).toBeDefined();
expect(FieryFalloutEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(FieryFalloutEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(FieryFalloutEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(FieryFalloutEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(FieryFalloutEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(FieryFalloutEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(FieryFalloutEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(FieryFalloutEncounter.options.length).toBe(3);
});
@ -106,7 +106,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
expect(FieryFalloutEncounter.onInit).toBeDefined();
FieryFalloutEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(FieryFalloutEncounter.enemyPartyConfigs).toEqual([
{
@ -152,10 +152,10 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(2);
expect(enemyField[0].species.speciesId).toBe(Species.VOLCARONA);
expect(enemyField[1].species.speciesId).toBe(Species.VOLCARONA);
@ -169,10 +169,10 @@ describe("Fiery Fallout - Mystery Encounter", () => {
it("should give charcoal to lead pokemon", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const leadPokemonId = scene.getParty()?.[0].id;
const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
@ -202,9 +202,9 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
const party = scene.getParty();
const lapras = party.find((pkm) => pkm.species.speciesId === Species.LAPRAS);
const lapras = party.find((pkm) => pkm.species.speciesId === Species.LAPRAS)!;
lapras.status = new Status(StatusEffect.POISON);
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA)!;
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
await runMysteryEncounterToEnd(game, 2);
@ -251,7 +251,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 3);
// await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const leadPokemonId = scene.getParty()?.[0].id;
const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier
@ -274,12 +274,12 @@ describe("Fiery Fallout - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const continueEncounterSpy = vi.spyOn((encounterPhase as MysteryEncounterPhase), "continueEncounter");
await runSelectMysteryEncounterOption(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(continueEncounterSpy).not.toHaveBeenCalled();
});
});

View File

@ -38,7 +38,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -60,9 +60,9 @@ describe("Fight or Flight - Mystery Encounter", () => {
expect(FightOrFlightEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(FightOrFlightEncounter.dialogue).toBeDefined();
expect(FightOrFlightEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(FightOrFlightEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(FightOrFlightEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(FightOrFlightEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(FightOrFlightEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(FightOrFlightEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(FightOrFlightEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(FightOrFlightEncounter.options.length).toBe(3);
});
@ -91,12 +91,12 @@ describe("Fight or Flight - Mystery Encounter", () => {
expect(FightOrFlightEncounter.onInit).toBeDefined();
FightOrFlightEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
const config = FightOrFlightEncounter.enemyPartyConfigs[0];
expect(config).toBeDefined();
expect(config.levelAdditiveMultiplier).toBe(1);
expect(config.pokemonConfigs[0].isBoss).toBe(true);
expect(config.pokemonConfigs?.[0].isBoss).toBe(true);
expect(onInitResult).toBe(true);
});
@ -120,12 +120,12 @@ describe("Fight or Flight - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs[0].species.speciesId;
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
});
@ -135,10 +135,10 @@ describe("Fight or Flight - Mystery Encounter", () => {
const item = game.scene.currentBattle.mysteryEncounter.misc;
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -171,7 +171,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -179,7 +179,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -196,7 +196,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -36,7 +36,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -59,9 +59,9 @@ describe("Lost at Sea - Mystery Encounter", () => {
expect(LostAtSeaEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(LostAtSeaEncounter.dialogue).toBeDefined();
expect(LostAtSeaEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(LostAtSeaEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(LostAtSeaEncounter.options.length).toBe(3);
});
@ -98,7 +98,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
expect(LostAtSeaEncounter.onInit).toBeDefined();
LostAtSeaEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25");
expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(Moves[Moves.SURF]);
@ -129,12 +129,12 @@ describe("Lost at Sea - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
const party = game.scene.getParty();
const blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
const expBefore = blastoise.exp;
const blastoise = party.find((pkm) => pkm.species.speciesId === Species.BLASTOISE);
const expBefore = blastoise!.exp;
await runMysteryEncounterToEnd(game, 2);
await runMysteryEncounterToEnd(game, 1);
expect(blastoise.exp).toBe(expBefore + Math.floor(laprasSpecies.baseExp * defaultWave / 5 + 1));
expect(blastoise?.exp).toBe(expBefore + Math.floor(laprasSpecies.baseExp * defaultWave / 5 + 1));
});
it("should leave encounter without battle", async () => {
@ -152,7 +152,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -160,7 +160,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -194,11 +194,11 @@ describe("Lost at Sea - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
const party = game.scene.getParty();
const pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
const expBefore = pidgeot.exp;
const expBefore = pidgeot!.exp;
await runMysteryEncounterToEnd(game, 2);
expect(pidgeot.exp).toBe(expBefore + Math.floor(laprasBaseExp * defaultWave / 5 + 1));
expect(pidgeot!.exp).toBe(expBefore + Math.floor(laprasBaseExp * defaultWave / 5 + 1));
});
it("should leave encounter without battle", async () => {
@ -216,7 +216,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -224,7 +224,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -254,7 +254,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
const party = game.scene.getParty();
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA)!;
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
await runMysteryEncounterToEnd(game, 3);

View File

@ -40,7 +40,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]],
@ -64,9 +64,9 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
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.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);
});
@ -105,7 +105,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
expect(encounter.onInit).toBeDefined();
encounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(encounter.enemyPartyConfigs).toBeDefined();
expect(encounter.enemyPartyConfigs.length).toBe(3);
@ -125,11 +125,11 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
female: expect.any(Boolean),
}
]);
expect(encounter.enemyPartyConfigs[1].trainerConfig.partyTemplates[0]).toEqual(new TrainerPartyCompoundTemplate(
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(
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))
@ -157,19 +157,19 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
it("should start battle against the trainer", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
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 runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -199,19 +199,19 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
it("should start battle against the trainer", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 2, null, true);
await runMysteryEncounterToEnd(game, 2, undefined, true);
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
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 runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -242,19 +242,19 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
it("should start battle against the trainer", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, defaultParty);
await runMysteryEncounterToEnd(game, 3, null, true);
await runMysteryEncounterToEnd(game, 3, undefined, true);
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
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 runMysteryEncounterToEnd(game, 3, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -36,7 +36,7 @@ describe("Part-Timer - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
@ -66,9 +66,9 @@ describe("Part-Timer - Mystery Encounter", () => {
text: `${namespace}.intro_dialogue`,
}
]);
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(PartTimerEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(PartTimerEncounter.options.length).toBe(3);
});
@ -127,7 +127,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Expect PP of mon's moves to have been reduced to 2
const moves = scene.getParty()[0].moveset;
for (const move of moves) {
expect(move.getMovePp() - move.ppUsed).toBe(2);
expect((move?.getMovePp() ?? 0) - (move?.ppUsed ?? 0)).toBe(2);
}
});
@ -147,7 +147,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Expect PP of mon's moves to have been reduced to 2
const moves = scene.getParty()[1].moveset;
for (const move of moves) {
expect(move.getMovePp() - move.ppUsed).toBe(2);
expect((move?.getMovePp() ?? 0) - (move?.ppUsed ?? 0)).toBe(2);
}
});
@ -192,7 +192,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Expect PP of mon's moves to have been reduced to 2
const moves = scene.getParty()[2].moveset;
for (const move of moves) {
expect(move.getMovePp() - move.ppUsed).toBe(2);
expect((move?.getMovePp() ?? 0) - (move?.ppUsed ?? 0)).toBe(2);
}
});
@ -212,7 +212,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Expect PP of mon's moves to have been reduced to 2
const moves = scene.getParty()[3].moveset;
for (const move of moves) {
expect(move.getMovePp() - move.ppUsed).toBe(2);
expect((move?.getMovePp() ?? 0) - (move?.ppUsed ?? 0)).toBe(2);
}
});
@ -252,7 +252,7 @@ describe("Part-Timer - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -260,7 +260,7 @@ describe("Part-Timer - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
@ -279,7 +279,7 @@ describe("Part-Timer - Mystery Encounter", () => {
// Expect PP of mon's moves to have been reduced to 2
const moves = scene.getParty()[0].moveset;
for (const move of moves) {
expect(move.getMovePp() - move.ppUsed).toBe(2);
expect((move?.getMovePp() ?? 0) - (move?.ppUsed ?? 0)).toBe(2);
}
});

View File

@ -35,7 +35,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
@ -62,9 +62,9 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
{ text: `${namespace}.intro` },
{ speaker: `${namespace}.speaker`, text: `${namespace}.intro_dialogue` }
]);
expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(ThePokemonSalesmanEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(ThePokemonSalesmanEncounter.options.length).toBe(2);
});
@ -101,7 +101,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
expect(ThePokemonSalesmanEncounter.onInit).toBeDefined();
ThePokemonSalesmanEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(ThePokemonSalesmanEncounter.dialogueTokens?.purchasePokemon).toBeDefined();
expect(ThePokemonSalesmanEncounter.dialogueTokens?.price).toBeDefined();
@ -167,7 +167,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
@ -175,7 +175,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
await runSelectMysteryEncounterOption(game, 1);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();

View File

@ -11,7 +11,6 @@ import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } f
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import BattleScene from "#app/battle-scene";
import * as Modifiers from "#app/modifier/modifier";
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
import { Nature } from "#app/data/nature";
import { BerryType } from "#enums/berry-type";
@ -19,7 +18,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { PokemonMove } from "#app/field/pokemon";
import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { PokemonBaseStatTotalModifier } from "#app/modifier/modifier";
import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modifier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
@ -45,7 +44,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -68,9 +67,9 @@ describe("The Strong Stuff - Mystery Encounter", () => {
expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(TheStrongStuffEncounter.dialogue).toBeDefined();
expect(TheStrongStuffEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(TheStrongStuffEncounter.options.length).toBe(2);
});
@ -109,7 +108,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
expect(TheStrongStuffEncounter.onInit).toBeDefined();
TheStrongStuffEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(TheStrongStuffEncounter.enemyPartyConfigs).toEqual([
{
@ -198,19 +197,19 @@ describe("The Strong Stuff - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runMysteryEncounterToEnd(game, 2, null, true);
await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(Species.SHUCKLE);
expect(enemyField[0].summonData.battleStats).toEqual([0, 2, 0, 2, 0, 0, 0]);
const shuckleItems = scene.getModifiers(Modifiers.BerryModifier, false);
const shuckleItems = enemyField[0].getHeldItems();
expect(shuckleItems.length).toBe(4);
expect(shuckleItems.find(m => m.berryType === BerryType.SITRUS)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m.berryType === BerryType.GANLON)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m.berryType === BerryType.APICOT)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m.berryType === BerryType.LUM)?.stackCount).toBe(2);
expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.SITRUS)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.GANLON)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.APICOT)?.stackCount).toBe(1);
expect(shuckleItems.find(m => m instanceof BerryModifier && m.berryType === BerryType.LUM)?.stackCount).toBe(2);
expect(enemyField[0].moveset).toEqual([new PokemonMove(Moves.INFESTATION), new PokemonMove(Moves.SALT_CURE), new PokemonMove(Moves.GASTRO_ACID), new PokemonMove(Moves.HEAL_ORDER)]);
// Should have used moves pre-battle
@ -222,10 +221,10 @@ describe("The Strong Stuff - Mystery Encounter", () => {
it("should have Soul Dew in rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runMysteryEncounterToEnd(game, 2, null, true);
await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -43,7 +43,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]],
@ -73,9 +73,9 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
text: `${namespace}.intro_dialogue`,
}
]);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(TheWinstrateChallengeEncounter.options.length).toBe(2);
});
@ -114,7 +114,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
expect(encounter.onInit).toBeDefined();
encounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(encounter.enemyPartyConfigs).toBeDefined();
expect(encounter.enemyPartyConfigs.length).toBe(5);
@ -273,42 +273,42 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
it("should battle all 5 trainers for a Macho Brace reward", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
await runMysteryEncounterToEnd(game, 1, undefined, true);
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICTOR);
expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VICTOR);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(4);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICTORIA);
expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VICTORIA);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(3);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VIVI);
expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VIVI);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(2);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICKY);
expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VICKY);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(1);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VITO);
expect(scene.currentBattle.trainer!.config.trainerType).toBe(TrainerType.VITO);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(0);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
// Should have Macho Brace in the rewards
await skipBattleToNextBattle(game, true);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
@ -348,7 +348,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
it("should have a Rarer Candy in the rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -41,7 +41,7 @@ describe("Trash to Treasure - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([
@ -63,9 +63,9 @@ describe("Trash to Treasure - Mystery Encounter", () => {
expect(TrashToTreasureEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA);
expect(TrashToTreasureEncounter.dialogue).toBeDefined();
expect(TrashToTreasureEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(TrashToTreasureEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(TrashToTreasureEncounter.options.length).toBe(2);
});
@ -96,7 +96,7 @@ describe("Trash to Treasure - Mystery Encounter", () => {
expect(TrashToTreasureEncounter.onInit).toBeDefined();
TrashToTreasureEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(TrashToTreasureEncounter.enemyPartyConfigs).toEqual([
{
@ -138,19 +138,19 @@ describe("Trash to Treasure - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const leftovers = scene.findModifier(m => m instanceof TurnHealModifier) as TurnHealModifier;
expect(leftovers).toBeDefined();
expect(leftovers.stackCount).toBe(2);
expect(leftovers?.stackCount).toBe(2);
const shellBell = scene.findModifier(m => m instanceof HitHealModifier) as HitHealModifier;
expect(shellBell).toBeDefined();
expect(shellBell.stackCount).toBe(2);
expect(shellBell?.stackCount).toBe(2);
const blackSludge = scene.findModifier(m => m instanceof RemoveHealShopModifier) as RemoveHealShopModifier;
expect(blackSludge).toBeDefined();
expect(blackSludge.stackCount).toBe(1);
expect(blackSludge?.stackCount).toBe(1);
});
it("should leave encounter without battle", async () => {
@ -183,10 +183,10 @@ describe("Trash to Treasure - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 2, null, true);
await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(Species.GARBODOR);
expect(enemyField[0].moveset).toEqual([new PokemonMove(Moves.PAYBACK), new PokemonMove(Moves.GUNK_SHOT), new PokemonMove(Moves.STOMPING_TANTRUM), new PokemonMove(Moves.DRAIN_PUNCH)]);
@ -200,10 +200,10 @@ describe("Trash to Treasure - Mystery Encounter", () => {
it("should have 2 Rogue, 1 Ultra, 1 Great in rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.TRASH_TO_TREASURE, defaultParty);
await runMysteryEncounterToEnd(game, 2, null, true);
await runMysteryEncounterToEnd(game, 2, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -36,7 +36,7 @@ describe("Weird Dream - Mystery Encounter", () => {
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
game.override.disableTrainerWaves();
vi.spyOn(EncounterTransformationSequence, "doPokemonTransformationSequence").mockImplementation(() => new Promise<void>(resolve => resolve()));
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
@ -67,9 +67,9 @@ describe("Weird Dream - Mystery Encounter", () => {
text: `${namespace}.intro_dialogue`,
},
]);
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(WeirdDreamEncounter.options.length).toBe(2);
});
@ -99,7 +99,7 @@ describe("Weird Dream - Mystery Encounter", () => {
expect(WeirdDreamEncounter.onInit).toBeDefined();
WeirdDreamEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
const onInitResult = onInit!(scene);
expect(loadBgmSpy).toHaveBeenCalled();
expect(onInitResult).toBe(true);
@ -129,7 +129,7 @@ describe("Weird Dream - Mystery Encounter", () => {
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
const pokemonAfter = scene.getParty();
const bstsAfter = pokemonAfter.map(pokemon => pokemon.getSpeciesForm().getBaseStatTotal());
@ -138,7 +138,7 @@ describe("Weird Dream - Mystery Encounter", () => {
for (let i = 0; i < pokemonAfter.length; i++) {
const newPokemon = pokemonAfter[i];
expect(newPokemon.getSpeciesForm().speciesId).not.toBe(pokemonPrior[i].getSpeciesForm().speciesId);
expect(newPokemon.mysteryEncounterData.types.length).toBe(2);
expect(newPokemon.mysteryEncounterData?.types.length).toBe(2);
}
const plus90To110 = bstDiff.filter(bst => bst > 80);
@ -152,7 +152,7 @@ describe("Weird Dream - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
await runMysteryEncounterToEnd(game, 1);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);

View File

@ -230,7 +230,7 @@ describe("Mystery Encounter Utils", () => {
it("gets species of specified types", () => {
// Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian
const result = getRandomSpeciesByStarterTier(9, null, [Type.GROUND]);
const result = getRandomSpeciesByStarterTier(9, undefined, [Type.GROUND]);
const pokeSpecies = getPokemonSpecies(result);
expect(pokeSpecies.speciesId).toBe(Species.GROUDON);
});

View File

@ -29,14 +29,14 @@ describe("Mystery Encounters", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, [Species.CHARIZARD, Species.VOLCARONA]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(game.scene.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name);
});
it("", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, [Species.CHARIZARD, Species.VOLCARONA]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(game.scene.getCurrentPhase()!.constructor.name).toBe(MysteryEncounterPhase.name);
});
it("spawns mysterious challengers encounter", async () => {

View File

@ -37,7 +37,7 @@ describe("Mystery Encounter Phases", () => {
await game.runToMysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS, [Species.CHARIZARD, Species.VOLCARONA]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(game.scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
});
it("Runs MysteryEncounterPhase", async() => {
@ -73,7 +73,7 @@ describe("Mystery Encounter Phases", () => {
handler.processInput(Button.ACTION);
// Waitfor required so that option select messages and preOptionPhase logic are handled
await vi.waitFor(() => expect(game.scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterOptionSelectedPhase.name));
await vi.waitFor(() => expect(game.scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterOptionSelectedPhase.name));
expect(game.scene.ui.getMode()).toBe(Mode.MESSAGE);
expect(dialogueSpy).toHaveBeenCalledTimes(1);
expect(messageSpy).toHaveBeenCalledTimes(2);

View File

@ -28,12 +28,6 @@ describe("SelectModifierPhase", () => {
game = new GameManager(phaserGame);
scene = game.scene;
vi.spyOn(scene, "resetSeed").mockImplementation(() => {
scene.waveSeed = "test";
Phaser.Math.RND.sow([scene.waveSeed]);
scene.rngCounter = 0;
});
initSceneWithoutEncounterPhase(scene, [Species.ABRA, Species.VOLCARONA]);
});
@ -49,7 +43,7 @@ describe("SelectModifierPhase", () => {
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
}, 20000);
});
it("should generate random modifiers", async () => {
const selectModifierPhase = new SelectModifierPhase(scene);
@ -60,10 +54,7 @@ describe("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).toEqual("TEMP_STAT_BOOSTER");
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("POKEBALL");
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("BERRY");
}, 5000);
});
it("should modify reroll cost", async () => {
const options = [
@ -73,12 +64,12 @@ describe("SelectModifierPhase", () => {
];
const selectModifierPhase1 = new SelectModifierPhase(scene);
const selectModifierPhase2 = new SelectModifierPhase(scene, 0, null, { rerollMultiplier: 2 });
const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { rerollMultiplier: 2 });
const cost1 = selectModifierPhase1.getRerollCost(options, false);
const cost2 = selectModifierPhase2.getRerollCost(options, false);
expect(cost2).toEqual(cost1 * 2);
}, 5000);
});
it("should generate random modifiers from reroll", async () => {
let selectModifierPhase = new SelectModifierPhase(scene);
@ -88,9 +79,6 @@ describe("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).toEqual("TEMP_STAT_BOOSTER");
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("POKEBALL");
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("BERRY");
// Simulate selecting reroll
selectModifierPhase = new SelectModifierPhase(scene, 1, [ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON]);
@ -100,10 +88,7 @@ describe("SelectModifierPhase", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
expect(modifierSelectHandler.options.length).toEqual(3);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("TM_COMMON");
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("LURE");
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("PP_UP");
}, 5000);
});
it("should generate random modifiers of same tier for reroll with reroll lock", async () => {
// Just use fully random seed for this test
@ -137,13 +122,13 @@ describe("SelectModifierPhase", () => {
expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount).toEqual(firstRollTiers[0]);
expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount).toEqual(firstRollTiers[1]);
expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount).toEqual(firstRollTiers[2]);
}, 5000);
});
it("should generate custom modifiers", async () => {
const customModifiers: CustomModifierSettings = {
guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_ULTRA, modifierTypes.LEFTOVERS, modifierTypes.AMULET_COIN, modifierTypes.GOLDEN_PUNCH]
};
const selectModifierPhase = new SelectModifierPhase(scene, 0, null, customModifiers);
const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers);
scene.pushPhase(selectModifierPhase);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -156,7 +141,7 @@ describe("SelectModifierPhase", () => {
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("LEFTOVERS");
expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("AMULET_COIN");
expect(modifierSelectHandler.options[4].modifierTypeOption.type.id).toEqual("GOLDEN_PUNCH");
}, 5000);
});
it("should generate custom modifier tiers that can upgrade from luck", async () => {
const customModifiers: CustomModifierSettings = {
@ -170,7 +155,7 @@ describe("SelectModifierPhase", () => {
}
scene.getParty().push(pokemon, pokemon, pokemon, pokemon, pokemon, pokemon);
const selectModifierPhase = new SelectModifierPhase(scene, 0, null, customModifiers);
const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers);
scene.pushPhase(selectModifierPhase);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -183,14 +168,14 @@ describe("SelectModifierPhase", () => {
expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ULTRA);
expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ROGUE);
expect(modifierSelectHandler.options[4].modifierTypeOption.type.tier - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount).toEqual(ModifierTier.MASTER);
}, 5000);
});
it("should generate custom modifiers and modifier tiers together", async () => {
const customModifiers: CustomModifierSettings = {
guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.TM_COMMON],
guaranteedModifierTiers: [ModifierTier.MASTER, ModifierTier.MASTER]
};
const selectModifierPhase = new SelectModifierPhase(scene, 0, null, customModifiers);
const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers);
scene.pushPhase(selectModifierPhase);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -202,7 +187,7 @@ describe("SelectModifierPhase", () => {
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("TM_COMMON");
expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER);
expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER);
}, 5000);
});
it("should fill remaining modifiers if fillRemaining is true with custom modifiers", async () => {
const customModifiers: CustomModifierSettings = {
@ -210,7 +195,7 @@ describe("SelectModifierPhase", () => {
guaranteedModifierTiers: [ModifierTier.MASTER],
fillRemaining: true
};
const selectModifierPhase = new SelectModifierPhase(scene, 0, null, customModifiers);
const selectModifierPhase = new SelectModifierPhase(scene, 0, undefined, customModifiers);
scene.pushPhase(selectModifierPhase);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -220,5 +205,5 @@ describe("SelectModifierPhase", () => {
expect(modifierSelectHandler.options.length).toEqual(3);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM");
expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier).toEqual(ModifierTier.MASTER);
}, 5000);
});
});

View File

@ -171,8 +171,8 @@ export default class GameManager {
*/
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) {
if (!isNullOrUndefined(encounterType)) {
this.override.disableTrainerWaves(true);
this.override.mysteryEncounter(encounterType);
this.override.disableTrainerWaves();
this.override.mysteryEncounter(encounterType!);
}
await this.runToTitle();

View File

@ -108,5 +108,5 @@ export function initSceneWithoutEncounterPhase(scene, species?: Species[]) {
scene.getParty().push(starterPokemon);
});
scene.currentBattle = new Battle(getGameMode(GameModes.CLASSIC), 5, BattleType.WILD, null, false);
scene.currentBattle = new Battle(getGameMode(GameModes.CLASSIC), 5, BattleType.WILD, undefined, false);
}

View File

@ -8,8 +8,10 @@ import * as GameMode from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode";
import { ModifierOverride } from "#app/modifier/modifier-type.js";
import Overrides from "#app/overrides";
import { vi } from "vitest";
import { MockInstance, vi } from "vitest";
import { GameManagerHelper } from "./gameManagerHelper";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/**
* Helper to handle overrides in tests
@ -281,6 +283,41 @@ export class OverridesHelper extends GameManagerHelper {
return this;
}
/**
* Override the encounter chance for a mystery encounter.
* @param percentage the encounter chance in %
* @returns spy instance
*/
mysteryEncounterChance(percentage: number) {
const maxRate: number = 256; // 100%
const rate = maxRate * (percentage / 100);
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_RATE_OVERRIDE", "get").mockReturnValue(rate);
this.log(`Mystery encounter chance set to ${percentage}% (=${rate})!`);
return spy;
}
/**
* Override the encounter chance for a mystery encounter.
* @returns spy instance
* @param tier
*/
mysteryEncounterTier(tier: MysteryEncounterTier): MockInstance {
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_TIER_OVERRIDE", "get").mockReturnValue(tier);
this.log(`Mystery encounter tier set to ${tier}!`);
return spy;
}
/**
* Override the encounter that spawns for the scene
* @param encounterType
* @returns spy instance
*/
mysteryEncounter(encounterType: MysteryEncounterType) {
const spy = vi.spyOn(Overrides, "MYSTERY_ENCOUNTER_OVERRIDE", "get").mockReturnValue(encounterType);
this.log(`Mystery encounter override set to ${encounterType}!`);
return spy;
}
private log(...params: any[]) {
console.log("Overrides:", ...params);
}

View File

@ -1,3 +1,3 @@
export interface MockGameObject {
name: string;
}

View File

@ -14,7 +14,7 @@ export default class MockContainer implements MockGameObject {
public frame;
protected textureManager;
public list: MockGameObject[] = [];
private name?: string;
name: string;
constructor(textureManager: MockTextureManager, x, y) {
this.x = x;

View File

@ -3,6 +3,7 @@ import { MockGameObject } from "../mockGameObject";
export default class MockGraphics implements MockGameObject {
private scene;
public list: MockGameObject[] = [];
name: string;
constructor(textureManager, config) {
this.scene = textureManager.scene;
}

View File

@ -4,6 +4,7 @@ export default class MockRectangle implements MockGameObject {
private fillColor;
private scene;
public list: MockGameObject[] = [];
name: string;
constructor(textureManager, x, y, width, height, fillColor) {
this.fillColor = fillColor;

View File

@ -14,6 +14,7 @@ export default class MockSprite implements MockGameObject {
public scene;
public anims;
public list: MockGameObject[] = [];
name: string;
constructor(textureManager, x, y, texture) {
this.textureManager = textureManager;
this.scene = textureManager.scene;

View File

@ -11,7 +11,7 @@ export default class MockText implements MockGameObject {
public list: MockGameObject[] = [];
public style;
public text = "";
private name?: string;
name: string;
public color?: string;
constructor(textureManager, x, y, content, styleOptions) {

View File

@ -12,6 +12,7 @@ export default class MockTexture implements MockGameObject {
public source;
public frames: object;
public firstFrame: string;
name: string;
constructor(manager, key: string, source) {
this.manager = manager;

View File

@ -336,7 +336,7 @@ export default class PhaseInterceptor {
const actionForNextPrompt = this.prompts[0];
const expireFn = actionForNextPrompt.expireFn && actionForNextPrompt.expireFn();
const currentMode = this.scene.ui.getMode();
const currentPhase = this.scene.getCurrentPhase().constructor.name;
const currentPhase = this.scene.getCurrentPhase()?.constructor.name;
const currentHandler = this.scene.ui.getHandler();
if (expireFn) {
this.prompts.shift();

View File

@ -18,24 +18,24 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export default class MysteryEncounterUiHandler extends UiHandler {
private cursorContainer: Phaser.GameObjects.Container;
private cursorObj: Phaser.GameObjects.Image;
private cursorObj?: Phaser.GameObjects.Image;
private optionsContainer: Phaser.GameObjects.Container;
private tooltipWindow: Phaser.GameObjects.NineSlice;
private tooltipContainer: Phaser.GameObjects.Container;
private tooltipScrollTween: Phaser.Tweens.Tween;
private tooltipScrollTween?: Phaser.Tweens.Tween;
private descriptionWindow: Phaser.GameObjects.NineSlice;
private descriptionContainer: Phaser.GameObjects.Container;
private descriptionScrollTween: Phaser.Tweens.Tween;
private descriptionScrollTween?: Phaser.Tweens.Tween;
private rarityBall: Phaser.GameObjects.Sprite;
private dexProgressWindow: Phaser.GameObjects.NineSlice;
private dexProgressContainer: Phaser.GameObjects.Container;
private showDexProgress: boolean = false;
private overrideSettings: OptionSelectSettings;
private overrideSettings?: OptionSelectSettings;
private encounterOptions: MysteryEncounterOption[] = [];
private optionsMeetsReqs: boolean[];
@ -330,9 +330,9 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.encounterOptions = this.overrideSettings?.overrideOptions ?? mysteryEncounter.options;
this.optionsMeetsReqs = [];
const titleText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE);
const descriptionText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.description, TextStyle.TOOLTIP_CONTENT);
const queryText: string = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.query, TextStyle.TOOLTIP_CONTENT);
const titleText: string | null = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue?.title, TextStyle.TOOLTIP_TITLE);
const descriptionText: string | null = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue?.description, TextStyle.TOOLTIP_CONTENT);
const queryText: string | null = getEncounterText(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue?.query, TextStyle.TOOLTIP_CONTENT);
// Clear options container (except cursor)
this.optionsContainer.removeAll(true);
@ -355,9 +355,9 @@ export default class MysteryEncounterUiHandler extends UiHandler {
}
this.optionsMeetsReqs.push(option.meetsRequirements(this.scene));
const optionDialogue = option.dialogue;
const optionDialogue = option.dialogue!;
const label = !this.optionsMeetsReqs[i] && optionDialogue.disabledButtonLabel ? optionDialogue.disabledButtonLabel : optionDialogue.buttonLabel;
let text: string;
let text: string | null;
if (option.hasRequirements() && this.optionsMeetsReqs[i] && (option.optionMode === MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL || option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) {
// Options with special requirements that are met are automatically colored green
text = getEncounterText(this.scene, label, TextStyle.SUMMARY_GREEN);
@ -383,7 +383,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.optionsContainer.add(viewPartyText);
// Description Window
const titleTextObject = addBBCodeTextObject(this.scene, 0, 0, titleText, TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 });
const titleTextObject = addBBCodeTextObject(this.scene, 0, 0, titleText ?? "", TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 });
this.descriptionContainer.add(titleTextObject);
titleTextObject.setPosition(72 - titleTextObject.displayWidth / 2, 5.5);
@ -395,7 +395,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
const ballType = getPokeballAtlasKey(index);
this.rarityBall.setTexture("pb", ballType);
const descriptionTextObject = addBBCodeTextObject(this.scene, 6, 25, descriptionText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } });
const descriptionTextObject = addBBCodeTextObject(this.scene, 6, 25, descriptionText ?? "", TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } });
// Sets up the mask that hides the description text to give an illusion of scrolling
const descriptionTextMaskRect = this.scene.make.graphics({});
@ -412,7 +412,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
if (this.descriptionScrollTween) {
this.descriptionScrollTween.remove();
this.descriptionScrollTween = null;
this.descriptionScrollTween = undefined;
}
// Animates the description text moving upwards
@ -429,7 +429,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.descriptionContainer.add(descriptionTextObject);
const queryTextObject = addBBCodeTextObject(this.scene, 0, 0, queryText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } });
const queryTextObject = addBBCodeTextObject(this.scene, 0, 0, queryText ?? "", TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } });
this.descriptionContainer.add(queryTextObject);
queryTextObject.setPosition(75 - queryTextObject.displayWidth / 2, 90);
@ -460,9 +460,9 @@ export default class MysteryEncounterUiHandler extends UiHandler {
return;
}
let text: string;
let text: string | null;
const cursorOption = this.encounterOptions[cursor];
const optionDialogue = cursorOption.dialogue;
const optionDialogue = cursorOption.dialogue!;
if (!this.optionsMeetsReqs[cursor] && (cursorOption.optionMode === MysteryEncounterOptionMode.DISABLED_OR_DEFAULT || cursorOption.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) && optionDialogue.disabledButtonTooltip) {
text = getEncounterText(this.scene, optionDialogue.disabledButtonTooltip, TextStyle.TOOLTIP_CONTENT);
} else {
@ -470,9 +470,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
}
// Auto-color options green/blue for good/bad by looking for (+)/(-)
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0];
text = text.replace(/(\(\+\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) + "[/color][/shadow]" + primaryStyleString);
text = text.replace(/(\(\-\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) + "[/color][/shadow]" + primaryStyleString);
if (text) {
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))!][0];
text = text.replace(/(\(\+\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) + "[/color][/shadow]" + primaryStyleString);
text = text.replace(/(\(\-\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) + "[/color][/shadow]" + primaryStyleString);
}
if (text) {
const tooltipTextObject = addBBCodeTextObject(this.scene, 6, 7, text, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 600 }, fontSize: "72px" });
@ -492,7 +494,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
if (this.tooltipScrollTween) {
this.tooltipScrollTween.remove();
this.tooltipScrollTween = null;
this.tooltipScrollTween = undefined;
}
// Animates the tooltip text moving upwards
@ -518,7 +520,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
clear(): void {
super.clear();
this.overrideSettings = null;
this.overrideSettings = undefined;
this.optionsContainer.setVisible(false);
this.optionsContainer.removeAll(true);
this.dexProgressContainer.setVisible(false);
@ -534,7 +536,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
if (this.cursorObj) {
this.cursorObj.destroy();
}
this.cursorObj = null;
this.cursorObj = undefined;
}
/**

View File

@ -243,7 +243,7 @@ export function getBBCodeFrag(content: string, textStyle: TextStyle, uiTheme: Ui
export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTheme: UiTheme = UiTheme.DEFAULT): string {
// Apply primary styling before anything else
let text = getBBCodeFrag(content, primaryStyle, uiTheme) + "[/color][/shadow]";
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0];
const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))!][0];
// Set custom colors
text = text.replace(/@\[([^{]*)\]{([^}]*)}/gi, (substring, textStyle: string, textToColor: string) => {