mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-20 16:42:45 +02:00
Compare commits
7 Commits
f34defb729
...
25e370e029
Author | SHA1 | Date | |
---|---|---|---|
|
25e370e029 | ||
|
1e306e25b5 | ||
|
43aa772603 | ||
|
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 |
@ -93,6 +93,10 @@ import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap
|
|||||||
import { applyMoveAttrs } from "./apply-attrs";
|
import { applyMoveAttrs } from "./apply-attrs";
|
||||||
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function used to conditionally determine execution of a given {@linkcode MoveAttr}.
|
||||||
|
* Conventionally returns `true` for success and `false` for failure.
|
||||||
|
*/
|
||||||
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
||||||
export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
||||||
|
|
||||||
@ -1390,18 +1394,31 @@ export class BeakBlastHeaderAttr extends AddBattlerTagHeaderAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute to display a message before a move is executed.
|
||||||
|
*/
|
||||||
export class PreMoveMessageAttr extends MoveAttr {
|
export class PreMoveMessageAttr extends MoveAttr {
|
||||||
private message: string | ((user: Pokemon, target: Pokemon, move: Move) => string);
|
/** The message to display or a function returning one */
|
||||||
|
private message: string | ((user: Pokemon, target: Pokemon, move: Move) => string | undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@linkcode PreMoveMessageAttr} to display a message before move execution.
|
||||||
|
* @param message - The message to display before move use, either as a string or a function producing one.
|
||||||
|
* @remarks
|
||||||
|
* If {@linkcode message} evaluates to an empty string (`''`), no message will be displayed
|
||||||
|
* (though the move will still succeed).
|
||||||
|
*/
|
||||||
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
||||||
super();
|
super();
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move: Move, _args: any[]): boolean {
|
||||||
const message = typeof this.message === "string"
|
const message = typeof this.message === "function"
|
||||||
? this.message as string
|
? this.message(user, target, move)
|
||||||
: this.message(user, target, move);
|
: this.message;
|
||||||
|
|
||||||
|
// TODO: Consider changing if/when MoveAttr `apply` return values become significant
|
||||||
if (message) {
|
if (message) {
|
||||||
globalScene.phaseManager.queueMessage(message, 500);
|
globalScene.phaseManager.queueMessage(message, 500);
|
||||||
return true;
|
return true;
|
||||||
@ -11299,7 +11316,11 @@ export function initMoves() {
|
|||||||
.attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL)
|
.attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL)
|
||||||
.condition(failIfLastInPartyCondition),
|
.condition(failIfLastInPartyCondition),
|
||||||
new SelfStatusMove(MoveId.CHILLY_RECEPTION, PokemonType.ICE, -1, 10, -1, 0, 9)
|
new SelfStatusMove(MoveId.CHILLY_RECEPTION, PokemonType.ICE, -1, 10, -1, 0, 9)
|
||||||
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) }))
|
.attr(PreMoveMessageAttr, (user, _target, _move) =>
|
||||||
|
// Don't display text if current move phase is follow up (ie move called indirectly)
|
||||||
|
isVirtual((globalScene.phaseManager.getCurrentPhase() as MovePhase).useMode)
|
||||||
|
? ""
|
||||||
|
: i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(user) }))
|
||||||
.attr(ChillyReceptionAttr, true),
|
.attr(ChillyReceptionAttr, true),
|
||||||
new SelfStatusMove(MoveId.TIDY_UP, PokemonType.NORMAL, -1, 10, -1, 0, 9)
|
new SelfStatusMove(MoveId.TIDY_UP, PokemonType.NORMAL, -1, 10, -1, 0, 9)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true)
|
||||||
|
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() ?? ""];
|
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 { 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 { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
|
||||||
import { getBiomeName } from "#app/data/balance/biomes";
|
import { getBiomeName } from "#app/data/balance/biomes";
|
||||||
|
import { SkyBattleEncounter } from "./encounters/sky-battle-encounter";
|
||||||
|
|
||||||
export const EXTREME_ENCOUNTER_BIOMES = [
|
export const EXTREME_ENCOUNTER_BIOMES = [
|
||||||
BiomeId.SEA,
|
BiomeId.SEA,
|
||||||
@ -135,6 +136,36 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [
|
|||||||
BiomeId.ISLAND,
|
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: {
|
export const allMysteryEncounters: {
|
||||||
[encounterType: number]: MysteryEncounter;
|
[encounterType: number]: MysteryEncounter;
|
||||||
} = {};
|
} = {};
|
||||||
@ -162,6 +193,9 @@ const civilizationBiomeEncounters: MysteryEncounterType[] = [
|
|||||||
MysteryEncounterType.GLOBAL_TRADE_SYSTEM,
|
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
|
* 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.UNCOMMON_BREED] = UncommonBreedEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter;
|
allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter;
|
||||||
allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter;
|
allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter;
|
||||||
|
// Temporarily disabled pending ME rebalancing
|
||||||
|
// allMysteryEncounters[MysteryEncounterType.SKY_BATTLE] = SkyBattleEncounter;
|
||||||
|
|
||||||
// Add extreme encounters to biome map
|
// Add extreme encounters to biome map
|
||||||
extremeBiomeEncounters.forEach(encounter => {
|
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
|
// Add ANY biome encounters to biome map
|
||||||
let _encounterBiomeTableLog = "";
|
let _encounterBiomeTableLog = "";
|
||||||
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
|
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
|
||||||
|
@ -764,7 +764,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
readonly subLegendary: boolean;
|
readonly subLegendary: boolean;
|
||||||
readonly legendary: boolean;
|
readonly legendary: boolean;
|
||||||
readonly mythical: boolean;
|
readonly mythical: boolean;
|
||||||
readonly species: string;
|
public category: string;
|
||||||
readonly growthRate: GrowthRate;
|
readonly growthRate: GrowthRate;
|
||||||
/** The chance (as a decimal) for this Species to be male, or `null` for genderless species */
|
/** The chance (as a decimal) for this Species to be male, or `null` for genderless species */
|
||||||
readonly malePercent: number | null;
|
readonly malePercent: number | null;
|
||||||
@ -778,7 +778,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
subLegendary: boolean,
|
subLegendary: boolean,
|
||||||
legendary: boolean,
|
legendary: boolean,
|
||||||
mythical: boolean,
|
mythical: boolean,
|
||||||
species: string,
|
category: string,
|
||||||
type1: PokemonType,
|
type1: PokemonType,
|
||||||
type2: PokemonType | null,
|
type2: PokemonType | null,
|
||||||
height: number,
|
height: number,
|
||||||
@ -829,7 +829,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
this.subLegendary = subLegendary;
|
this.subLegendary = subLegendary;
|
||||||
this.legendary = legendary;
|
this.legendary = legendary;
|
||||||
this.mythical = mythical;
|
this.mythical = mythical;
|
||||||
this.species = species;
|
this.category = category;
|
||||||
this.growthRate = growthRate;
|
this.growthRate = growthRate;
|
||||||
this.malePercent = malePercent;
|
this.malePercent = malePercent;
|
||||||
this.genderDiffs = genderDiffs;
|
this.genderDiffs = genderDiffs;
|
||||||
@ -968,6 +968,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
|||||||
|
|
||||||
localize(): void {
|
localize(): void {
|
||||||
this.name = i18next.t(`pokemon:${SpeciesId[this.speciesId].toLowerCase()}`);
|
this.name = i18next.t(`pokemon:${SpeciesId[this.speciesId].toLowerCase()}`);
|
||||||
|
this.category = i18next.t(`pokemonCategory:${SpeciesId[this.speciesId].toLowerCase()}_category`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWildSpeciesForLevel(level: number, allowEvolving: boolean, isBoss: boolean, gameMode: GameMode): SpeciesId {
|
getWildSpeciesForLevel(level: number, allowEvolving: boolean, isBoss: boolean, gameMode: GameMode): SpeciesId {
|
||||||
|
@ -6045,4 +6045,9 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
.setVictoryBgm("mystery_encounter_weird_dream")
|
.setVictoryBgm("mystery_encounter_weird_dream")
|
||||||
.setLocalizedName("Future Self F")
|
.setLocalizedName("Future Self F")
|
||||||
.setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)),
|
.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,
|
FUN_AND_GAMES,
|
||||||
UNCOMMON_BREED,
|
UNCOMMON_BREED,
|
||||||
GLOBAL_TRADE_SYSTEM,
|
GLOBAL_TRADE_SYSTEM,
|
||||||
THE_EXPERT_POKEMON_BREEDER
|
THE_EXPERT_POKEMON_BREEDER,
|
||||||
|
SKY_BATTLE,
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,7 @@ export enum TrainerType {
|
|||||||
EXPERT_POKEMON_BREEDER,
|
EXPERT_POKEMON_BREEDER,
|
||||||
FUTURE_SELF_M,
|
FUTURE_SELF_M,
|
||||||
FUTURE_SELF_F,
|
FUTURE_SELF_F,
|
||||||
|
SKY_TRAINER,
|
||||||
|
|
||||||
BROCK = 200,
|
BROCK = 200,
|
||||||
MISTY,
|
MISTY,
|
||||||
|
@ -668,6 +668,9 @@ export class MovePhase extends BattlePhase {
|
|||||||
}),
|
}),
|
||||||
500,
|
500,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Moves with pre-use messages (Magnitude, Chilly Reception, Fickle Beam, etc.) always display their messages even on failure
|
||||||
|
// TODO: This assumes single target for message funcs - is this sustainable?
|
||||||
applyMoveAttrs("PreMoveMessageAttr", this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove());
|
applyMoveAttrs("PreMoveMessageAttr", this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +245,7 @@ export async function initI18n(): Promise<void> {
|
|||||||
"pokeball",
|
"pokeball",
|
||||||
"pokedexUiHandler",
|
"pokedexUiHandler",
|
||||||
"pokemon",
|
"pokemon",
|
||||||
|
"pokemonCategory",
|
||||||
"pokemonEvolutions",
|
"pokemonEvolutions",
|
||||||
"pokemonForm",
|
"pokemonForm",
|
||||||
"pokemonInfo",
|
"pokemonInfo",
|
||||||
@ -294,6 +295,7 @@ export async function initI18n(): Promise<void> {
|
|||||||
"mysteryEncounters/theWinstrateChallenge",
|
"mysteryEncounters/theWinstrateChallenge",
|
||||||
"mysteryEncounters/teleportingHijinks",
|
"mysteryEncounters/teleportingHijinks",
|
||||||
"mysteryEncounters/bugTypeSuperfan",
|
"mysteryEncounters/bugTypeSuperfan",
|
||||||
|
"mysteryEncounters/skyBattle",
|
||||||
"mysteryEncounters/funAndGames",
|
"mysteryEncounters/funAndGames",
|
||||||
"mysteryEncounters/uncommonBreed",
|
"mysteryEncounters/uncommonBreed",
|
||||||
"mysteryEncounters/globalTradeSystem",
|
"mysteryEncounters/globalTradeSystem",
|
||||||
|
@ -174,6 +174,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
private pokemonCaughtHatchedContainer: Phaser.GameObjects.Container;
|
private pokemonCaughtHatchedContainer: Phaser.GameObjects.Container;
|
||||||
private pokemonCaughtCountText: Phaser.GameObjects.Text;
|
private pokemonCaughtCountText: Phaser.GameObjects.Text;
|
||||||
private pokemonFormText: Phaser.GameObjects.Text;
|
private pokemonFormText: Phaser.GameObjects.Text;
|
||||||
|
private pokemonCategoryText: Phaser.GameObjects.Text;
|
||||||
private pokemonHatchedIcon: Phaser.GameObjects.Sprite;
|
private pokemonHatchedIcon: Phaser.GameObjects.Sprite;
|
||||||
private pokemonHatchedCountText: Phaser.GameObjects.Text;
|
private pokemonHatchedCountText: Phaser.GameObjects.Text;
|
||||||
private pokemonShinyIcons: Phaser.GameObjects.Sprite[];
|
private pokemonShinyIcons: Phaser.GameObjects.Sprite[];
|
||||||
@ -409,6 +410,12 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonFormText.setOrigin(0, 0);
|
this.pokemonFormText.setOrigin(0, 0);
|
||||||
this.starterSelectContainer.add(this.pokemonFormText);
|
this.starterSelectContainer.add(this.pokemonFormText);
|
||||||
|
|
||||||
|
this.pokemonCategoryText = addTextObject(100, 18, "Category", TextStyle.WINDOW_ALT, {
|
||||||
|
fontSize: "42px",
|
||||||
|
});
|
||||||
|
this.pokemonCategoryText.setOrigin(1, 0);
|
||||||
|
this.starterSelectContainer.add(this.pokemonCategoryText);
|
||||||
|
|
||||||
this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25);
|
this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25);
|
||||||
this.pokemonCaughtHatchedContainer.setScale(0.5);
|
this.pokemonCaughtHatchedContainer.setScale(0.5);
|
||||||
this.starterSelectContainer.add(this.pokemonCaughtHatchedContainer);
|
this.starterSelectContainer.add(this.pokemonCaughtHatchedContainer);
|
||||||
@ -2354,6 +2361,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonCaughtHatchedContainer.setVisible(true);
|
this.pokemonCaughtHatchedContainer.setVisible(true);
|
||||||
this.pokemonCandyContainer.setVisible(false);
|
this.pokemonCandyContainer.setVisible(false);
|
||||||
this.pokemonFormText.setVisible(false);
|
this.pokemonFormText.setVisible(false);
|
||||||
|
this.pokemonCategoryText.setVisible(false);
|
||||||
|
|
||||||
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true);
|
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true);
|
||||||
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||||
@ -2382,6 +2390,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonCaughtHatchedContainer.setVisible(false);
|
this.pokemonCaughtHatchedContainer.setVisible(false);
|
||||||
this.pokemonCandyContainer.setVisible(false);
|
this.pokemonCandyContainer.setVisible(false);
|
||||||
this.pokemonFormText.setVisible(false);
|
this.pokemonFormText.setVisible(false);
|
||||||
|
this.pokemonCategoryText.setVisible(false);
|
||||||
|
|
||||||
this.setSpeciesDetails(species!, {
|
this.setSpeciesDetails(species!, {
|
||||||
// TODO: is this bang correct?
|
// TODO: is this bang correct?
|
||||||
@ -2534,6 +2543,13 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonNameText.setText(species ? "???" : "");
|
this.pokemonNameText.setText(species ? "???" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setting the category
|
||||||
|
if (isFormCaught) {
|
||||||
|
this.pokemonCategoryText.setText(species.category);
|
||||||
|
} else {
|
||||||
|
this.pokemonCategoryText.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
// Setting tint of the sprite
|
// Setting tint of the sprite
|
||||||
if (isFormCaught) {
|
if (isFormCaught) {
|
||||||
this.species.loadAssets(female!, formIndex, shiny, variant as Variant, true).then(() => {
|
this.species.loadAssets(female!, formIndex, shiny, variant as Variant, true).then(() => {
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { AbilityId } from "#enums/ability-id";
|
import { RandomMoveAttr } from "#app/data/moves/move";
|
||||||
|
import { MoveResult } from "#enums/move-result";
|
||||||
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { AbilityId } from "#app/enums/ability-id";
|
||||||
import { WeatherType } from "#enums/weather-type";
|
import { WeatherType } from "#enums/weather-type";
|
||||||
import GameManager from "#test/testUtils/gameManager";
|
import GameManager from "#test/testUtils/gameManager";
|
||||||
|
import i18next from "i18next";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
//import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
describe("Moves - Chilly Reception", () => {
|
describe("Moves - Chilly Reception", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -25,95 +28,121 @@ describe("Moves - Chilly Reception", () => {
|
|||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
game.override
|
game.override
|
||||||
.battleStyle("single")
|
.battleStyle("single")
|
||||||
.moveset([MoveId.CHILLY_RECEPTION, MoveId.SNOWSCAPE])
|
.moveset([MoveId.CHILLY_RECEPTION, MoveId.SNOWSCAPE, MoveId.SPLASH, MoveId.METRONOME])
|
||||||
.enemyMoveset(MoveId.SPLASH)
|
.enemyMoveset(MoveId.SPLASH)
|
||||||
.enemyAbility(AbilityId.BALL_FETCH)
|
.enemyAbility(AbilityId.BALL_FETCH)
|
||||||
.ability(AbilityId.BALL_FETCH);
|
.ability(AbilityId.BALL_FETCH);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should still change the weather if user can't switch out", async () => {
|
it("should display message before use, switch the user out and change the weather to snow", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
|
|
||||||
|
const [slowking, meowth] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.scene.getPlayerPokemon()).toBe(meowth);
|
||||||
|
expect(slowking.isOnField()).toBe(false);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
|
expect(game.textInterceptor.logs).toContain(
|
||||||
|
i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(slowking) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should still change weather if user can't switch out", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING]);
|
||||||
|
|
||||||
game.move.select(MoveId.CHILLY_RECEPTION);
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
|
||||||
|
expect(game.scene.getPlayerPokemon()?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should switch out even if it's snowing", async () => {
|
it("should still switch out even if weather cannot be changed", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
// first turn set up snow with snowscape, try chilly reception on second turn
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
|
const [slowking, meowth] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
game.move.select(MoveId.SNOWSCAPE);
|
game.move.select(MoveId.SNOWSCAPE);
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.toNextTurn();
|
||||||
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
game.move.select(MoveId.CHILLY_RECEPTION);
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
game.doSelectPartyPokemon(1);
|
game.doSelectPartyPokemon(1);
|
||||||
|
// TODO: Uncomment lines once wimp out PR fixes force switches to not reset summon data immediately
|
||||||
|
// await game.phaseInterceptor.to("SwitchSummonPhase", false);
|
||||||
|
// expect(slowking.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
|
||||||
|
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MEOWTH);
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
|
expect(game.scene.getPlayerPokemon()).toBe(meowth);
|
||||||
|
expect(slowking.isOnField()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("happy case - switch out and weather changes", async () => {
|
// Source: https://replay.pokemonshowdown.com/gen9ou-2367532550
|
||||||
|
it("should fail (while still displaying message) if neither weather change nor switch out succeeds", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.SLOWKING]);
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
|
const slowking = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(MoveId.SNOWSCAPE);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
|
game.move.select(MoveId.CHILLY_RECEPTION);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
|
||||||
|
expect(game.scene.getPlayerPokemon()).toBe(slowking);
|
||||||
|
expect(slowking.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
|
||||||
|
expect(game.textInterceptor.logs).toContain(
|
||||||
|
i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(slowking) }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should succeed without message if called indirectly", async () => {
|
||||||
|
vi.spyOn(RandomMoveAttr.prototype, "getMoveOverride").mockReturnValue(MoveId.CHILLY_RECEPTION);
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
|
|
||||||
game.move.select(MoveId.CHILLY_RECEPTION);
|
const [slowking, meowth] = game.scene.getPlayerParty();
|
||||||
game.doSelectPartyPokemon(1);
|
|
||||||
|
game.move.select(MoveId.METRONOME);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(SpeciesId.MEOWTH);
|
expect(game.scene.getPlayerPokemon()).toBe(meowth);
|
||||||
|
expect(slowking.isOnField()).toBe(false);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
|
||||||
|
expect(game.textInterceptor.logs).not.toContain(
|
||||||
|
i18next.t("moveTriggers:chillyReception", { pokemonName: getPokemonNameWithAffix(slowking) }),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// enemy uses another move and weather doesn't change
|
// Bugcheck test for enemy AI bug
|
||||||
it("check case - enemy not selecting chilly reception doesn't change weather ", async () => {
|
it("check case - enemy not selecting chilly reception doesn't change weather", async () => {
|
||||||
game.override.battleStyle("single").enemyMoveset([MoveId.CHILLY_RECEPTION, MoveId.TACKLE]).moveset(MoveId.SPLASH);
|
game.override.enemyMoveset([MoveId.CHILLY_RECEPTION, MoveId.TACKLE]);
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
await game.classicMode.startBattle([SpeciesId.SLOWKING, SpeciesId.MEOWTH]);
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
game.move.select(MoveId.SPLASH);
|
||||||
await game.move.selectEnemyMove(MoveId.TACKLE);
|
await game.move.selectEnemyMove(MoveId.TACKLE);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
expect(game.scene.arena.weather?.weatherType).toBeUndefined();
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("enemy trainer - expected behavior ", async () => {
|
|
||||||
game.override
|
|
||||||
.battleStyle("single")
|
|
||||||
.startingWave(8)
|
|
||||||
.enemyMoveset(MoveId.CHILLY_RECEPTION)
|
|
||||||
.enemySpecies(SpeciesId.MAGIKARP)
|
|
||||||
.moveset([MoveId.SPLASH, MoveId.THUNDERBOLT]);
|
|
||||||
|
|
||||||
await game.classicMode.startBattle([SpeciesId.JOLTEON]);
|
|
||||||
const RIVAL_MAGIKARP1 = game.scene.getEnemyPokemon()?.id;
|
|
||||||
|
|
||||||
game.move.select(MoveId.SPLASH);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
expect(game.scene.getEnemyPokemon()?.id !== RIVAL_MAGIKARP1);
|
|
||||||
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
game.move.select(MoveId.SPLASH);
|
|
||||||
|
|
||||||
// second chilly reception should still switch out
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
expect(game.scene.getEnemyPokemon()?.id === RIVAL_MAGIKARP1);
|
|
||||||
game.move.select(MoveId.THUNDERBOLT);
|
|
||||||
|
|
||||||
// enemy chilly recep move should fail: it's snowing and no option to switch out
|
|
||||||
// no crashing
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
await game.phaseInterceptor.to("TurnInitPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
game.move.select(MoveId.SPLASH);
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
|
||||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
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