Merge pull request #3861 from pagefaultgames/fun-and-games

Fun And Games! Encounter
This commit is contained in:
ImperialSympathizer 2024-08-27 21:30:37 -04:00 committed by GitHub
commit 162a3b1d5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 1189 additions and 216 deletions

Binary file not shown.

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "carnival_game.png",
"format": "RGBA8888",
"size": {
"w": 38,
"h": 82
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 38,
"h": 82
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 38,
"h": 82
},
"frame": {
"x": 0,
"y": 0,
"w": 38,
"h": 82
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:d40b6742392c2fe8ca0735b3f561e319:5dcda5410b12f0aa75eb0dd1fbcbe4f9:d171fb17d3017d1f655cd8dd14c252b7$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "carnival_man.png",
"format": "RGBA8888",
"size": {
"w": 50,
"h": 77
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 15,
"y": 3,
"w": 50,
"h": 77
},
"frame": {
"x": 0,
"y": 0,
"w": 50,
"h": 77
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:e80aa9a809a7cca6d05992cb82f6dbd9:ea9962edd1cdc1e503deecf2ce1863c1:55647352b6547cf03212506309f2abf5$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "carnival_wobbuffet.png",
"format": "RGBA8888",
"size": {
"w": 45,
"h": 55
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 45,
"h": 55
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 45,
"h": 55
},
"frame": {
"x": 0,
"y": 0,
"w": 45,
"h": 55
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:879de17da906ea52e5a71afacb88fcf6:90f64e8eaac4ff1e67373f60c3d98d36:a090cb3294ca1218a4f90ecb97df81d7$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

View File

@ -254,7 +254,7 @@ export default class BattleScene extends SceneBase {
public pokemonInfoContainer: PokemonInfoContainer;
private party: PlayerPokemon[];
public mysteryEncounterData: MysteryEncounterData = new MysteryEncounterData(null);
public lastMysteryEncounter: MysteryEncounter;
public lastMysteryEncounter?: MysteryEncounter;
/** Combined Biome and Wave count text */
private biomeWaveText: Phaser.GameObjects.Text;
private moneyText: Phaser.GameObjects.Text;
@ -1213,7 +1213,7 @@ export default class BattleScene extends SceneBase {
const maxExpLevel = this.getMaxExpLevel();
this.lastEnemyTrainer = lastBattle?.trainer ?? null;
this.lastMysteryEncounter = lastBattle?.mysteryEncounter ?? null;
this.lastMysteryEncounter = lastBattle?.mysteryEncounter;
this.executeWithSeedOffset(() => {
this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble);

View File

@ -70,7 +70,7 @@ export default class Battle {
public lastUsedPokeball: PokeballType | null;
public playerFaints: number; // The amount of times pokemon on the players side have fainted
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
public mysteryEncounter: MysteryEncounter;
public mysteryEncounter?: MysteryEncounter;
private rngCounter: integer = 0;

View File

@ -891,6 +891,8 @@ export abstract class BattleAnim {
const isUser = frame.target === AnimFrameTarget.USER;
if (isUser && target === user) {
continue;
} else if (this.playOnEmptyField && frame.target === AnimFrameTarget.TARGET) {
continue;
}
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];
const spriteSource = isUser ? userSprite : targetSprite;
@ -1223,8 +1225,8 @@ export class CommonBattleAnim extends BattleAnim {
export class MoveAnim extends BattleAnim {
public move: Moves;
constructor(move: Moves, user: Pokemon, target: BattlerIndex) {
super(user, user.scene.getField()[target]);
constructor(move: Moves, user: Pokemon, target: BattlerIndex, playOnEmptyField: boolean = false) {
super(user, user.scene.getField()[target], playOnEmptyField);
this.move = move;
}

View File

@ -34,7 +34,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
])
.withAutoHideIntroVisuals(false)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Randomly pick from 1 of the 5 stat trainers to spawn
let trainerType: TrainerType;
@ -135,7 +135,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
buttonTooltip: `${namespace}.option.1.tooltip`
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
@ -160,7 +160,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
buttonTooltip: `${namespace}.option.2.tooltip`
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Full heal party
scene.unshiftPhase(new PartyHealPhase(scene, true));

View File

@ -173,7 +173,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
scene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
@ -242,7 +242,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Provides 1x Reviver Seed to each party member at end of battle
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
@ -283,7 +283,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const berryMap = encounter.misc.berryItemsMap;
// Returns 2/5 of the berries stolen from each Pokemon
@ -349,7 +349,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("battle-anims/Follow Me");
scene.tweens.chain({
@ -423,7 +423,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 +455,7 @@ function doBerrySpritePile(scene: BattleScene, isEat: boolean = false) {
if (isEat) {
animationOrder = animationOrder.reverse();
}
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
animationOrder.forEach((berry, i) => {
const introVisualsIndex = encounter.spriteConfigs.findIndex(config => config.spriteKey?.includes(berry));
let sprite: Phaser.GameObjects.Sprite, tintSprite: Phaser.GameObjects.Sprite;

View File

@ -58,7 +58,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
const price = scene.getWaveMoneyAmount(10);
@ -99,7 +99,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);
scene.removePokemonFromPlayerParty(encounter.misc.pokemon);
@ -132,7 +132,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Extort the rich kid for money
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Update money and remove pokemon from party
updatePlayerMoney(scene, encounter.misc.price);

View File

@ -53,7 +53,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
@ -125,7 +125,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const numBerries = encounter.misc.numBerries;
const doBerryRewards = async () => {
@ -150,7 +150,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
}
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
)
.withOption(
@ -162,7 +162,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick race for berries
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const fastestPokemon = encounter.misc.fastestPokemon;
const enemySpeed = encounter.misc.enemySpeed;
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
@ -191,7 +191,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
}
};
const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);

View File

@ -204,7 +204,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculates what trainers are available for battle in the encounter
// Bug type superfan trainer config
@ -241,7 +241,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Select battle the bug trainer
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
// Init the moves available for tutor
@ -272,7 +272,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Player shows off their bug types
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Player gets different rewards depending on the number of bug types they have
const numBugTypes = scene.getParty().filter(p => p.isOfType(Type.BUG, true)).length;
@ -360,7 +360,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
secondOptionPrompt: `${namespace}.option.3.select_prompt`,
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter(item => {
@ -403,7 +403,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier;
// Remove the modifier if its stacks go to 0
@ -583,7 +583,7 @@ function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSl
function doBugTypeMoveTutor(scene: BattleScene): Promise<void> {
return new Promise<void>(async resolve => {
const moveOptions = scene.currentBattle.mysteryEncounter.misc.moveTutorOptions;
const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
await showEncounterDialogue(scene, `${namespace}.battle_won`, `${namespace}.speaker`);
const overlayScale = 1;

View File

@ -102,7 +102,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const clownTrainerType = TrainerType.HARLEQUIN;
const clownConfig = trainerConfigs[clownTrainerType].copy();
@ -159,7 +159,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn battle
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
@ -236,7 +236,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
// Swap player's items on pokemon with the most items
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
// So Vitamins, form change items, etc. are not included
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const party = scene.getParty();
let mostHeldItemsPokemon = party[0];
@ -418,8 +418,8 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
if (!pokemon.mysteryEncounterData) {
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, Abilities.AERILATE);
}
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter!.misc.ability;
scene.currentBattle.mysteryEncounter!.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
};

View File

@ -102,7 +102,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const species = getPokemonSpecies(Species.ORICORIO);
const enemyPokemon = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false);
@ -170,7 +170,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
@ -200,7 +200,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Learn its Dance
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
@ -236,7 +236,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Open menu for selecting pokemon with a Dancing move
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
return pokemon.moveset
@ -272,7 +272,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Show the Oricorio a dance, and recruit it
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const oricorio = encounter.misc.oricorioData.toPokemon(scene);
oricorio.passive = true;

View File

@ -127,14 +127,15 @@ export const DarkDealEncounter: MysteryEncounter =
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
scene.removePokemonFromPlayerParty(removedPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
const encounter = scene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
// Store removed pokemon types
scene.currentBattle.mysteryEncounter.misc = [
encounter.misc = [
removedPokemon.species.type1,
];
if (removedPokemon.species.type2) {
scene.currentBattle.mysteryEncounter.misc.push(removedPokemon.species.type2);
encounter.misc.push(removedPokemon.species.type2);
}
})
.withOptionPhase(async (scene: BattleScene) => {
@ -142,7 +143,7 @@ export const DarkDealEncounter: MysteryEncounter =
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL));
// Start encounter with random legendary (7-10 starter strength) that has level additive
const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[];
const bossTypes = scene.currentBattle.mysteryEncounter!.misc as Type[];
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
const roll = randSeedInt(100);
const starterTier: number | [number, number] =

View File

@ -102,7 +102,7 @@ export const DelibirdyEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
return true;
})
@ -140,7 +140,7 @@ export const DelibirdyEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
@ -178,7 +178,7 @@ export const DelibirdyEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier;
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
@ -235,7 +235,7 @@ export const DelibirdyEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
@ -273,7 +273,7 @@ export const DelibirdyEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const modifier = encounter.misc.chosenModifier;
// Check if the player has max stacks of Berry Pouch already

View File

@ -67,7 +67,7 @@ export const FieldTripEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
@ -123,7 +123,7 @@ export const FieldTripEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ATK])!,
@ -153,7 +153,7 @@ export const FieldTripEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
@ -215,7 +215,7 @@ export const FieldTripEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPATK])!,
@ -245,7 +245,7 @@ export const FieldTripEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for Pokemon move valid for this option
return pokemon.moveset.map((move: PokemonMove) => {
@ -301,7 +301,7 @@ export const FieldTripEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
if (encounter.misc.correctMove) {
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ACC])!,

View File

@ -50,7 +50,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate boss mons
const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA);
@ -132,7 +132,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonCharcoal(scene));
encounter.startOfBattleEffects.push(
@ -160,7 +160,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
move: new PokemonMove(Moves.QUIVER_DANCE),
ignorePp: true
});
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
)
.withSimpleOption(
@ -175,7 +175,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Damage non-fire types and burn 1 random non-fire type member
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE));
for (const pkm of nonFireTypes) {
@ -220,7 +220,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Fire types help calm the Volcarona
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
transitionMysteryEncounterIntroVisuals(scene);
setEncounterRewards(scene,
{ fillRemaining: true },
@ -245,7 +245,7 @@ function giveLeadPokemonCharcoal(scene: BattleScene) {
if (leadPokemon) {
const charcoal = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]) as AttackTypeBoosterModifierType;
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal);
scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
scene.currentBattle.mysteryEncounter!.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}.found_charcoal`);
}
}

View File

@ -46,7 +46,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
@ -122,10 +122,9 @@ export const FightOrFlightEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Pick battle
const item = scene.currentBattle.mysteryEncounter
.misc as ModifierTypeOption;
const item = scene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]);
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
}
)
.withOption(
@ -144,8 +143,8 @@ export const FightOrFlightEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Pick steal
const encounter = scene.currentBattle.mysteryEncounter;
const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption;
const encounter = scene.currentBattle.mysteryEncounter!;
const item = scene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
// Use primaryPokemon to execute the thievery

View File

@ -0,0 +1,422 @@
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage, showEncounterText } 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 { Species } from "#enums/species";
import i18next from "i18next";
import { getPokemonNameWithAffix } from "#app/messages";
import { PlayerGender } from "#enums/player-gender";
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
import { addPokeballOpenParticles } from "#app/field/anims";
import { ShinySparklePhase } from "#app/phases/shiny-sparkle-phase";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { modifierTypes } from "#app/modifier/modifier-type";
import { Nature } from "#enums/nature";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:funAndGames";
/**
* Fun and Games! encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3819 | GitHub Issue #3819}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FunAndGamesEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FUN_AND_GAMES)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play
.withAutoHideIntroVisuals(false)
// Allows using move without a visible enemy pokemon
.withBattleAnimationsWithoutTargets(true)
// The Wobbuffet won't use moves
.withSkipEnemyBattleTurns(true)
// Will skip COMMAND selection menu and go straight to FIGHT (move select) menu
.withSkipToFightInput(true)
.withIntroSpriteConfigs([
{
spriteKey: "carnival_game",
fileRoot: "mystery-encounters",
hasShadow: false,
x: 0,
y: 6,
},
{
spriteKey: "carnival_wobbuffet",
fileRoot: "mystery-encounters",
hasShadow: true,
x: -28,
y: 6,
yShadow: 6
},
{
spriteKey: "carnival_man",
fileRoot: "mystery-encounters",
hasShadow: true,
x: 40,
y: 6,
yShadow: 6
},
])
.withIntroDialogue([
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
},
])
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
scene.loadBgm("mystery_encounter_fun_and_games", "mystery_encounter_fun_and_games.mp3");
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
// Change the bgm
scene.fadeOutBgm(2000, false);
scene.time.delayedCall(2000, () => {
scene.playBgm("mystery_encounter_fun_and_games");
});
return true;
})
.withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Select Pokemon for minigame
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.misc = {
playerPokemon: pokemon,
};
};
// Only Pokemon that are not KOed/legal can be selected
const selectableFilter = (pokemon: Pokemon) => {
const meetsReqs = pokemon.isAllowedInBattle();
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
}
return null;
};
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
// Start minigame
const encounter = scene.currentBattle.mysteryEncounter!;
encounter.misc.turnsRemaining = 3;
// Update money
const moneyCost = (encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney;
updatePlayerMoney(scene, -moneyCost, true, false);
await showEncounterText(scene, i18next.t("mysteryEncounterMessages:paid_money", { amount: moneyCost }));
// Handlers for battle events
encounter.onTurnStart = handleNextTurn; // triggered during TurnInitPhase
encounter.doContinueEncounter = handleLoseMinigame; // triggered during MysteryEncounterRewardsPhase, post VictoryPhase if the player KOs Wobbuffet
hideShowmanIntroSprite(scene);
await summonPlayerPokemon(scene);
await showWobbuffetHealthBar(scene);
return true;
})
.build()
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Leave encounter with no rewards or exp
transitionMysteryEncounterIntroVisuals(scene, true, true);
leaveEncounterWithoutBattle(scene, true);
return true;
}
)
.build();
async function summonPlayerPokemon(scene: BattleScene) {
return new Promise<void>(async resolve => {
const encounter = scene.currentBattle.mysteryEncounter!;
const playerPokemon = encounter.misc.playerPokemon;
// Swaps the chosen Pokemon and the first player's lead Pokemon in the party
const party = scene.getParty();
const chosenIndex = party.indexOf(playerPokemon);
if (chosenIndex !== 0) {
[party[chosenIndex], party[0]] = [party[chosenIndex], party[chosenIndex]];
}
// Do trainer summon animation
let playerAnimationPromise: Promise<void> | undefined;
scene.ui.showText(i18next.t("battle:playerGo", { pokemonName: getPokemonNameWithAffix(playerPokemon) }));
scene.pbTray.hide();
scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`);
scene.time.delayedCall(562, () => {
scene.trainer.setFrame("2");
scene.time.delayedCall(64, () => {
scene.trainer.setFrame("3");
});
});
scene.tweens.add({
targets: scene.trainer,
x: -36,
duration: 1000,
onComplete: () => scene.trainer.setVisible(false)
});
scene.time.delayedCall(750, () => {
playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon);
});
// Also loads Wobbuffet data
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
scene.currentBattle.enemyParty = [];
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false);
wobbuffet.ivs = [0, 0, 0, 0, 0, 0];
wobbuffet.setNature(Nature.MILD);
wobbuffet.setAlpha(0);
wobbuffet.setVisible(false);
wobbuffet.calculateStats();
scene.currentBattle.enemyParty[0] = wobbuffet;
scene.gameData.setPokemonSeen(wobbuffet, true);
await wobbuffet.loadAssets();
const id = setInterval(checkPlayerAnimationPromise, 500);
async function checkPlayerAnimationPromise() {
if (playerAnimationPromise) {
clearInterval(id);
await playerAnimationPromise;
resolve();
}
}
});
}
function handleLoseMinigame(scene: BattleScene) {
return new Promise<void>(async resolve => {
// Check Wobbuffet is still alive
const wobbuffet = scene.getEnemyPokemon();
if (!wobbuffet || wobbuffet.isFainted(true) || wobbuffet.hp === 0) {
// Player loses
// End the battle
if (wobbuffet) {
wobbuffet.hideInfo();
scene.field.remove(wobbuffet);
}
transitionMysteryEncounterIntroVisuals(scene, true, true);
scene.currentBattle.enemyParty = [];
scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, true);
await showEncounterText(scene, `${namespace}.ko`);
const reviveCost = scene.getWaveMoneyAmount(1.5);
updatePlayerMoney(scene, -reviveCost, true, false);
}
resolve();
});
}
function handleNextTurn(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
const wobbuffet = scene.getEnemyPokemon();
if (!wobbuffet) {
// Should never be triggered, just handling the edge case
handleLoseMinigame(scene);
return true;
}
if (encounter.misc.turnsRemaining <= 0) {
// Check Wobbuffet's health for the actual result
const healthRatio = wobbuffet.hp / wobbuffet.getMaxHp();
let resultMessageKey: string;
let isHealPhase = false;
if (healthRatio < 0.03) {
// Grand prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MULTI_LENS], fillRemaining: false });
resultMessageKey = `${namespace}.best_result`;
} else if (healthRatio < 0.15) {
// 2nd prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SCOPE_LENS], fillRemaining: false });
resultMessageKey = `${namespace}.great_result`;
} else if (healthRatio < 0.33) {
// 3rd prize
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.WIDE_LENS], fillRemaining: false });
resultMessageKey = `${namespace}.good_result`;
} else {
// No prize
isHealPhase = true;
resultMessageKey = `${namespace}.bad_result`;
}
// End the battle
wobbuffet.hideInfo();
scene.field.remove(wobbuffet);
scene.currentBattle.enemyParty = [];
scene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
leaveEncounterWithoutBattle(scene, isHealPhase);
// Must end the TurnInit phase prematurely so battle phases aren't added to queue
queueEncounterMessage(scene, `${namespace}.end_game`);
queueEncounterMessage(scene, resultMessageKey);
// Skip remainder of TurnInitPhase
return true;
} else {
if (encounter.misc.turnsRemaining < 3) {
// Display charging messages on turns that aren't the initial turn
queueEncounterMessage(scene, `${namespace}.charging_continue`);
}
queueEncounterMessage(scene, `${namespace}.turn_remaining_${encounter.misc.turnsRemaining}`);
encounter.misc.turnsRemaining--;
}
// Don't skip remainder of TurnInitPhase
return false;
}
async function showWobbuffetHealthBar(scene: BattleScene) {
const wobbuffet = scene.getEnemyPokemon()!;
scene.add.existing(wobbuffet);
scene.field.add(wobbuffet);
const playerPokemon = scene.getPlayerPokemon() as Pokemon;
if (playerPokemon?.visible) {
scene.field.moveBelow(wobbuffet, playerPokemon);
}
// Show health bar and trigger cry
wobbuffet.showInfo();
scene.time.delayedCall(1000, () => {
wobbuffet.cry();
});
wobbuffet.resetSummonData();
// Track the HP change across turns
scene.currentBattle.mysteryEncounter!.misc.wobbuffetHealth = wobbuffet.hp;
}
function summonPlayerPokemonAnimation(scene: BattleScene, pokemon: PlayerPokemon): Promise<void> {
return new Promise<void>(resolve => {
const pokeball = scene.addFieldSprite(36, 80, "pb", getPokeballAtlasKey(pokemon.pokeball));
pokeball.setVisible(false);
pokeball.setOrigin(0.5, 0.625);
scene.field.add(pokeball);
pokemon.setFieldPosition(FieldPosition.CENTER, 0);
const fpOffset = pokemon.getFieldPositionOffset();
pokeball.setVisible(true);
scene.tweens.add({
targets: pokeball,
duration: 650,
x: 100 + fpOffset[0]
});
scene.tweens.add({
targets: pokeball,
duration: 150,
ease: "Cubic.easeOut",
y: 70 + fpOffset[1],
onComplete: () => {
scene.tweens.add({
targets: pokeball,
duration: 500,
ease: "Cubic.easeIn",
angle: 1440,
y: 132 + fpOffset[1],
onComplete: () => {
scene.playSound("se/pb_rel");
pokeball.destroy();
scene.add.existing(pokemon);
scene.field.add(pokemon);
addPokeballOpenParticles(scene, pokemon.x, pokemon.y - 16, pokemon.pokeball);
scene.updateModifiers(true);
scene.updateFieldScale();
pokemon.showInfo();
pokemon.playAnim();
pokemon.setVisible(true);
pokemon.getSprite().setVisible(true);
pokemon.setScale(0.5);
pokemon.tint(getPokeballTintColor(pokemon.pokeball));
pokemon.untint(250, "Sine.easeIn");
scene.updateFieldScale();
scene.tweens.add({
targets: pokemon,
duration: 250,
ease: "Sine.easeIn",
scale: pokemon.getSpriteScale(),
onComplete: () => {
pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 });
pokemon.getSprite().clearTint();
pokemon.resetSummonData();
scene.time.delayedCall(1000, () => {
if (pokemon.isShiny()) {
scene.unshiftPhase(new ShinySparklePhase(scene, pokemon.getBattlerIndex()));
}
pokemon.resetTurnData();
scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);
scene.pushPhase(new PostSummonPhase(scene, pokemon.getBattlerIndex()));
resolve();
});
}
});
}
});
}
});
});
}
function hideShowmanIntroSprite(scene: BattleScene) {
const carnivalGame = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(0)[0];
const wobbuffet = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1)[0];
const showMan = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(2)[0];
// Hide the showman
scene.tweens.add({
targets: showMan,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750
});
// Slide the Wobbuffet and Game over slightly
scene.tweens.add({
targets: [wobbuffet, carnivalGame],
x: "+=16",
ease: "Sine.easeInOut",
duration: 750
});
}

View File

@ -40,11 +40,11 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
])
.withIntroDialogue([{ text: `${namespace}.intro` }])
.withOnInit((scene: BattleScene) => {
const { mysteryEncounter } = scene.currentBattle;
const encounter = scene.currentBattle.mysteryEncounter!;
mysteryEncounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE));
mysteryEncounter.setDialogueToken("option1RequiredMove", Moves[OPTION_1_REQUIRED_MOVE]);
mysteryEncounter.setDialogueToken("option2RequiredMove", Moves[OPTION_2_REQUIRED_MOVE]);
encounter.setDialogueToken("damagePercentage", String(DAMAGE_PERCENTAGE));
encounter.setDialogueToken("option1RequiredMove", Moves[OPTION_1_REQUIRED_MOVE]);
encounter.setDialogueToken("option2RequiredMove", Moves[OPTION_2_REQUIRED_MOVE]);
return true;
})
@ -130,7 +130,7 @@ async function handlePokemonGuidingYouPhase(scene: BattleScene) {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
const { mysteryEncounter } = scene.currentBattle;
if (mysteryEncounter.selectedOption?.primaryPokemon?.id) {
if (mysteryEncounter?.selectedOption?.primaryPokemon?.id) {
setEncounterExp(scene, mysteryEncounter.selectedOption.primaryPokemon.id, laprasSpecies.baseExp, true);
} else {
console.warn("Lost at sea: No guide pokemon found but pokemon guides player. huh!?");

View File

@ -37,7 +37,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculates what trainers are available for battle in the encounter
// Normal difficulty trainer is randomly pulled from biome
@ -137,7 +137,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
@ -162,7 +162,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
@ -187,7 +187,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn brutal fight with ROGUE/ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];

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();
})
@ -113,7 +113,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
);
koPlayerPokemon(scene, highestLevelPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
scene.currentBattle.mysteryEncounter!.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
// Show which Pokemon was KOed, then leave encounter with no rewards
// Does this synchronously so that game over doesn't happen over result message
await showEncounterText(scene, `${namespace}.option.1.bad`);

View File

@ -82,7 +82,7 @@ export const PartTimerEncounter: MysteryEncounter =
]
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
@ -130,7 +130,7 @@ export const PartTimerEncounter: MysteryEncounter =
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
const moneyMultiplier = scene.currentBattle.mysteryEncounter.misc.moneyMultiplier;
const moneyMultiplier = scene.currentBattle.mysteryEncounter!.misc.moneyMultiplier;
// Give money and do dialogue
if (moneyMultiplier > 2.5) {
@ -160,7 +160,7 @@ export const PartTimerEncounter: MysteryEncounter =
]
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
@ -211,7 +211,7 @@ export const PartTimerEncounter: MysteryEncounter =
// Bring visuals back in
await transitionMysteryEncounterIntroVisuals(scene, false, false);
const moneyMultiplier = scene.currentBattle.mysteryEncounter.misc.moneyMultiplier;
const moneyMultiplier = scene.currentBattle.mysteryEncounter!.misc.moneyMultiplier;
// Give money and do dialogue
if (moneyMultiplier > 2.5) {
@ -244,7 +244,7 @@ export const PartTimerEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const selectedPokemon = encounter.selectedOption?.primaryPokemon!;
encounter.setDialogueToken("selectedPokemon", selectedPokemon.getNameToRender());

View File

@ -65,7 +65,7 @@ export const SafariZoneEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
encounter.continuousEncounter = true;
encounter.misc = {
safariPokemonRemaining: 3
@ -130,7 +130,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw a ball option
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
const catchResult = await throwPokeball(scene, pokemon);
@ -165,7 +165,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw bait option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
const pokemon = scene.currentBattle.mysteryEncounter!.misc.pokemon;
await throwBait(scene, pokemon);
// 100% chance to increase catch stage +2
@ -195,7 +195,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw mud option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
const pokemon = scene.currentBattle.mysteryEncounter!.misc.pokemon;
await throwMud(scene, pokemon);
// 100% chance to decrease flee stage -2
tryChangeFleeStage(scene, -2);
@ -219,7 +219,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.withOptionPhase(async (scene: BattleScene) => {
// Flee option
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
await doPlayerFlee(scene, pokemon);
// Check how many safari pokemon left
@ -237,7 +237,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
];
async function summonSafariPokemon(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Message pokemon remaining
encounter.setDialogueToken("remainingCount", encounter.misc.safariPokemonRemaining);
scene.queueMessage(getEncounterText(scene, `${namespace}.safari.remaining_count`) ?? "", null, true);
@ -301,7 +301,7 @@ async function summonSafariPokemon(scene: BattleScene) {
function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise<boolean> {
const baseCatchRate = pokemon.species.catchRate;
// Catch stage ranges from -6 to +6 (like stat boost stages)
const safariCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage;
const safariCatchStage = scene.currentBattle.mysteryEncounter!.misc.catchStage;
// Catch modifier ranges from 2/8 (-6 stage) to 8/2 (+6)
const safariModifier = (2 + Math.min(Math.max(safariCatchStage, 0), 6)) / (2 - Math.max(Math.min(safariCatchStage, 0), -6));
// Catch rate same as safari ball
@ -464,8 +464,8 @@ function tryChangeFleeStage(scene: BattleScene, change: number, chance?: number)
if (chance && randSeedInt(10) >= chance) {
return false;
}
const currentFleeStage = scene.currentBattle.mysteryEncounter.misc.fleeStage ?? 0;
scene.currentBattle.mysteryEncounter.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
const currentFleeStage = scene.currentBattle.mysteryEncounter!.misc.fleeStage ?? 0;
scene.currentBattle.mysteryEncounter!.misc.fleeStage = Math.min(Math.max(currentFleeStage + change, -6), 6);
return true;
}
@ -473,13 +473,13 @@ function tryChangeCatchStage(scene: BattleScene, change: number, chance?: number
if (chance && randSeedInt(10) >= chance) {
return false;
}
const currentCatchStage = scene.currentBattle.mysteryEncounter.misc.catchStage ?? 0;
scene.currentBattle.mysteryEncounter.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
const currentCatchStage = scene.currentBattle.mysteryEncounter!.misc.catchStage ?? 0;
scene.currentBattle.mysteryEncounter!.misc.catchStage = Math.min(Math.max(currentCatchStage + change, -6), 6);
return true;
}
async function doEndTurn(scene: BattleScene, cursorIndex: number) {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = encounter.misc.pokemon;
const isFlee = isPokemonFlee(pokemon, encounter.misc.fleeStage);
if (isFlee) {

View File

@ -73,7 +73,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Update money
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
@ -105,7 +105,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Choose Cheap Option
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon;
const modifiers = encounter.misc.modifiers;
@ -117,7 +117,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Damage and status applied after dealer leaves (to make thematic sense)
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon;
// Pokemon takes 1/3 max HP damage
@ -156,7 +156,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Update money
updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
@ -188,7 +188,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Choose Expensive Option
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon;
const modifiers = encounter.misc.modifiers;
@ -200,7 +200,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Status applied after dealer leaves (to make thematic sense)
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const chosenPokemon = encounter.misc.chosenPokemon;
// Roll for poison (20%)

View File

@ -48,7 +48,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
console.log(encounter);
// Calculate boss mon
@ -85,7 +85,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: true});
encounter.startOfBattleEffects.push(
{
@ -137,7 +137,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
})
.withOptionPhase(async (scene: BattleScene) => {
// Steal the Snorlax's Leftovers
const instance = scene.currentBattle.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);

View File

@ -57,7 +57,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const price = scene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
@ -81,7 +81,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
})
.withPreOptionPhase(async (scene: BattleScene) => {
// Update money
updatePlayerMoney(scene, -scene.currentBattle.mysteryEncounter.misc.price, true, false);
updatePlayerMoney(scene, -scene.currentBattle.mysteryEncounter!.misc.price, true, false);
})
.withOptionPhase(async (scene: BattleScene) => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene);
@ -107,7 +107,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit(scene);
setEncounterRewards(scene, { fillRemaining: true });
setEncounterExp(scene, scene.currentBattle.mysteryEncounter.selectedOption!.primaryPokemon!.id, 100);
setEncounterExp(scene, scene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100);
await initBattleWithEnemyConfig(scene, config);
})
.build()
@ -124,7 +124,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Inspect the Machine
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Init enemy
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
@ -150,7 +150,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.build();
async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate new biome (cannot be current biome)
const filteredBiomes = BIOME_CANDIDATES.filter(b => scene.arena.biomeType !== b);

View File

@ -51,7 +51,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
const tries = 0;
@ -118,7 +118,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const price = encounter.misc.price;
const purchasedPokemon = encounter.misc.pokemon as PlayerPokemon;

View File

@ -60,7 +60,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const config: EnemyPartyConfig = {
@ -118,7 +118,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
]
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Do blackout and hide intro visuals during blackout
scene.time.delayedCall(750, () => {
transitionMysteryEncounterIntroVisuals(scene, true, true, 50);
@ -176,7 +176,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Pick battle
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW], fillRemaining: true });
encounter.startOfBattleEffects.push(
{

View File

@ -80,7 +80,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
])
.withAutoHideIntroVisuals(false)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Loaded back to front for pop() operations
encounter.enemyPartyConfigs.push(getVitoTrainerConfig(scene));
@ -107,8 +107,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
},
async (scene: BattleScene) => {
// Spawn 5 trainer battles back to back with Macho Brace in rewards
// scene.currentBattle.mysteryEncounter.continuousEncounter = true;
scene.currentBattle.mysteryEncounter.doContinueEncounter = (scene: BattleScene) => {
scene.currentBattle.mysteryEncounter!.doContinueEncounter = (scene: BattleScene) => {
return endTrainerBattleAndShowDialogue(scene);
};
await transitionMysteryEncounterIntroVisuals(scene, true, false);
@ -136,7 +135,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter =
.build();
async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const nextConfig = encounter.enemyPartyConfigs.pop();
if (!nextConfig) {
await transitionMysteryEncounterIntroVisuals(scene, false, false);
@ -151,7 +150,7 @@ async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
return new Promise(async resolve => {
if (scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length === 0) {
if (scene.currentBattle.mysteryEncounter!.enemyPartyConfigs.length === 0) {
// Battle is over
const trainer = scene.currentBattle.trainer;
if (trainer) {

View File

@ -65,7 +65,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
],
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
encounter.misc = {
playerPokemon: pokemon,
@ -85,7 +85,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn light training session with chosen pokemon
@ -189,7 +189,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
// Open menu for selecting pokemon and Nature
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const natures = new Array(25).fill(null).map((val, i) => i as Nature);
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for nature selection
@ -223,7 +223,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn medium training session with chosen pokemon
@ -277,7 +277,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
})
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
// Open menu for selecting pokemon and ability to learn
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Return the options for ability selection
const speciesForm = !!pokemon.getFusionSpeciesForm()
@ -320,7 +320,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
const playerPokemon: PlayerPokemon = encounter.misc.playerPokemon;
// Spawn hard training session with chosen pokemon

View File

@ -54,7 +54,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
@ -124,7 +124,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
await showEncounterText(scene, `${namespace}.option.2.selected_2`);
transitionMysteryEncounterIntroVisuals(scene);
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
encounter.startOfBattleEffects.push(

View File

@ -150,7 +150,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
// Calculate all the newly transformed Pokemon and begin asset load
const teamTransformations = getTeamTransformations(scene);
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
scene.currentBattle.mysteryEncounter.misc = {
scene.currentBattle.mysteryEncounter!.misc = {
teamTransformations,
loadAssets
};
@ -161,8 +161,8 @@ export const WeirdDreamEncounter: MysteryEncounter =
// Change the entire player's party
// Wait for all new Pokemon assets to be loaded before showing transformation animations
await Promise.all(scene.currentBattle.mysteryEncounter.misc.loadAssets);
const transformations = scene.currentBattle.mysteryEncounter.misc.teamTransformations;
await Promise.all(scene.currentBattle.mysteryEncounter!.misc.loadAssets);
const transformations = scene.currentBattle.mysteryEncounter!.misc.teamTransformations;
// If there are 1-3 transformations, do them centered back to back
// Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions

View File

@ -42,6 +42,9 @@ export interface IMysteryEncounter {
catchAllowed: boolean;
continuousEncounter: boolean;
maxAllowedEncounters: number;
hasBattleAnimationsWithoutTargets: boolean;
skipEnemyBattleTurns: boolean;
skipToFightInput: boolean;
onInit?: (scene: BattleScene) => boolean;
onVisualsStart?: (scene: BattleScene) => boolean;
@ -113,7 +116,20 @@ export default class MysteryEncounter implements IMysteryEncounter {
* Rogue tier encounters default to 1, others default to 3
*/
maxAllowedEncounters: number;
/**
* If true, encounter will not animate the target Pokemon as part of battle animations
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
*/
hasBattleAnimationsWithoutTargets: boolean;
/**
* If true, will skip enemy pokemon turns during battle for the encounter
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
*/
skipEnemyBattleTurns: boolean;
/**
* If true, will skip COMMAND input and go straight to FIGHT (move select) input menu
*/
skipToFightInput: boolean;
/**
* Event callback functions
@ -122,6 +138,8 @@ export default class MysteryEncounter implements IMysteryEncounter {
onInit?: (scene: BattleScene) => boolean;
/** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */
onVisualsStart?: (scene: BattleScene) => boolean;
/** Event triggered prior to {@link CommandPhase}, during {@link TurnInitPhase} */
onTurnStart?: (scene: BattleScene) => boolean;
/** Event prior to any rewards logic in {@link MysteryEncounterRewardsPhase} */
onRewards?: (scene: BattleScene) => Promise<void>;
/** Will provide the player party EXP before rewards are displayed for that wave */
@ -471,6 +489,9 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
catchAllowed: boolean = false;
lockEncounterRewardTiers: boolean = false;
startOfBattleEffectsComplete: boolean = false;
hasBattleAnimationsWithoutTargets: boolean = false;
skipEnemyBattleTurns: boolean = false;
skipToFightInput: boolean = false;
maxAllowedEncounters: number = 3;
expMultiplier: number = 1;
@ -600,6 +621,35 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
return Object.assign(this, { continuousEncounter: continuousEncounter });
}
/**
* If true, encounter will not animate the target Pokemon as part of battle animations
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
* Default false
* @param hasBattleAnimationsWithoutTargets
*/
withBattleAnimationsWithoutTargets(hasBattleAnimationsWithoutTargets: boolean): this & Required<Pick<IMysteryEncounter, "hasBattleAnimationsWithoutTargets">> {
return Object.assign(this, { hasBattleAnimationsWithoutTargets });
}
/**
* If true, encounter will not animate the target Pokemon as part of battle animations
* Used for encounters where it is not a "real" battle, but still uses battle animations and commands (see {@link FunAndGamesEncounter} for an example)
* Default false
* @param skipEnemyBattleTurns
*/
withSkipEnemyBattleTurns(skipEnemyBattleTurns: boolean): this & Required<Pick<IMysteryEncounter, "skipEnemyBattleTurns">> {
return Object.assign(this, { skipEnemyBattleTurns });
}
/**
* If true, will skip COMMAND input and go straight to FIGHT (move select) input menu
* Default false
* @param skipToFightInput
*/
withSkipToFightInput(skipToFightInput: boolean): this & Required<Pick<IMysteryEncounter, "skipToFightInput">> {
return Object.assign(this, { skipToFightInput });
}
/**
* Sets the maximum number of times that an encounter can spawn in a given Classic run
* @param maxAllowedEncounters

View File

@ -28,6 +28,7 @@ import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/wei
import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter";
import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter";
import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounters/bug-type-superfan-encounter";
import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter";
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
@ -155,7 +156,8 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
const civilizationBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.DEPARTMENT_STORE_SALE,
MysteryEncounterType.PART_TIMER
MysteryEncounterType.PART_TIMER,
MysteryEncounterType.FUN_AND_GAMES
];
/**
@ -279,6 +281,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.THE_WINSTRATE_CHALLENGE] = TheWinstrateChallengeEncounter;
allMysteryEncounters[MysteryEncounterType.TELEPORTING_HIJINKS] = TeleportingHijinksEncounter;
allMysteryEncounters[MysteryEncounterType.BUG_TYPE_SUPERFAN] = BugTypeSuperfanEncounter;
allMysteryEncounters[MysteryEncounterType.FUN_AND_GAMES] = FunAndGamesEncounter;
// Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => {

View File

@ -120,7 +120,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
const partyTrainerConfig = partyConfig?.trainerConfig;
let trainerConfig: TrainerConfig;
if (!isNullOrUndefined(trainerType) || partyTrainerConfig) {
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.TRAINER_BATTLE;
scene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.TRAINER_BATTLE;
if (scene.currentBattle.trainer) {
scene.currentBattle.trainer.setVisible(false);
scene.currentBattle.trainer.destroy();
@ -141,7 +141,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex);
} else {
// Wild
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.WILD_BATTLE;
scene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.WILD_BATTLE;
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());
}
@ -184,7 +184,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemySpecies = config.species;
isBoss = config.isBoss;
if (isBoss) {
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.BOSS_BATTLE;
scene.currentBattle.mysteryEncounter!.encounterMode = MysteryEncounterMode.BOSS_BATTLE;
}
} else {
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true);
@ -429,7 +429,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
const pokemon = scene.getParty()[slotIndex];
const secondaryOptions = onPokemonSelected(pokemon);
if (!secondaryOptions) {
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
scene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
resolve(true);
return;
}
@ -443,7 +443,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
const onSelect = option.handler;
option.handler = () => {
onSelect();
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
scene.currentBattle.mysteryEncounter!.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
resolve(true);
return true;
};
@ -595,7 +595,7 @@ export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelec
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before MysteryEncounterRewardsPhase)
*/
export function setEncounterRewards(scene: BattleScene, customShopRewards?: CustomModifierSettings, eggRewards?: IEggOptions[], preRewardsCallback?: Function) {
scene.currentBattle.mysteryEncounter.doEncounterRewards = (scene: BattleScene) => {
scene.currentBattle.mysteryEncounter!.doEncounterRewards = (scene: BattleScene) => {
if (preRewardsCallback) {
preRewardsCallback();
}
@ -638,7 +638,7 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust
export function setEncounterExp(scene: BattleScene, participantId: integer | integer[], baseExpValue: number, useWaveIndex: boolean = true) {
const participantIds = Array.isArray(participantId) ? participantId : [participantId];
scene.currentBattle.mysteryEncounter.doEncounterExp = (scene: BattleScene) => {
scene.currentBattle.mysteryEncounter!.doEncounterExp = (scene: BattleScene) => {
const party = scene.getParty();
const expShareModifier = scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier;
const expBalanceModifier = scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier;
@ -650,7 +650,7 @@ export function setEncounterExp(scene: BattleScene, participantId: integer | int
let expValue = Math.floor(baseExpValue * (useWaveIndex ? scene.currentBattle.waveIndex : 1) / 5 + 1);
if (participantIds?.length > 0) {
if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (scene.currentBattle.mysteryEncounter!.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
expValue = Math.floor(expValue * 1.5);
}
for (const partyMember of nonFaintedPartyMembers) {
@ -752,7 +752,7 @@ export function initSubsequentOptionSelect(scene: BattleScene, optionSelectSetti
* @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase)
*/
export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) {
scene.currentBattle.mysteryEncounter.encounterMode = encounterMode;
scene.currentBattle.mysteryEncounter!.encounterMode = encounterMode;
scene.clearPhaseQueue();
scene.clearPhaseQueueSplice();
handleMysteryEncounterVictory(scene, addHealPhase);
@ -775,7 +775,7 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
// If in repeated encounter variant, do nothing
// Variant must eventually be swapped in order to handle "true" end of the encounter
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
if (encounter.continuousEncounter || doNotContinue) {
return;
} else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
@ -805,7 +805,7 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
*/
export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide: boolean = true, destroy: boolean = true, duration: number = 750): Promise<boolean> {
return new Promise(resolve => {
const introVisuals = scene.currentBattle.mysteryEncounter.introVisuals;
const introVisuals = scene.currentBattle.mysteryEncounter!.introVisuals;
const enemyPokemon = scene.getEnemyField();
if (enemyPokemon) {
scene.currentBattle.enemyParty = [];
@ -835,7 +835,7 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide:
scene.field.remove(pokemon, true);
});
scene.currentBattle.mysteryEncounter.introVisuals = undefined;
scene.currentBattle.mysteryEncounter!.introVisuals = undefined;
}
resolve(true);
}
@ -852,8 +852,8 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide:
* @param scene
*/
export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {
const encounter = scene.currentBattle?.mysteryEncounter;
if (scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE && !encounter.startOfBattleEffectsComplete) {
const encounter = scene.currentBattle.mysteryEncounter;
if (scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && encounter && encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE && !encounter.startOfBattleEffectsComplete) {
const effects = encounter.startOfBattleEffects;
effects.forEach(effect => {
let source;
@ -884,6 +884,21 @@ export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {
}
}
/**
* Can queue extra phases or logic during {@link TurnInitPhase}
* Mostly useful for allowing MysteryEncounter enemies to "cheat" and use moves before the first turn
* @param scene
* @return boolean - if true, will skip the remainder of the {@link TurnInitPhase}
*/
export function handleMysteryEncounterTurnStartEffects(scene: BattleScene): boolean {
const encounter = scene.currentBattle.mysteryEncounter;
if (scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && encounter && encounter.onTurnStart) {
return encounter.onTurnStart(scene);
}
return false;
}
/**
* TODO: remove once encounter spawn rate is finalized
* Just a helper function to calculate aggregate stats for MEs in a Classic run

View File

@ -25,5 +25,6 @@ export enum MysteryEncounterType {
WEIRD_DREAM,
THE_WINSTRATE_CHALLENGE,
TELEPORTING_HIJINKS,
BUG_TYPE_SUPERFAN
BUG_TYPE_SUPERFAN,
FUN_AND_GAMES
}

View File

@ -25,6 +25,7 @@ import weirdDreamDialogue from "#app/locales/en/mystery-encounters/weird-dream-d
import theWinstrateChallengeDialogue from "#app/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json";
import teleportingHijinksDialogue from "#app/locales/en/mystery-encounters/teleporting-hijinks-dialogue.json";
import bugTypeSuperfanDialogue from "#app/locales/en/mystery-encounters/bug-type-superfan-dialogue.json";
import funAndGamesDialogue from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json";
/**
* Injection patterns that can be used:
@ -71,5 +72,6 @@ export const mysteryEncounter = {
weirdDream: weirdDreamDialogue,
theWinstrateChallenge: theWinstrateChallengeDialogue,
teleportingHijinks: teleportingHijinksDialogue,
bugTypeSuperfan: bugTypeSuperfanDialogue
bugTypeSuperfan: bugTypeSuperfanDialogue,
funAndGames: funAndGamesDialogue
} as const;

View File

@ -0,0 +1,30 @@
{
"intro_dialogue": "Step right up, folks! Try your luck\non the brand new Wobbuffet Whack-o-matic!",
"speaker": "Showman",
"title": "Fun And Games!",
"description": "You've encountered a traveling show with a prize game! You will have @[TOOLTIP_TITLE]{3 turns} to bring the Wobbuffet as close to @[TOOLTIP_TITLE]{1 HP} as possible @[TOOLTIP_TITLE]{without KOing it} so it can wind up a huge Counter on the bell-ringing machine.\nBut be careful! If you KO the Wobbuffet, you'll have to pay for the cost of reviving it!",
"query": "Would you like to play?",
"option": {
"1": {
"label": "Play the Game",
"tooltip": "(-) Pay {{option1Money, money}}\n(+) Play Wobbuffet Whack-o-matic",
"selected": "Time to test your luck!"
},
"2": {
"label": "Leave",
"tooltip": "(-) No Rewards",
"selected": "You hurry along your way,\nwith a slight feeling of regret."
}
},
"ko": "Oh no! The Wobbuffet fainted!$You lose the game and\nhave to pay for the revive cost...",
"charging_continue": "The Wubboffet keeps charging its counter-swing!",
"turn_remaining_3": "Three turns remaining!",
"turn_remaining_2": "Two turns remaining!",
"turn_remaining_1": "One turn remaining!",
"end_game": "Time's up!$The Wobbuffet winds up to counter-swing and@d{16}.@d{16}.@d{16}.",
"best_result": "The Wobbuffet smacks the button so hard\nthe bell breaks off the top!$You win the grand prize!",
"great_result": "The Wobbuffet smacks the button, nearly hitting the bell!$So close!\nYou earn the second tier prize!",
"good_result": "The Wobbuffet hits the button hard enough to go midway up the scale!$You earn the third tier prize!",
"bad_result": "The Wobbuffet barely taps the button and nothing happens...$Oh no!\nYou don't win anything!",
"outro": "That was a fun little game!"
}

View File

@ -72,7 +72,12 @@ export class CommandPhase extends FieldPhase {
}
}
} else {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && this.scene.currentBattle.mysteryEncounter?.skipToFightInput) {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex);
} else {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}
}
}
@ -138,7 +143,7 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && !this.scene.currentBattle.mysteryEncounter.catchAllowed) {
} else if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => {

View File

@ -150,7 +150,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");
@ -349,12 +349,13 @@ export class EncounterPhase extends BattlePhase {
showDialogueAndSummon();
}
}
} else if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER) {
const introVisuals = this.scene.currentBattle.mysteryEncounter.introVisuals!;
} else if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && this.scene.currentBattle.mysteryEncounter) {
const encounter = this.scene.currentBattle.mysteryEncounter;
const introVisuals = encounter.introVisuals!;
introVisuals.playAnim();
if (this.scene.currentBattle.mysteryEncounter.onVisualsStart) {
this.scene.currentBattle.mysteryEncounter.onVisualsStart(this.scene);
if (encounter.onVisualsStart) {
encounter.onVisualsStart(this.scene);
}
const doEncounter = () => {
@ -367,7 +368,7 @@ export class EncounterPhase extends BattlePhase {
};
if (showEncounterMessage) {
const introDialogue = this.scene.currentBattle.mysteryEncounter.dialogue.intro;
const introDialogue = encounter.dialogue.intro;
if (!introDialogue) {
doShowEncounterOptions();
} else {

View File

@ -17,11 +17,15 @@ import { FieldPhase } from "./field-phase";
*/
export class EnemyCommandPhase extends FieldPhase {
protected fieldIndex: integer;
protected skipTurn: boolean = false;
constructor(scene: BattleScene, fieldIndex: integer) {
super(scene);
this.fieldIndex = fieldIndex;
if (this.scene.currentBattle.mysteryEncounter?.skipEnemyBattleTurns) {
this.skipTurn = true;
}
}
start() {
@ -63,7 +67,7 @@ export class EnemyCommandPhase extends FieldPhase {
const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores);
battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
{ command: Command.POKEMON, cursor: index, args: [false] };
{ command: Command.POKEMON, cursor: index, args: [false], skip: this.skipTurn };
battle.enemySwitchCounter++;
@ -77,7 +81,7 @@ export class EnemyCommandPhase extends FieldPhase {
const nextMove = enemyPokemon.getNextMove();
this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
{ command: Command.FIGHT, move: nextMove };
{ command: Command.FIGHT, move: nextMove, skip: this.skipTurn };
this.scene.currentBattle.enemySwitchCounter = Math.max(this.scene.currentBattle.enemySwitchCounter - 1, 0);

View File

@ -119,8 +119,9 @@ export class MoveEffectPhase extends PokemonPhase {
/** All move effect attributes are chained together in this array to be applied asynchronously. */
const applyAttrs: Promise<void>[] = [];
const playOnEmptyField = this.scene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
// Move animation only needs one target
new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()!).play(this.scene, () => { // TODO: is the bang correct here?
new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()!, playOnEmptyField).play(this.scene, () => { // TODO: is the bang correct here?
/** Has the move successfully hit a target (for damage) yet? */
let hasHit: boolean = false;
for (const target of targets) {

View File

@ -54,12 +54,13 @@ export class MysteryEncounterPhase extends Phase {
this.scene.clearPhaseQueue();
this.scene.clearPhaseQueueSplice();
this.scene.currentBattle.mysteryEncounter.updateSeedOffset(this.scene);
const encounter = this.scene.currentBattle.mysteryEncounter!;
encounter.updateSeedOffset(this.scene);
if (!this.optionSelectSettings) {
// Sets flag that ME was encountered, only if this is not a followup option select phase
// Can be used in later MEs to check for requirements to spawn, etc.
this.scene.mysteryEncounterData.encounteredEvents.push([this.scene.currentBattle.mysteryEncounter.encounterType, this.scene.currentBattle.mysteryEncounter.encounterTier]);
this.scene.mysteryEncounterData.encounteredEvents.push([encounter.encounterType, encounter.encounterTier]);
}
// Initiates encounter dialogue window and option select
@ -68,14 +69,14 @@ export class MysteryEncounterPhase extends Phase {
handleOptionSelect(option: MysteryEncounterOption, index: number): boolean {
// Set option selected flag
this.scene.currentBattle.mysteryEncounter.selectedOption = option;
this.scene.currentBattle.mysteryEncounter!.selectedOption = option;
if (!option.onOptionPhase) {
return false;
}
// Populate dialogue tokens for option requirements
this.scene.currentBattle.mysteryEncounter.populateDialogueTokensFromRequirements(this.scene);
this.scene.currentBattle.mysteryEncounter!.populateDialogueTokensFromRequirements(this.scene);
if (option.onPreOptionPhase) {
this.scene.executeWithSeedOffset(async () => {
@ -85,7 +86,7 @@ export class MysteryEncounterPhase extends Phase {
this.continueEncounter();
}
});
}, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset());
} else {
this.continueEncounter();
}
@ -149,25 +150,25 @@ 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() {
super.start();
if (this.scene.currentBattle.mysteryEncounter.autoHideIntroVisuals) {
if (this.scene.currentBattle.mysteryEncounter?.autoHideIntroVisuals) {
transitionMysteryEncounterIntroVisuals(this.scene).then(() => {
this.scene.executeWithSeedOffset(() => {
this.onOptionSelect(this.scene).finally(() => {
this.end();
});
}, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset());
});
} else {
this.scene.executeWithSeedOffset(() => {
this.onOptionSelect(this.scene).finally(() => {
this.end();
});
}, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset());
}
}
}
@ -222,7 +223,7 @@ export class MysteryEncounterBattlePhase extends Phase {
getBattleMessage(scene: BattleScene): string {
const enemyField = scene.getEnemyField();
const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode;
if (scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
return i18next.t("battle:bossAppeared", { bossName: enemyField[0].name });
@ -243,7 +244,7 @@ export class MysteryEncounterBattlePhase extends Phase {
}
doMysteryEncounterBattle(scene: BattleScene) {
const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode;
if (encounterMode === MysteryEncounterMode.WILD_BATTLE || encounterMode === MysteryEncounterMode.BOSS_BATTLE) {
// Summons the wild/boss Pokemon
if (encounterMode === MysteryEncounterMode.BOSS_BATTLE) {
@ -255,7 +256,7 @@ export class MysteryEncounterBattlePhase extends Phase {
scene.unshiftPhase(new SummonPhase(scene, 1, false));
}
if (!scene.currentBattle.mysteryEncounter.hideBattleIntroMessage) {
if (!scene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) {
scene.ui.showText(this.getBattleMessage(scene), null, () => this.endBattleSetup(scene), 0);
} else {
this.endBattleSetup(scene);
@ -276,7 +277,7 @@ export class MysteryEncounterBattlePhase extends Phase {
}
this.endBattleSetup(scene);
};
if (!scene.currentBattle.mysteryEncounter.hideBattleIntroMessage) {
if (!scene.currentBattle.mysteryEncounter?.hideBattleIntroMessage) {
scene.ui.showText(this.getBattleMessage(scene), null, doTrainerSummon, 1000, true);
} else {
doTrainerSummon();
@ -290,7 +291,7 @@ export class MysteryEncounterBattlePhase extends Phase {
} else {
const trainer = this.scene.currentBattle.trainer;
let message: string;
scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.mysteryEncounter.getSeedOffset());
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, () => {
@ -308,7 +309,7 @@ export class MysteryEncounterBattlePhase extends Phase {
endBattleSetup(scene: BattleScene) {
const enemyField = scene.getEnemyField();
const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
const encounterMode = scene.currentBattle.mysteryEncounter!.encounterMode;
// PostSummon and ShinySparkle phases are handled by SummonPhase
@ -410,7 +411,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
start() {
super.start();
const encounter = this.scene.currentBattle.mysteryEncounter;
const encounter = this.scene.currentBattle.mysteryEncounter!;
if (encounter.doContinueEncounter) {
encounter.doContinueEncounter(this.scene).then(() => {
@ -431,7 +432,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
}
doEncounterRewardsAndContinue() {
const encounter = this.scene.currentBattle.mysteryEncounter;
const encounter = this.scene.currentBattle.mysteryEncounter!;
if (encounter.doEncounterExp) {
encounter.doEncounterExp(this.scene);
@ -462,7 +463,7 @@ export class PostMysteryEncounterPhase extends Phase {
constructor(scene: BattleScene) {
super(scene);
this.onPostOptionSelect = this.scene.currentBattle.mysteryEncounter.selectedOption?.onPostOptionPhase;
this.onPostOptionSelect = this.scene.currentBattle.mysteryEncounter?.selectedOption?.onPostOptionPhase;
}
start() {
@ -476,7 +477,7 @@ export class PostMysteryEncounterPhase extends Phase {
this.continueEncounter();
}
});
}, this.scene.currentBattle.mysteryEncounter.getSeedOffset());
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset());
} else {
this.continueEncounter();
}

View File

@ -24,11 +24,11 @@ export class NextEncounterPhase extends EncounterPhase {
const enemyField = this.scene.getEnemyField();
const moveTargets: any[] = [this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer];
const lastEncounterVisuals = this.scene?.lastMysteryEncounter?.introVisuals;
const lastEncounterVisuals = this.scene.lastMysteryEncounter?.introVisuals;
if (lastEncounterVisuals) {
moveTargets.push(lastEncounterVisuals);
}
const nextEncounterVisuals = this.scene.currentBattle?.mysteryEncounter?.introVisuals;
const nextEncounterVisuals = this.scene.currentBattle.mysteryEncounter?.introVisuals;
if (nextEncounterVisuals) {
const enterFromRight = nextEncounterVisuals.enterFromRight;
if (enterFromRight) {
@ -58,7 +58,7 @@ export class NextEncounterPhase extends EncounterPhase {
}
if (lastEncounterVisuals) {
this.scene.field.remove(lastEncounterVisuals, true);
this.scene.lastMysteryEncounter.introVisuals = undefined;
this.scene.lastMysteryEncounter!.introVisuals = undefined;
}
if (!this.tryOverrideForBattleSpec()) {

View File

@ -9,7 +9,7 @@ import { CommandPhase } from "./command-phase";
import { EnemyCommandPhase } from "./enemy-command-phase";
import { GameOverPhase } from "./game-over-phase";
import { TurnStartPhase } from "./turn-start-phase";
import { handleMysteryEncounterBattleStartEffects } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { handleMysteryEncounterBattleStartEffects, handleMysteryEncounterTurnStartEffects } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
export class TurnInitPhase extends FieldPhase {
constructor(scene: BattleScene) {
@ -49,6 +49,12 @@ export class TurnInitPhase extends FieldPhase {
handleMysteryEncounterBattleStartEffects(this.scene);
// If true, will skip remainder of current phase (and not queue CommandPhases etc.)
if (handleMysteryEncounterTurnStartEffects(this.scene)) {
this.end();
return;
}
this.scene.getField().forEach((pokemon, i) => {
if (pokemon?.isActive()) {
if (pokemon.isPlayer()) {

View File

@ -46,7 +46,7 @@ export class VictoryPhase extends PokemonPhase {
let expValue = this.getPokemon().getExpValue();
if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
expValue = Math.floor(expValue * 1.5);
} else if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER) {
} else if (this.scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && this.scene.currentBattle.mysteryEncounter) {
expValue = Math.floor(expValue * this.scene.currentBattle.mysteryEncounter.expMultiplier);
}
for (const partyMember of nonFaintedPartyMembers) {

View File

@ -140,7 +140,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
const price = scene.currentBattle.mysteryEncounter.misc.price;
const price = scene.currentBattle.mysteryEncounter!.misc.price;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, price);
expect(scene.money).toBe(initialMoney + price);
@ -160,7 +160,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
const initialPartySize = scene.getParty().length;
const pokemonName = scene.currentBattle.mysteryEncounter.misc.pokemon.name;
const pokemonName = scene.currentBattle.mysteryEncounter!.misc.pokemon.name;
await runMysteryEncounterToEnd(game, 1);
@ -227,7 +227,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
const price = scene.currentBattle.mysteryEncounter.misc.price;
const price = scene.currentBattle.mysteryEncounter!.misc.price;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, price);
expect(scene.money).toBe(initialMoney + price);

View File

@ -119,7 +119,7 @@ describe("Berries Abound - Mystery Encounter", () => {
it("should start a fight against the boss", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
await runMysteryEncounterToEnd(game, 1, undefined, true);
@ -133,7 +133,7 @@ describe("Berries Abound - Mystery Encounter", () => {
it("should reward the player with X berries based on wave", { retry: 5 }, async () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const numBerries = game.scene.currentBattle.mysteryEncounter.misc.numBerries;
const numBerries = game.scene.currentBattle.mysteryEncounter!.misc.numBerries;
scene.modifiers = [];
await runMysteryEncounterToEnd(game, 1, undefined, true);
@ -179,7 +179,7 @@ describe("Berries Abound - Mystery Encounter", () => {
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
// Setting enemy's level arbitrarily high to outspeed
config.pokemonConfigs![0].dataSource!.level = 1000;

View File

@ -199,7 +199,7 @@ describe("Clowning Around - Mystery Encounter", () => {
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
const abilityToTrain = scene.currentBattle.mysteryEncounter.misc.ability;
const abilityToTrain = scene.currentBattle.mysteryEncounter?.misc.ability;
game.onNextPrompt("PostMysteryEncounterPhase", Mode.MESSAGE, () => {
game.scene.ui.getHandler().processInput(Button.ACTION);

View File

@ -114,7 +114,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
await runMysteryEncounterToEnd(game, 1);
const price = (scene.currentBattle.mysteryEncounter.options[0].requirements[0] as MoneyRequirement).requiredMoney;
const price = (scene.currentBattle.mysteryEncounter?.options[0].requirements[0] as MoneyRequirement).requiredMoney;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, -price, true, false);
expect(scene.money).toBe(initialMoney - price);

View File

@ -213,7 +213,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE));
const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE));
expect(scene.currentBattle.mysteryEncounter.dialogueTokens["burnedPokemon"]).toBe("Gengar");
expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe("Gengar");
burnablePokemon.forEach((pkm) => {
expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2));
});

View File

@ -120,7 +120,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
it("should start a fight against the boss", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
await runMysteryEncounterToEnd(game, 1, undefined, true);
@ -134,7 +134,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
it("should reward the player with the item based on wave", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
const item = game.scene.currentBattle.mysteryEncounter.misc;
const item = game.scene.currentBattle.mysteryEncounter?.misc;
await runMysteryEncounterToEnd(game, 1, undefined, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
@ -193,7 +193,7 @@ describe("Fight or Flight - Mystery Encounter", () => {
// Mock moveset
scene.getParty()[0].moveset = [new PokemonMove(Moves.KNOCK_OFF)];
const item = game.scene.currentBattle.mysteryEncounter.misc;
const item = game.scene.currentBattle.mysteryEncounter!.misc;
await runMysteryEncounterToEnd(game, 2);
await game.phaseInterceptor.to(SelectModifierPhase, false);

View File

@ -0,0 +1,304 @@
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
import { Biome } from "#app/enums/biome";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils";
import BattleScene from "#app/battle-scene";
import { Mode } from "#app/ui/ui";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { Nature } from "#enums/nature";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { CommandPhase } from "#app/phases/command-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter";
import { Moves } from "#enums/moves";
import { Command } from "#app/ui/command-ui-handler";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
const namespace = "mysteryEncounter:funAndGames";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.CAVE;
const defaultWave = 45;
describe("Fun And Games! - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves();
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]],
]);
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
biomeMap.set(biome, [MysteryEncounterType.FUN_AND_GAMES]);
});
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
expect(FunAndGamesEncounter.encounterType).toBe(MysteryEncounterType.FUN_AND_GAMES);
expect(FunAndGamesEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
expect(FunAndGamesEncounter.dialogue).toBeDefined();
expect(FunAndGamesEncounter.dialogue.intro).toStrictEqual([
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
}
]);
expect(FunAndGamesEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
expect(FunAndGamesEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
expect(FunAndGamesEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
expect(FunAndGamesEncounter.options.length).toBe(2);
});
it("should not spawn outside of CIVILIZATIONN biomes", async () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
game.override.startingBiome(Biome.VOLCANO);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FUN_AND_GAMES);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FUN_AND_GAMES);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(FunAndGamesEncounter);
const encounter = scene.currentBattle.mysteryEncounter!;
scene.currentBattle.waveIndex = defaultWave;
const { onInit } = encounter;
expect(encounter.onInit).toBeDefined();
const onInitResult = onInit!(scene);
expect(onInitResult).toBe(true);
});
describe("Option 1 - Play the Wobbuffet game", () => {
it("should have the correct properties", () => {
const option = FunAndGamesEncounter.options[0];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
text: `${namespace}.option.1.selected`,
},
],
});
});
it("should NOT be selectable if the player doesn't have enough money", async () => {
game.scene.money = 0;
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
vi.spyOn(scene.ui, "playError");
await runSelectMysteryEncounterOption(game, 1);
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
});
it("should get 3 turns to attack the Wobbuffet for a reward", async () => {
scene.money = 20000;
game.override.moveset([Moves.TACKLE]);
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(scene.getEnemyPokemon()?.species.speciesId).toBe(Species.WOBBUFFET);
expect(scene.getEnemyPokemon()?.ivs).toEqual([0, 0, 0, 0, 0, 0]);
expect(scene.getEnemyPokemon()?.nature).toBe(Nature.MILD);
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
game.endPhase();
});
// Turn 1
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false);
await game.phaseInterceptor.to(CommandPhase);
// Turn 2
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false);
await game.phaseInterceptor.to(CommandPhase);
// Turn 3
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false);
await game.phaseInterceptor.to(SelectModifierPhase, false);
// Rewards
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
});
it("should have no items in rewards if Wubboffet doesn't take enough damage", async () => {
scene.money = 20000;
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
game.endPhase();
});
// Skip minigame
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false);
await game.phaseInterceptor.to(SelectModifierPhase, false);
// Rewards
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(0);
});
it("should have Wide Lens item in rewards if Wubboffet is at 15-33% HP remaining", async () => {
scene.money = 20000;
game.override.moveset([Moves.SPLASH]);
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
game.endPhase();
});
// Skip minigame
const wobbuffet = scene.getEnemyPokemon()!;
wobbuffet.hp = Math.floor(0.2 * wobbuffet.getMaxHp());
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false);
await game.phaseInterceptor.to(SelectModifierPhase, false);
// Rewards
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(1);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("WIDE_LENS");
});
it("should have Scope Lens item in rewards if Wubboffet is at 3-15% HP remaining", async () => {
scene.money = 20000;
game.override.moveset([Moves.SPLASH]);
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
game.endPhase();
});
// Skip minigame
const wobbuffet = scene.getEnemyPokemon()!;
wobbuffet.hp = Math.floor(0.1 * wobbuffet.getMaxHp());
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false);
await game.phaseInterceptor.to(SelectModifierPhase, false);
// Rewards
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(1);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("SCOPE_LENS");
});
it("should have Multi Lens item in rewards if Wubboffet is at <3% HP remaining", async () => {
scene.money = 20000;
game.override.moveset([Moves.SPLASH]);
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 1, { pokemonNo: 1 }, true);
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
game.endPhase();
});
// Skip minigame
const wobbuffet = scene.getEnemyPokemon()!;
wobbuffet.hp = 1;
scene.currentBattle.mysteryEncounter!.misc.turnsRemaining = 0;
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, 0, false);
await game.phaseInterceptor.to(SelectModifierPhase, false);
// Rewards
expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(1);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MULTI_LENS");
});
});
describe("Option 2 - Leave", () => {
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.FUN_AND_GAMES, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
});

View File

@ -70,7 +70,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
game.override.startingBiome(Biome.MOUNTAIN);
await game.runToMysteryEncounter();
expect(game.scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA);
expect(game.scene.currentBattle.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.LOST_AT_SEA);
});
it("should not run below wave 11", async () => {

View File

@ -98,7 +98,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(MysteriousChallengersEncounter);
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
scene.currentBattle.waveIndex = defaultWave;
const { onInit } = encounter;
@ -162,7 +162,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
});
it("should have normal trainer rewards after battle", async () => {
@ -204,7 +204,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
});
it("should have hard trainer rewards after battle", async () => {
@ -247,7 +247,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
});
it("should have brutal trainer rewards after battle", async () => {

View File

@ -142,7 +142,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
await runMysteryEncounterToEnd(game, 1);
const price = scene.currentBattle.mysteryEncounter.misc.price;
const price = scene.currentBattle.mysteryEncounter!.misc.price;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, -price, true, false);
expect(scene.money).toBe(initialMoney - price);
@ -153,7 +153,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
const initialPartySize = scene.getParty().length;
const pokemonName = scene.currentBattle.mysteryEncounter.misc.pokemon.name;
const pokemonName = scene.currentBattle.mysteryEncounter!.misc.pokemon.name;
await runMysteryEncounterToEnd(game, 1);

View File

@ -109,7 +109,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(TheWinstrateChallengeEncounter);
const encounter = scene.currentBattle.mysteryEncounter;
const encounter = scene.currentBattle.mysteryEncounter!;
scene.currentBattle.waveIndex = defaultWave;
const { onInit } = encounter;
@ -281,32 +281,32 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
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.mysteryEncounter.enemyPartyConfigs.length).toBe(4);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
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.mysteryEncounter.enemyPartyConfigs.length).toBe(3);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
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.mysteryEncounter.enemyPartyConfigs.length).toBe(2);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
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.mysteryEncounter.enemyPartyConfigs.length).toBe(1);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
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.mysteryEncounter.enemyPartyConfigs.length).toBe(0);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
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);

View File

@ -286,7 +286,7 @@ describe("Mystery Encounter Utils", () => {
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene.ui, "showText");
showEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
await showEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
expect(spy).toHaveBeenCalledWith("valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", null, expect.any(Function), 0, true);
});
});
@ -297,7 +297,7 @@ describe("Mystery Encounter Utils", () => {
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene.ui, "showDialogue");
showEncounterDialogue(scene, "mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue");
await showEncounterDialogue(scene, "mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue");
expect(spy).toHaveBeenCalledWith("valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", "valuevalue {{testvalue}} {{test1}} {{test}} {{test\\}} {{test\\}} {test}}", null, expect.any(Function), 0);
});
});

View File

@ -10,6 +10,7 @@ import i18next from "i18next";
import {Button} from "#enums/buttons";
import Pokemon, { PokemonMove } from "#app/field/pokemon.js";
import { CommandPhase } from "#app/phases/command-phase.js";
import { BattleType } from "#app/battle";
export default class FightUiHandler extends UiHandler {
public static readonly MOVES_CONTAINER_NAME = "moves";
@ -116,8 +117,11 @@ export default class FightUiHandler extends UiHandler {
ui.playError();
}
} else {
ui.setMode(Mode.COMMAND, this.fieldIndex);
success = true;
// Cannot back out of fight menu if skipToFightInput is enabled
if (this.scene.currentBattle.battleType !== BattleType.MYSTERY_ENCOUNTER || !this.scene.currentBattle.mysteryEncounter?.skipToFightInput) {
ui.setMode(Mode.COMMAND, this.fieldIndex);
success = true;
}
}
} else {
switch (button) {

View File

@ -326,7 +326,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
displayEncounterOptions(slideInDescription: boolean = true): void {
this.getUi().clearText();
const mysteryEncounter = this.scene.currentBattle.mysteryEncounter;
const mysteryEncounter = this.scene.currentBattle.mysteryEncounter!;
this.encounterOptions = this.overrideSettings?.overrideOptions ?? mysteryEncounter.options;
this.optionsMeetsReqs = [];