mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-20 16:42:45 +02:00
Compare commits
5 Commits
25e370e029
...
f34defb729
Author | SHA1 | Date | |
---|---|---|---|
|
f34defb729 | ||
|
656a21882e | ||
|
50816b41f5 | ||
|
f0d6b88a17 | ||
|
821e380e57 |
41
public/images/trainer/sky_trainer_f.json
Normal file
41
public/images/trainer/sky_trainer_f.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "sky_trainer_f.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 56,
|
||||
"h": 67
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 56,
|
||||
"h": 67
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 56,
|
||||
"h": 67
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 56,
|
||||
"h": 67
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:d051332055512f245ae68e912a28cd81:4926ba3fd9fe432bb535d13f4df1df4e:621aca0914900a3b9e235ebf0431ce2b$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/sky_trainer_f.png
Normal file
BIN
public/images/trainer/sky_trainer_f.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
41
public/images/trainer/sky_trainer_m.json
Normal file
41
public/images/trainer/sky_trainer_m.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "sky_trainer_m.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 48,
|
||||
"h": 79
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 48,
|
||||
"h": 79
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 48,
|
||||
"h": 79
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 48,
|
||||
"h": 79
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:d051332055512f245ae68e912a28cd81:4926ba3fd9fe432bb535d13f4df1df4e:621aca0914900a3b9e235ebf0431ce2b$"
|
||||
}
|
||||
}
|
BIN
public/images/trainer/sky_trainer_m.png
Normal file
BIN
public/images/trainer/sky_trainer_m.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1006 B |
589
src/data/mystery-encounters/encounters/sky-battle-encounter.ts
Normal file
589
src/data/mystery-encounters/encounters/sky-battle-encounter.ts
Normal file
@ -0,0 +1,589 @@
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { globalScene } from "#app/global-scene";
|
||||
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
type EnemyPartyConfig,
|
||||
initBattleWithEnemyConfig,
|
||||
leaveEncounterWithoutBattle,
|
||||
selectOptionThenPokemon,
|
||||
setEncounterRewards,
|
||||
transitionMysteryEncounterIntroVisuals,
|
||||
} from "../utils/encounter-phase-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import {
|
||||
AbilityRequirement,
|
||||
AnyCombinationPokemonRequirement,
|
||||
TypeRequirement,
|
||||
} from "../mystery-encounter-requirements";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { randSeedInt, randSeedShuffle } from "#app/utils/common";
|
||||
import type { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import i18next from "i18next";
|
||||
import MoveInfoOverlay from "#app/ui/move-info-overlay";
|
||||
import { showEncounterDialogue } from "../utils/encounter-dialogue-utils";
|
||||
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import { allMoves, modifierTypes } from "#app/data/data-lists";
|
||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
|
||||
import { getRandomPartyMemberFunc, type TrainerConfig, trainerConfigs } from "#app/data/trainers/trainer-config";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import { TrainerSlot } from "#enums/trainer-slot";
|
||||
|
||||
/** The i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/skyBattle";
|
||||
|
||||
const SKY_BATTLE_WAVES: [number, number] = [50, 180];
|
||||
|
||||
/**
|
||||
* These pokemon come from serebii's
|
||||
* {@link https://www.serebii.net/xy/skybattles.shtml | Sky Battle Page}
|
||||
* Also pokemon that are expected to fly (e.g beedril and mew)
|
||||
*/
|
||||
const POOL_0_POKEMON = [
|
||||
SpeciesId.CHARIZARD,
|
||||
SpeciesId.BUTTERFREE,
|
||||
SpeciesId.BEEDRILL,
|
||||
SpeciesId.PIDGEOTTO,
|
||||
SpeciesId.PIDGEOT,
|
||||
SpeciesId.FEAROW,
|
||||
SpeciesId.ZUBAT,
|
||||
SpeciesId.GOLBAT,
|
||||
SpeciesId.VENOMOTH,
|
||||
SpeciesId.HAUNTER,
|
||||
SpeciesId.KOFFING,
|
||||
SpeciesId.WEEZING,
|
||||
SpeciesId.SCYTHER,
|
||||
SpeciesId.GYARADOS,
|
||||
SpeciesId.AERODACTYL,
|
||||
SpeciesId.ARTICUNO,
|
||||
SpeciesId.ZAPDOS,
|
||||
SpeciesId.MOLTRES,
|
||||
SpeciesId.DRAGONITE,
|
||||
SpeciesId.MEWTWO, // ?
|
||||
SpeciesId.MEW,
|
||||
SpeciesId.NOCTOWL,
|
||||
SpeciesId.LEDYBA,
|
||||
SpeciesId.LEDIAN,
|
||||
SpeciesId.CROBAT,
|
||||
SpeciesId.TOGETIC,
|
||||
SpeciesId.XATU,
|
||||
SpeciesId.HOPPIP,
|
||||
SpeciesId.SKIPLOOM,
|
||||
SpeciesId.JUMPLUFF,
|
||||
SpeciesId.YANMA,
|
||||
SpeciesId.MISDREAVUS,
|
||||
SpeciesId.UNOWN,
|
||||
SpeciesId.FORRETRESS, // ?
|
||||
SpeciesId.GLIGAR,
|
||||
SpeciesId.MANTINE,
|
||||
SpeciesId.SKARMORY,
|
||||
SpeciesId.LUGIA,
|
||||
SpeciesId.HO_OH,
|
||||
SpeciesId.CELEBI,
|
||||
SpeciesId.BEAUTIFLY,
|
||||
SpeciesId.DUSTOX,
|
||||
SpeciesId.SWELLOW,
|
||||
SpeciesId.WINGULL,
|
||||
SpeciesId.PELIPPER,
|
||||
SpeciesId.MASQUERAIN,
|
||||
SpeciesId.NINJASK,
|
||||
SpeciesId.SHEDINJA, // ?
|
||||
SpeciesId.VIBRAVA,
|
||||
SpeciesId.FLYGON,
|
||||
SpeciesId.SWABLU,
|
||||
SpeciesId.ALTARIA,
|
||||
SpeciesId.LUNATONE,
|
||||
SpeciesId.SOLROCK,
|
||||
SpeciesId.BALTOY,
|
||||
SpeciesId.CLAYDOL,
|
||||
SpeciesId.DUSKULL,
|
||||
SpeciesId.TROPIUS,
|
||||
SpeciesId.CHIMECHO,
|
||||
SpeciesId.GLALIE, // ?
|
||||
SpeciesId.SALAMENCE,
|
||||
SpeciesId.METANG,
|
||||
SpeciesId.METAGROSS, // ?
|
||||
SpeciesId.LATIAS,
|
||||
SpeciesId.LATIOS,
|
||||
SpeciesId.RAYQUAZA,
|
||||
SpeciesId.JIRACHI,
|
||||
SpeciesId.STARAVIA,
|
||||
SpeciesId.STARAPTOR,
|
||||
SpeciesId.MOTHIM,
|
||||
SpeciesId.COMBEE,
|
||||
SpeciesId.VESPIQUEN,
|
||||
SpeciesId.DRIFLOON,
|
||||
SpeciesId.DRIFBLIM,
|
||||
SpeciesId.MISMAGIUS,
|
||||
SpeciesId.HONCHKROW,
|
||||
SpeciesId.CHINGLING,
|
||||
SpeciesId.BRONZOR,
|
||||
SpeciesId.BRONZONG,
|
||||
SpeciesId.CARNIVINE,
|
||||
SpeciesId.MANTYKE,
|
||||
SpeciesId.MAGNEZONE, // ?
|
||||
SpeciesId.TOGEKISS,
|
||||
SpeciesId.YANMEGA,
|
||||
SpeciesId.GLISCOR,
|
||||
SpeciesId.DUSKNOIR, // ?
|
||||
SpeciesId.ROTOM,
|
||||
SpeciesId.UXIE,
|
||||
SpeciesId.MESPRIT,
|
||||
SpeciesId.AZELF,
|
||||
SpeciesId.GIRATINA,
|
||||
SpeciesId.CRESSELIA,
|
||||
SpeciesId.ARCEUS,
|
||||
SpeciesId.TRANQUILL,
|
||||
SpeciesId.UNFEZANT,
|
||||
SpeciesId.WOOBAT,
|
||||
SpeciesId.SWOOBAT,
|
||||
SpeciesId.SIGILYPH,
|
||||
SpeciesId.ARCHEOPS,
|
||||
SpeciesId.SOLOSIS,
|
||||
SpeciesId.DUOSION,
|
||||
SpeciesId.REUNICLUS,
|
||||
SpeciesId.SWANNA,
|
||||
SpeciesId.VANILLISH,
|
||||
SpeciesId.VANILLUXE,
|
||||
SpeciesId.EMOLGA,
|
||||
SpeciesId.TYNAMO,
|
||||
SpeciesId.EELEKTRIK,
|
||||
SpeciesId.EELEKTROSS,
|
||||
SpeciesId.LAMPENT,
|
||||
SpeciesId.CHANDELURE,
|
||||
SpeciesId.CRYOGONAL,
|
||||
SpeciesId.BRAVIARY,
|
||||
SpeciesId.MANDIBUZZ,
|
||||
SpeciesId.HYDREIGON,
|
||||
SpeciesId.VOLCARONA,
|
||||
SpeciesId.TORNADUS,
|
||||
SpeciesId.THUNDURUS,
|
||||
SpeciesId.RESHIRAM,
|
||||
SpeciesId.ZEKROM,
|
||||
SpeciesId.LANDORUS,
|
||||
SpeciesId.FLETCHINDER,
|
||||
SpeciesId.TALONFLAME,
|
||||
SpeciesId.VIVILLON,
|
||||
SpeciesId.FLOETTE,
|
||||
SpeciesId.FLORGES,
|
||||
SpeciesId.HAWLUCHA, // ?
|
||||
SpeciesId.NOIBAT,
|
||||
SpeciesId.NOIVERN,
|
||||
SpeciesId.YVELTAL,
|
||||
SpeciesId.DARTRIX,
|
||||
SpeciesId.DECIDUEYE, //?
|
||||
SpeciesId.TRUMBEAK,
|
||||
SpeciesId.TOUCANNON,
|
||||
SpeciesId.VIKAVOLT,
|
||||
SpeciesId.ORICORIO,
|
||||
SpeciesId.RIBOMBEE,
|
||||
SpeciesId.COMFEY, //?
|
||||
SpeciesId.MINIOR,
|
||||
SpeciesId.TAPU_KOKO,
|
||||
SpeciesId.TAPU_LELE,
|
||||
SpeciesId.TAPU_BULU,
|
||||
SpeciesId.TAPU_FINI,
|
||||
SpeciesId.LUNALA,
|
||||
SpeciesId.NIHILEGO,
|
||||
SpeciesId.BUZZWOLE,
|
||||
SpeciesId.CELESTEELA,
|
||||
SpeciesId.NECROZMA,
|
||||
SpeciesId.POIPOLE,
|
||||
SpeciesId.NAGANADEL,
|
||||
SpeciesId.CORVISQUIRE,
|
||||
SpeciesId.CORVIKNIGHT,
|
||||
SpeciesId.ORBEETLE,
|
||||
SpeciesId.FLAPPLE,
|
||||
SpeciesId.CRAMORANT,
|
||||
SpeciesId.FROSMOTH,
|
||||
SpeciesId.DRAKLOAK,
|
||||
SpeciesId.DRAGAPULT,
|
||||
SpeciesId.ETERNATUS,
|
||||
SpeciesId.ENAMORUS,
|
||||
SpeciesId.SQUAWKABILLY,
|
||||
SpeciesId.WATTREL,
|
||||
SpeciesId.KILOWATTREL,
|
||||
SpeciesId.BOMBIRDIER,
|
||||
SpeciesId.FLAMIGO,
|
||||
SpeciesId.FLUTTER_MANE,
|
||||
SpeciesId.IRON_JUGULIS,
|
||||
SpeciesId.ROARING_MOON,
|
||||
SpeciesId.MIRAIDON,
|
||||
SpeciesId.KORAIDON,
|
||||
];
|
||||
|
||||
const PHYSICAL_TUTOR_MOVES = [
|
||||
MoveId.FLY,
|
||||
MoveId.BRAVE_BIRD,
|
||||
MoveId.ACROBATICS,
|
||||
MoveId.DRAGON_ASCENT,
|
||||
MoveId.BEAK_BLAST,
|
||||
MoveId.FLOATY_FALL,
|
||||
MoveId.DUAL_WINGBEAT,
|
||||
];
|
||||
|
||||
const SPECIAL_TUTOR_MOVES = [MoveId.AEROBLAST, MoveId.AIR_SLASH, MoveId.HURRICANE, MoveId.BLEAKWIND_STORM];
|
||||
|
||||
const SUPPORT_TUTOR_MOVES = [MoveId.FEATHER_DANCE, MoveId.ROOST, MoveId.PLUCK, MoveId.TAILWIND];
|
||||
|
||||
// Not sure the best way to do this
|
||||
const INELIGIBLE_MOVES: MoveId[] = [
|
||||
MoveId.BODY_SLAM,
|
||||
MoveId.BULLDOZE,
|
||||
MoveId.DIG,
|
||||
MoveId.DIVE,
|
||||
MoveId.EARTH_POWER,
|
||||
MoveId.EARTHQUAKE,
|
||||
MoveId.ELECTRIC_TERRAIN,
|
||||
MoveId.FIRE_PLEDGE,
|
||||
MoveId.FISSURE,
|
||||
MoveId.FLYING_PRESS,
|
||||
MoveId.FRENZY_PLANT,
|
||||
MoveId.GEOMANCY,
|
||||
MoveId.GRASS_KNOT,
|
||||
MoveId.GRASS_PLEDGE,
|
||||
MoveId.GRASSY_TERRAIN,
|
||||
MoveId.GRAVITY,
|
||||
MoveId.HEAVY_SLAM,
|
||||
MoveId.INGRAIN,
|
||||
MoveId.LANDS_WRATH,
|
||||
MoveId.MAGNITUDE,
|
||||
MoveId.MAT_BLOCK,
|
||||
MoveId.MISTY_TERRAIN,
|
||||
MoveId.MUD_SPORT,
|
||||
MoveId.MUDDY_WATER,
|
||||
MoveId.ROTOTILLER,
|
||||
MoveId.SEISMIC_TOSS,
|
||||
MoveId.SLAM,
|
||||
MoveId.SMACK_DOWN,
|
||||
MoveId.SPIKES,
|
||||
MoveId.STOMP,
|
||||
MoveId.SUBSTITUTE,
|
||||
MoveId.SURF,
|
||||
MoveId.TOXIC_SPIKES,
|
||||
MoveId.WATER_PLEDGE,
|
||||
MoveId.WATER_SPORT,
|
||||
];
|
||||
|
||||
const sky_battle_requirements = new AnyCombinationPokemonRequirement(
|
||||
3,
|
||||
new TypeRequirement(PokemonType.FLYING, false, 1),
|
||||
new AbilityRequirement(AbilityId.LEVITATE, false, 1),
|
||||
);
|
||||
|
||||
// Helpful variables
|
||||
let originalUsedPP: number[] = [];
|
||||
const disallowedPokemon: Map<number, PlayerPokemon> = new Map<number, PlayerPokemon>();
|
||||
|
||||
/**
|
||||
* Sky Battle encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/5487 | GitHub Issue #5487}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const SkyBattleEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
||||
MysteryEncounterType.SKY_BATTLE,
|
||||
)
|
||||
.withPrimaryPokemonRequirement(sky_battle_requirements)
|
||||
.withMaxAllowedEncounters(1)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withSceneWaveRangeRequirement(...SKY_BATTLE_WAVES)
|
||||
.withIntroSpriteConfigs([]) // Sprite is set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withOnInit(() => {
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const partySize: number = sky_battle_requirements.queryParty(globalScene.getPlayerParty()).length;
|
||||
|
||||
// randomize trainer gender
|
||||
const female = !!randSeedInt(2);
|
||||
const config = getTrainerConfig(partySize, female);
|
||||
const spriteKey = config.getSpriteKey(female);
|
||||
encounter.enemyPartyConfigs.push({
|
||||
trainerConfig: config,
|
||||
female: female,
|
||||
});
|
||||
|
||||
// loads trainer sprite at start of encounter
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: "trainer",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 7,
|
||||
yShadow: 7,
|
||||
},
|
||||
];
|
||||
|
||||
const intro = [
|
||||
{
|
||||
text: `${namespace}:intro` + female ? "_f" : "",
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue` + female ? "_f" : "",
|
||||
},
|
||||
];
|
||||
const title = `${namespace}:title` + female ? "_f" : "";
|
||||
const description = `${namespace}:description` + female ? "_f" : "";
|
||||
const outro = [
|
||||
{
|
||||
text: `${namespace}:outro` + female ? "_f" : "",
|
||||
},
|
||||
];
|
||||
|
||||
encounter.dialogue = { ...encounter.dialogue, intro: intro };
|
||||
let encounterOptionsDialogue = encounter.dialogue.encounterOptionsDialogue ?? {};
|
||||
encounter.dialogue = {
|
||||
...encounter.dialogue,
|
||||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
title,
|
||||
},
|
||||
};
|
||||
encounterOptionsDialogue = encounter.dialogue.encounterOptionsDialogue ?? {};
|
||||
encounter.dialogue = {
|
||||
...encounter.dialogue,
|
||||
encounterOptionsDialogue: {
|
||||
...encounterOptionsDialogue,
|
||||
description,
|
||||
},
|
||||
};
|
||||
encounter.dialogue = { ...encounter.dialogue, outro: outro };
|
||||
|
||||
return true;
|
||||
})
|
||||
.setLocalizationKey(`${namespace}`)
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withSimpleOption(
|
||||
//Option 1: Battle
|
||||
{
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
// Select sky battle
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
// Init the moves available for tutor
|
||||
const moveTutorOptions: PokemonMove[] = [];
|
||||
moveTutorOptions.push(new PokemonMove(PHYSICAL_TUTOR_MOVES[randSeedInt(PHYSICAL_TUTOR_MOVES.length)]));
|
||||
moveTutorOptions.push(new PokemonMove(SPECIAL_TUTOR_MOVES[randSeedInt(SPECIAL_TUTOR_MOVES.length)]));
|
||||
moveTutorOptions.push(new PokemonMove(SUPPORT_TUTOR_MOVES[randSeedInt(SUPPORT_TUTOR_MOVES.length)]));
|
||||
encounter.misc = {
|
||||
moveTutorOptions,
|
||||
};
|
||||
|
||||
//Remove disallowed pokemon
|
||||
const allowedPokemon = sky_battle_requirements.queryParty(globalScene.getPlayerParty());
|
||||
globalScene.getPlayerParty().filter(pokemon => !allowedPokemon.includes(pokemon));
|
||||
globalScene.getPlayerParty().map((pokemon, index) => {
|
||||
if (!allowedPokemon.includes(pokemon)) {
|
||||
disallowedPokemon.set(index, pokemon);
|
||||
}
|
||||
});
|
||||
|
||||
disallowedPokemon.forEach(pokemon => globalScene.removePokemonFromPlayerParty(pokemon, false));
|
||||
|
||||
//Set illegal pokemon moves pp to 0
|
||||
originalUsedPP = [];
|
||||
globalScene.getPlayerParty().forEach(pokemon =>
|
||||
pokemon.moveset
|
||||
.filter(move => INELIGIBLE_MOVES.includes(move.getMove().id))
|
||||
.forEach(move => {
|
||||
originalUsedPP.push(move.ppUsed);
|
||||
move.ppUsed = move.getMovePp();
|
||||
}),
|
||||
);
|
||||
|
||||
// Assigns callback that teaches move before continuing to rewards
|
||||
encounter.onRewards = doFlyingTypeTutor;
|
||||
|
||||
setEncounterRewards({ fillRemaining: true });
|
||||
await transitionMysteryEncounterIntroVisuals(true, true);
|
||||
await initBattleWithEnemyConfig(config);
|
||||
|
||||
//Set illegal enemy pokemon moves pp to 0
|
||||
globalScene.getEnemyParty().forEach(pokemon =>
|
||||
pokemon.moveset
|
||||
.filter(move => INELIGIBLE_MOVES.includes(move.getMove().id))
|
||||
.forEach(move => {
|
||||
move.ppUsed = move.getMovePp();
|
||||
}),
|
||||
);
|
||||
},
|
||||
)
|
||||
.withOption(
|
||||
//Option 2: Flaunt flying pokemon
|
||||
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(sky_battle_requirements) // Must pass the same requirements to trigger this encounter
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
})
|
||||
.withPreOptionPhase(async () => {
|
||||
// Player shows off their Flying pokemon
|
||||
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
||||
|
||||
setEncounterRewards({
|
||||
guaranteedModifierTypeFuncs: [modifierTypes.QUICK_CLAW, modifierTypes.MAX_LURE, modifierTypes.ULTRA_BALL],
|
||||
fillRemaining: false,
|
||||
});
|
||||
encounter.selectedOption!.dialogue!.selected = [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.2.selected`,
|
||||
},
|
||||
];
|
||||
})
|
||||
.withOptionPhase(async () => {
|
||||
// Player shows off their Flying pokémon
|
||||
leaveEncounterWithoutBattle();
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.withSimpleOption(
|
||||
//Option 3: Reject battle and leave with no rewards
|
||||
{
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async () => {
|
||||
leaveEncounterWithoutBattle();
|
||||
return true;
|
||||
},
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
},
|
||||
])
|
||||
.build();
|
||||
|
||||
function getTrainerConfig(party_size: number, female: boolean): TrainerConfig {
|
||||
// Sky trainer config
|
||||
const config = trainerConfigs[TrainerType.SKY_TRAINER].clone();
|
||||
const name = female ? "sky_trainer_f" : "sky_trainer_m";
|
||||
config.name = i18next.t("trainerNames:" + name);
|
||||
|
||||
let pool0Copy = POOL_0_POKEMON.slice(0);
|
||||
pool0Copy = randSeedShuffle(pool0Copy);
|
||||
let pool0Mon = pool0Copy.pop()!;
|
||||
|
||||
config.setPartyTemplates(new TrainerPartyTemplate(party_size, PartyMemberStrength.STRONG));
|
||||
|
||||
// adds a non-repeating random pokemon
|
||||
for (let index = 0; index < party_size; index++) {
|
||||
config.setPartyMemberFunc(index, getRandomPartyMemberFunc([pool0Mon], TrainerSlot.TRAINER, true));
|
||||
pool0Mon = pool0Copy.pop()!;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function doFlyingTypeTutor(): Promise<void> {
|
||||
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO explain
|
||||
return new Promise<void>(async resolve => {
|
||||
const moveOptions = globalScene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;
|
||||
const female = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0].female; //TODO: Is this [0] correct enought?
|
||||
await showEncounterDialogue(`${namespace}:battle_won` + female ? "_f" : "", `${namespace}:speaker`);
|
||||
|
||||
const overlayScale = 1;
|
||||
const moveInfoOverlay = new MoveInfoOverlay({
|
||||
delayVisibility: false,
|
||||
scale: overlayScale,
|
||||
onSide: true,
|
||||
right: true,
|
||||
x: 1,
|
||||
y: -MoveInfoOverlay.getHeight(overlayScale, true) - 1,
|
||||
width: globalScene.game.canvas.width / 6 - 2,
|
||||
});
|
||||
globalScene.ui.add(moveInfoOverlay);
|
||||
|
||||
const optionSelectItems = moveOptions.map((move: PokemonMove) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: move.getName(),
|
||||
handler: () => {
|
||||
moveInfoOverlay.active = false;
|
||||
moveInfoOverlay.setVisible(false);
|
||||
return true;
|
||||
},
|
||||
onHover: () => {
|
||||
moveInfoOverlay.active = true;
|
||||
moveInfoOverlay.show(allMoves[move.moveId]);
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
|
||||
const onHoverOverCancel = () => {
|
||||
moveInfoOverlay.active = false;
|
||||
moveInfoOverlay.setVisible(false);
|
||||
};
|
||||
|
||||
const result = await selectOptionThenPokemon(
|
||||
optionSelectItems,
|
||||
`${namespace}:teach_move_prompt`,
|
||||
undefined, // No filter
|
||||
onHoverOverCancel,
|
||||
);
|
||||
if (!result) {
|
||||
moveInfoOverlay.active = false;
|
||||
moveInfoOverlay.setVisible(false);
|
||||
}
|
||||
// Option select complete, handle if they are learning a move
|
||||
if (result && result.selectedOptionIndex < moveOptions.length) {
|
||||
globalScene.phaseManager.unshiftPhase(
|
||||
new LearnMovePhase(result.selectedPokemonIndex, moveOptions[result.selectedOptionIndex].moveId),
|
||||
);
|
||||
}
|
||||
|
||||
// Reset ineligible moves' pp
|
||||
let idx = 0;
|
||||
globalScene.getPlayerParty().forEach(pokemon =>
|
||||
pokemon.moveset
|
||||
.filter(move => INELIGIBLE_MOVES.includes(move.getMove().id))
|
||||
.forEach(move => {
|
||||
move.ppUsed = originalUsedPP[idx++];
|
||||
}),
|
||||
);
|
||||
|
||||
//Return disallowed pokemons
|
||||
disallowedPokemon.forEach((pokemon, index) => {
|
||||
globalScene.getPlayerParty().splice(index, 0, pokemon);
|
||||
});
|
||||
|
||||
// Complete battle and go to rewards
|
||||
resolve();
|
||||
});
|
||||
}
|
@ -1147,3 +1147,51 @@ export class WeightRequirement extends EncounterPokemonRequirement {
|
||||
return ["weight", pokemon?.getWeight().toString() ?? ""];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class tests for the minimum number of pokemon that passes any of the requirements
|
||||
* E.g. having 3 pokemon that are either flying type, or have the levitate ability
|
||||
*/
|
||||
export class AnyCombinationPokemonRequirement extends EncounterPokemonRequirement {
|
||||
private requirements: EncounterPokemonRequirement[];
|
||||
|
||||
constructor(minNumberOfPokemon: number, ...requirements: EncounterPokemonRequirement[]) {
|
||||
super();
|
||||
this.invertQuery = false;
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.requirements = requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if at least {@linkcode minNumberOfPokemon} pokemon meet any of the requirements
|
||||
* @returns true if at least {@linkcode minNumberOfPokemon} pokemon meet any of the requirements
|
||||
*/
|
||||
override meetsRequirement(): boolean {
|
||||
const party = globalScene.getPlayerParty();
|
||||
return this.queryParty(party).length >= this.minNumberOfPokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the players party for all party members that are compatible with any of the requirements
|
||||
* @param partyPokemon The party of {@linkcode PlayerPokemon}
|
||||
* @returns All party members that are compatible with any of the requirements
|
||||
*/
|
||||
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
return partyPokemon.filter(pokemon => this.requirements.some(req => req.queryParty([pokemon]).length !== 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a dialogue token key/value pair for the given {@linkcode EncounterPokemonRequirement | requirements}.
|
||||
* @param pokemon The {@linkcode PlayerPokemon} to check against
|
||||
* @returns A dialogue token key/value pair
|
||||
*/
|
||||
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
|
||||
for (const req of this.requirements) {
|
||||
if (req.meetsRequirement()) {
|
||||
return req.getDialogueToken(pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
return this.requirements[0].getDialogueToken(pokemon);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/
|
||||
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
|
||||
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
|
||||
import { getBiomeName } from "#app/data/balance/biomes";
|
||||
import { SkyBattleEncounter } from "./encounters/sky-battle-encounter";
|
||||
|
||||
export const EXTREME_ENCOUNTER_BIOMES = [
|
||||
BiomeId.SEA,
|
||||
@ -135,6 +136,36 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [
|
||||
BiomeId.ISLAND,
|
||||
];
|
||||
|
||||
/**
|
||||
* Places where you could fly like a bird
|
||||
*/
|
||||
export const OPEN_SKY_BIOMES = [
|
||||
BiomeId.TOWN,
|
||||
BiomeId.PLAINS,
|
||||
BiomeId.GRASS,
|
||||
BiomeId.TALL_GRASS,
|
||||
BiomeId.METROPOLIS,
|
||||
BiomeId.FOREST,
|
||||
BiomeId.SEA,
|
||||
BiomeId.SWAMP,
|
||||
BiomeId.BEACH,
|
||||
BiomeId.LAKE,
|
||||
BiomeId.MOUNTAIN,
|
||||
BiomeId.BADLANDS,
|
||||
BiomeId.DESERT,
|
||||
BiomeId.MEADOW,
|
||||
BiomeId.VOLCANO,
|
||||
BiomeId.GRAVEYARD,
|
||||
BiomeId.RUINS,
|
||||
BiomeId.WASTELAND,
|
||||
BiomeId.CONSTRUCTION_SITE,
|
||||
BiomeId.JUNGLE,
|
||||
BiomeId.TEMPLE,
|
||||
BiomeId.SLUM,
|
||||
BiomeId.SNOWY_FOREST,
|
||||
BiomeId.ISLAND,
|
||||
];
|
||||
|
||||
export const allMysteryEncounters: {
|
||||
[encounterType: number]: MysteryEncounter;
|
||||
} = {};
|
||||
@ -162,6 +193,9 @@ const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.GLOBAL_TRADE_SYSTEM,
|
||||
];
|
||||
|
||||
// Temporarily disabled pending ME rebalancing
|
||||
// const openSkyBiomeEncounters: MysteryEncounterType[] = [MysteryEncounterType.SKY_BATTLE];
|
||||
|
||||
/**
|
||||
* To add an encounter to every biome possible, use this array
|
||||
*/
|
||||
@ -257,6 +291,8 @@ export function initMysteryEncounters() {
|
||||
allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter;
|
||||
// Temporarily disabled pending ME rebalancing
|
||||
// allMysteryEncounters[MysteryEncounterType.SKY_BATTLE] = SkyBattleEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
@ -295,6 +331,19 @@ export function initMysteryEncounters() {
|
||||
});
|
||||
});
|
||||
|
||||
// add open sky encounters to biome map
|
||||
// Temporarily disabled pending ME rebalancing
|
||||
/*
|
||||
openSkyBiomeEncounters.forEach(encounter => {
|
||||
OPEN_SKY_BIOMES.forEach(biome => {
|
||||
const encountersForBiome = mysteryEncountersByBiome.get(biome);
|
||||
if (encountersForBiome && !encountersForBiome.includes(encounter)) {
|
||||
encountersForBiome.push(encounter);
|
||||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
// Add ANY biome encounters to biome map
|
||||
let _encounterBiomeTableLog = "";
|
||||
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
|
||||
|
@ -6045,4 +6045,9 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setVictoryBgm("mystery_encounter_weird_dream")
|
||||
.setLocalizedName("Future Self F")
|
||||
.setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)),
|
||||
[TrainerType.SKY_TRAINER]: new TrainerConfig(++t)
|
||||
.setHasGenders("Sky Trainer Felicia")
|
||||
.setMoneyMultiplier(2.25)
|
||||
.setEncounterBgm(TrainerType.ACE_TRAINER)
|
||||
.setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)),
|
||||
};
|
||||
|
@ -29,5 +29,6 @@ export enum MysteryEncounterType {
|
||||
FUN_AND_GAMES,
|
||||
UNCOMMON_BREED,
|
||||
GLOBAL_TRADE_SYSTEM,
|
||||
THE_EXPERT_POKEMON_BREEDER
|
||||
THE_EXPERT_POKEMON_BREEDER,
|
||||
SKY_BATTLE,
|
||||
}
|
||||
|
@ -118,6 +118,7 @@ export enum TrainerType {
|
||||
EXPERT_POKEMON_BREEDER,
|
||||
FUTURE_SELF_M,
|
||||
FUTURE_SELF_F,
|
||||
SKY_TRAINER,
|
||||
|
||||
BROCK = 200,
|
||||
MISTY,
|
||||
|
@ -294,6 +294,7 @@ export async function initI18n(): Promise<void> {
|
||||
"mysteryEncounters/theWinstrateChallenge",
|
||||
"mysteryEncounters/teleportingHijinks",
|
||||
"mysteryEncounters/bugTypeSuperfan",
|
||||
"mysteryEncounters/skyBattle",
|
||||
"mysteryEncounters/funAndGames",
|
||||
"mysteryEncounters/uncommonBreed",
|
||||
"mysteryEncounters/globalTradeSystem",
|
||||
|
478
test/mystery-encounter/encounters/sky-battle-encounter.test.ts
Normal file
478
test/mystery-encounter/encounters/sky-battle-encounter.test.ts
Normal file
@ -0,0 +1,478 @@
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { BiomeId } from "#app/enums/biome-id";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { SpeciesId } from "#app/enums/species-id";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
runMysteryEncounterToEnd,
|
||||
runSelectMysteryEncounterOption,
|
||||
skipBattleRunMysteryEncounterRewardsPhase,
|
||||
} from "#test/mystery-encounter/encounter-test-utils";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import type BattleScene from "#app/battle-scene";
|
||||
import { PokemonMove } from "#app/data/moves/pokemon-move";
|
||||
import { UiMode } from "#enums/ui-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/testUtils/gameManagerUtils";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import * as encounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { SkyBattleEncounter } from "#app/data/mystery-encounters/encounters/sky-battle-encounter";
|
||||
import { Button } from "#enums/buttons";
|
||||
|
||||
const namespace = "mysteryEncounters/skyBattle";
|
||||
const defaultParty = [SpeciesId.RAYQUAZA, SpeciesId.WEEDLE, SpeciesId.FLYGON, SpeciesId.RATTATA, SpeciesId.AERODACTYL];
|
||||
const defaultBiome = BiomeId.BEACH;
|
||||
const defaultWave = 52;
|
||||
|
||||
const POOL_0_POKEMON = [
|
||||
SpeciesId.CHARIZARD,
|
||||
SpeciesId.BUTTERFREE,
|
||||
SpeciesId.BEEDRILL,
|
||||
SpeciesId.PIDGEOTTO,
|
||||
SpeciesId.PIDGEOT,
|
||||
SpeciesId.FEAROW,
|
||||
SpeciesId.ZUBAT,
|
||||
SpeciesId.GOLBAT,
|
||||
SpeciesId.VENOMOTH,
|
||||
SpeciesId.HAUNTER,
|
||||
SpeciesId.KOFFING,
|
||||
SpeciesId.WEEZING,
|
||||
SpeciesId.SCYTHER,
|
||||
SpeciesId.GYARADOS,
|
||||
SpeciesId.AERODACTYL,
|
||||
SpeciesId.ARTICUNO,
|
||||
SpeciesId.ZAPDOS,
|
||||
SpeciesId.MOLTRES,
|
||||
SpeciesId.DRAGONITE,
|
||||
SpeciesId.MEWTWO, // ?
|
||||
SpeciesId.MEW,
|
||||
SpeciesId.NOCTOWL,
|
||||
SpeciesId.LEDYBA,
|
||||
SpeciesId.LEDIAN,
|
||||
SpeciesId.CROBAT,
|
||||
SpeciesId.TOGETIC,
|
||||
SpeciesId.XATU,
|
||||
SpeciesId.HOPPIP,
|
||||
SpeciesId.SKIPLOOM,
|
||||
SpeciesId.JUMPLUFF,
|
||||
SpeciesId.YANMA,
|
||||
SpeciesId.MISDREAVUS,
|
||||
SpeciesId.UNOWN,
|
||||
SpeciesId.FORRETRESS, // ?
|
||||
SpeciesId.GLIGAR,
|
||||
SpeciesId.MANTINE,
|
||||
SpeciesId.SKARMORY,
|
||||
SpeciesId.LUGIA,
|
||||
SpeciesId.HO_OH,
|
||||
SpeciesId.CELEBI,
|
||||
SpeciesId.BEAUTIFLY,
|
||||
SpeciesId.DUSTOX,
|
||||
SpeciesId.SWELLOW,
|
||||
SpeciesId.WINGULL,
|
||||
SpeciesId.PELIPPER,
|
||||
SpeciesId.MASQUERAIN,
|
||||
SpeciesId.NINJASK,
|
||||
SpeciesId.SHEDINJA, // ?
|
||||
SpeciesId.VIBRAVA,
|
||||
SpeciesId.FLYGON,
|
||||
SpeciesId.SWABLU,
|
||||
SpeciesId.ALTARIA,
|
||||
SpeciesId.LUNATONE,
|
||||
SpeciesId.SOLROCK,
|
||||
SpeciesId.BALTOY,
|
||||
SpeciesId.CLAYDOL,
|
||||
SpeciesId.DUSKULL,
|
||||
SpeciesId.TROPIUS,
|
||||
SpeciesId.CHIMECHO,
|
||||
SpeciesId.GLALIE, // ?
|
||||
SpeciesId.SALAMENCE,
|
||||
SpeciesId.METANG,
|
||||
SpeciesId.METAGROSS, // ?
|
||||
SpeciesId.LATIAS,
|
||||
SpeciesId.LATIOS,
|
||||
SpeciesId.RAYQUAZA,
|
||||
SpeciesId.JIRACHI,
|
||||
SpeciesId.STARAVIA,
|
||||
SpeciesId.STARAPTOR,
|
||||
SpeciesId.MOTHIM,
|
||||
SpeciesId.COMBEE,
|
||||
SpeciesId.VESPIQUEN,
|
||||
SpeciesId.DRIFLOON,
|
||||
SpeciesId.DRIFBLIM,
|
||||
SpeciesId.MISMAGIUS,
|
||||
SpeciesId.HONCHKROW,
|
||||
SpeciesId.CHINGLING,
|
||||
SpeciesId.BRONZOR,
|
||||
SpeciesId.BRONZONG,
|
||||
SpeciesId.CARNIVINE,
|
||||
SpeciesId.MANTYKE,
|
||||
SpeciesId.MAGNEZONE, // ?
|
||||
SpeciesId.TOGEKISS,
|
||||
SpeciesId.YANMEGA,
|
||||
SpeciesId.GLISCOR,
|
||||
SpeciesId.DUSKNOIR, // ?
|
||||
SpeciesId.ROTOM,
|
||||
SpeciesId.UXIE,
|
||||
SpeciesId.MESPRIT,
|
||||
SpeciesId.AZELF,
|
||||
SpeciesId.GIRATINA,
|
||||
SpeciesId.CRESSELIA,
|
||||
SpeciesId.ARCEUS,
|
||||
SpeciesId.TRANQUILL,
|
||||
SpeciesId.UNFEZANT,
|
||||
SpeciesId.WOOBAT,
|
||||
SpeciesId.SWOOBAT,
|
||||
SpeciesId.SIGILYPH,
|
||||
SpeciesId.ARCHEOPS,
|
||||
SpeciesId.SOLOSIS,
|
||||
SpeciesId.DUOSION,
|
||||
SpeciesId.REUNICLUS,
|
||||
SpeciesId.SWANNA,
|
||||
SpeciesId.VANILLISH,
|
||||
SpeciesId.VANILLUXE,
|
||||
SpeciesId.EMOLGA,
|
||||
SpeciesId.TYNAMO,
|
||||
SpeciesId.EELEKTRIK,
|
||||
SpeciesId.EELEKTROSS,
|
||||
SpeciesId.LAMPENT,
|
||||
SpeciesId.CHANDELURE,
|
||||
SpeciesId.CRYOGONAL,
|
||||
SpeciesId.BRAVIARY,
|
||||
SpeciesId.MANDIBUZZ,
|
||||
SpeciesId.HYDREIGON,
|
||||
SpeciesId.VOLCARONA,
|
||||
SpeciesId.TORNADUS,
|
||||
SpeciesId.THUNDURUS,
|
||||
SpeciesId.RESHIRAM,
|
||||
SpeciesId.ZEKROM,
|
||||
SpeciesId.LANDORUS,
|
||||
SpeciesId.FLETCHINDER,
|
||||
SpeciesId.TALONFLAME,
|
||||
SpeciesId.VIVILLON,
|
||||
SpeciesId.FLOETTE,
|
||||
SpeciesId.FLORGES,
|
||||
SpeciesId.HAWLUCHA, // ?
|
||||
SpeciesId.NOIBAT,
|
||||
SpeciesId.NOIVERN,
|
||||
SpeciesId.YVELTAL,
|
||||
SpeciesId.DARTRIX,
|
||||
SpeciesId.DECIDUEYE, //?
|
||||
SpeciesId.TRUMBEAK,
|
||||
SpeciesId.TOUCANNON,
|
||||
SpeciesId.VIKAVOLT,
|
||||
SpeciesId.ORICORIO,
|
||||
SpeciesId.RIBOMBEE,
|
||||
SpeciesId.COMFEY, //?
|
||||
SpeciesId.MINIOR,
|
||||
SpeciesId.TAPU_KOKO,
|
||||
SpeciesId.TAPU_LELE,
|
||||
SpeciesId.TAPU_BULU,
|
||||
SpeciesId.TAPU_FINI,
|
||||
SpeciesId.LUNALA,
|
||||
SpeciesId.NIHILEGO,
|
||||
SpeciesId.BUZZWOLE,
|
||||
SpeciesId.CELESTEELA,
|
||||
SpeciesId.NECROZMA,
|
||||
SpeciesId.POIPOLE,
|
||||
SpeciesId.NAGANADEL,
|
||||
SpeciesId.CORVISQUIRE,
|
||||
SpeciesId.CORVIKNIGHT,
|
||||
SpeciesId.ORBEETLE,
|
||||
SpeciesId.FLAPPLE,
|
||||
SpeciesId.CRAMORANT,
|
||||
SpeciesId.FROSMOTH,
|
||||
SpeciesId.DRAKLOAK,
|
||||
SpeciesId.DRAGAPULT,
|
||||
SpeciesId.ETERNATUS,
|
||||
SpeciesId.ENAMORUS,
|
||||
SpeciesId.SQUAWKABILLY,
|
||||
SpeciesId.WATTREL,
|
||||
SpeciesId.KILOWATTREL,
|
||||
SpeciesId.BOMBIRDIER,
|
||||
SpeciesId.FLAMIGO,
|
||||
SpeciesId.FLUTTER_MANE,
|
||||
SpeciesId.IRON_JUGULIS,
|
||||
SpeciesId.ROARING_MOON,
|
||||
SpeciesId.MIRAIDON,
|
||||
SpeciesId.KORAIDON,
|
||||
];
|
||||
|
||||
const PHYSICAL_TUTOR_MOVES = [
|
||||
MoveId.FLY,
|
||||
MoveId.BRAVE_BIRD,
|
||||
MoveId.ACROBATICS,
|
||||
MoveId.DRAGON_ASCENT,
|
||||
MoveId.BEAK_BLAST,
|
||||
MoveId.FLOATY_FALL,
|
||||
MoveId.DUAL_WINGBEAT,
|
||||
];
|
||||
|
||||
const SPECIAL_TUTOR_MOVES = [MoveId.AEROBLAST, MoveId.AIR_SLASH, MoveId.HURRICANE, MoveId.BLEAKWIND_STORM];
|
||||
|
||||
const SUPPORT_TUTOR_MOVES = [MoveId.FEATHER_DANCE, MoveId.ROOST, MoveId.PLUCK, MoveId.TAILWIND];
|
||||
|
||||
// Re-enable when the ME is re-enabled
|
||||
describe.todo("Sky Battle - 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();
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<BiomeId, MysteryEncounterType[]>([[BiomeId.BEACH, [MysteryEncounterType.SKY_BATTLE]]]),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, defaultParty);
|
||||
|
||||
expect(SkyBattleEncounter.encounterType).toBe(MysteryEncounterType.SKY_BATTLE);
|
||||
expect(SkyBattleEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA);
|
||||
expect(SkyBattleEncounter.dialogue).toBeDefined();
|
||||
expect(SkyBattleEncounter.dialogue.intro).toStrictEqual([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
},
|
||||
]);
|
||||
expect(SkyBattleEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`);
|
||||
expect(SkyBattleEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}:description`);
|
||||
expect(SkyBattleEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}:query`);
|
||||
expect(SkyBattleEncounter.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should initialize fully", async () => {
|
||||
initSceneWithoutEncounterPhase(scene, defaultParty);
|
||||
scene.currentBattle.mysteryEncounter = SkyBattleEncounter;
|
||||
|
||||
const { onInit } = SkyBattleEncounter;
|
||||
|
||||
expect(SkyBattleEncounter.onInit).toBeDefined();
|
||||
|
||||
SkyBattleEncounter.populateDialogueTokensFromRequirements();
|
||||
const onInitResult = onInit!();
|
||||
const config = SkyBattleEncounter.enemyPartyConfigs[0];
|
||||
|
||||
expect(config).toBeDefined();
|
||||
expect(config.trainerConfig?.trainerType).toBe(TrainerType.SKY_TRAINER);
|
||||
expect(config.trainerConfig?.partyTemplates).toBeDefined();
|
||||
// Allows any gender (randomized)
|
||||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
describe("Option 1 - Battle the Sky Trainer", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = SkyBattleEncounter.options[0];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.1.label`,
|
||||
buttonTooltip: `${namespace}:option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:option.1.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should start battle against the Sky Trainer", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
|
||||
const enemyParty = scene.getEnemyParty();
|
||||
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
expect(scene.currentBattle.trainer?.config.trainerType).toBe(TrainerType.SKY_TRAINER);
|
||||
//Ensure the number of enemy pokemon match our party
|
||||
expect(enemyParty.length).toBe(scene.getPlayerParty().length);
|
||||
expect(enemyParty.every(pkm => POOL_0_POKEMON.includes(pkm.species.speciesId)));
|
||||
});
|
||||
|
||||
it("should zero disallowed moves' pp", async () => {
|
||||
game.override.moveset([MoveId.DRAGON_CLAW, MoveId.EARTHQUAKE]);
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game, false);
|
||||
|
||||
// Only allow acceptable moves (setting available pp to 0)
|
||||
const moveGood = scene.getPlayerParty()[0].getMoveset()[0];
|
||||
const moveBad = scene.getPlayerParty()[0].getMoveset()[1];
|
||||
expect(moveBad.ppUsed).toBe(moveBad.getMovePp());
|
||||
expect(moveGood.ppUsed).toBe(0);
|
||||
|
||||
game.phaseInterceptor["prompts"] = []; // Clear out prompt handlers
|
||||
game.onNextPrompt("MysteryEncounterRewardsPhase", UiMode.OPTION_SELECT, () => {
|
||||
game.scene.ui.setCursor(3);
|
||||
game.scene.ui.processInput(Button.ACTION);
|
||||
});
|
||||
await game.phaseInterceptor.run(MysteryEncounterRewardsPhase);
|
||||
|
||||
// Return unacceptable moves' pp
|
||||
const moveBadAfter = scene.getPlayerParty()[0].getMoveset()[1];
|
||||
expect(moveBadAfter.ppUsed).toBe(0);
|
||||
});
|
||||
|
||||
it("should remove ineligeble pokemon from player party", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game, false);
|
||||
|
||||
// Only allow acceptable pokemon
|
||||
expect(scene.getPlayerParty().length).toBe(3); // we have 2 ineligle pokemon in the default party
|
||||
expect(
|
||||
scene
|
||||
.getPlayerParty()
|
||||
.every(pokemon => ![SpeciesId.WEEDLE, SpeciesId.RATTATA].includes(pokemon.species.speciesId)),
|
||||
).toBe(true);
|
||||
|
||||
game.phaseInterceptor["prompts"] = []; // Clear out prompt handlers
|
||||
game.onNextPrompt("MysteryEncounterRewardsPhase", UiMode.OPTION_SELECT, () => {
|
||||
game.scene.ui.setCursor(3);
|
||||
game.scene.ui.processInput(Button.ACTION);
|
||||
});
|
||||
await game.phaseInterceptor.run(MysteryEncounterRewardsPhase);
|
||||
|
||||
// Return unacceptable pokemons to party
|
||||
expect(scene.getPlayerParty().length).toBe(defaultParty.length);
|
||||
expect(scene.getPlayerParty()[1].species.speciesId).toBe(SpeciesId.WEEDLE);
|
||||
expect(scene.getPlayerParty()[3].species.speciesId).toBe(SpeciesId.RATTATA);
|
||||
});
|
||||
|
||||
it("should let the player learn a Flying move after battle ends", async () => {
|
||||
const selectOptionSpy = vi.spyOn(encounterPhaseUtils, "selectOptionThenPokemon");
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game, false);
|
||||
|
||||
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterRewardsPhase.name);
|
||||
game.phaseInterceptor["prompts"] = []; // Clear out prompt handlers
|
||||
game.onNextPrompt("MysteryEncounterRewardsPhase", UiMode.OPTION_SELECT, () => {
|
||||
game.phaseInterceptor.superEndPhase();
|
||||
});
|
||||
await game.phaseInterceptor.run(MysteryEncounterRewardsPhase);
|
||||
|
||||
expect(selectOptionSpy).toHaveBeenCalledTimes(1);
|
||||
const optionData = selectOptionSpy.mock.calls[0][0];
|
||||
expect(PHYSICAL_TUTOR_MOVES.some(move => new PokemonMove(move).getName() === optionData[0].label)).toBe(true);
|
||||
expect(SPECIAL_TUTOR_MOVES.some(move => new PokemonMove(move).getName() === optionData[1].label)).toBe(true);
|
||||
expect(SUPPORT_TUTOR_MOVES.some(move => new PokemonMove(move).getName() === optionData[2].label)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Show off Flying Types", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = SkyBattleEncounter.options[1];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.2.label`,
|
||||
buttonTooltip: `${namespace}:option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should NOT be selectable if the player doesn't have enough Flying pokemon", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, [
|
||||
SpeciesId.ABRA,
|
||||
SpeciesId.PIDGEY,
|
||||
SpeciesId.SPEAROW,
|
||||
]);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.phaseManager.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, 2);
|
||||
|
||||
expect(scene.phaseManager.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 proceed to rewards screen with reward options", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
|
||||
expect(scene.phaseManager.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name);
|
||||
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||
|
||||
expect(scene.ui.getMode()).to.equal(UiMode.MODIFIER_SELECT);
|
||||
const modifierSelectHandler = scene.ui.handlers.find(
|
||||
h => h instanceof ModifierSelectUiHandler,
|
||||
) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.length).toEqual(3);
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("QUICK_CLAW");
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE");
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("ULTRA_BALL");
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(encounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Reject battle", () => {
|
||||
it("should have the correct properties", async () => {
|
||||
const option = SkyBattleEncounter.options[2];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option.3.label`,
|
||||
buttonTooltip: `${namespace}:option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option.3.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(encounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.SKY_BATTLE, [SpeciesId.RAYQUAZA]);
|
||||
await runMysteryEncounterToEnd(game, 3);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user