Merge pull request #117 from AsdarDevelops/delibirdy-avarice
Delibird-y Encounter
This commit is contained in:
commit
cf92c572c9
|
@ -0,0 +1,247 @@
|
|||
import { leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { BerryModifier, PokemonBaseStatModifier, PokemonBaseStatTotalModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, TerastallizeModifier } from "#app/modifier/modifier";
|
||||
import { ModifierRewardPhase } from "#app/phases";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:delibirdy";
|
||||
|
||||
/** Berries only */
|
||||
const OPTION_2_ALLOWED_MODIFIERS = [BerryModifier.name, PokemonInstantReviveModifier.name];
|
||||
|
||||
/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
|
||||
const OPTION_3_DISALLOWED_MODIFIERS = [
|
||||
BerryModifier.name,
|
||||
PokemonInstantReviveModifier.name,
|
||||
TerastallizeModifier.name,
|
||||
PokemonBaseStatModifier.name,
|
||||
PokemonBaseStatTotalModifier.name
|
||||
];
|
||||
|
||||
/**
|
||||
* Delibird-y encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/57 | GitHub Issue #57}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const DelibirdyEncounter: IMysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Must have enough money for it to spawn at the very least
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.DELIBIRD.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 38,
|
||||
scale: 0.94
|
||||
},
|
||||
{
|
||||
spriteKey: Species.DELIBIRD.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.06
|
||||
},
|
||||
{
|
||||
spriteKey: Species.DELIBIRD.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
startFrame: 65,
|
||||
x: 1,
|
||||
y: 5,
|
||||
yShadow: 5
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
}
|
||||
])
|
||||
.withTitle(`${namespace}:title`)
|
||||
.withDescription(`${namespace}:description`)
|
||||
.withQuery(`${namespace}:query`)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}:outro`,
|
||||
}
|
||||
])
|
||||
.withOption(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneMoneyRequirement(0, 2.75)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option:1:label`,
|
||||
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:1:selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
|
||||
return true;
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Give the player an Ability Charm
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option:2:label`,
|
||||
buttonTooltip: `${namespace}:option:2:tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option:2:select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:2:selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}:invalid_selection`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
|
||||
if (modifier.type.name.includes("Berry")) {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.CANDY_JAR));
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM));
|
||||
}
|
||||
|
||||
// Remove the modifier if its stacks go to 0
|
||||
modifier.stackCount -= 1;
|
||||
if (modifier.stackCount === 0) {
|
||||
scene.removeModifier(modifier);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}:option:3:label`,
|
||||
buttonTooltip: `${namespace}:option:3:tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option:3:select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:3:selected`,
|
||||
},
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
const option: OptionSelectItem = {
|
||||
label: modifier.type.name,
|
||||
handler: () => {
|
||||
// Pokemon and item selected
|
||||
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
||||
encounter.misc = {
|
||||
chosenPokemon: pokemon,
|
||||
chosenModifier: modifier,
|
||||
};
|
||||
return true;
|
||||
},
|
||||
};
|
||||
return option;
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}:invalid_selection`);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
// Give the player a Berry Pouch
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH));
|
||||
|
||||
// Remove the modifier if its stacks go to 0
|
||||
modifier.stackCount -= 1;
|
||||
if (modifier.stackCount === 0) {
|
||||
scene.removeModifier(modifier);
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
|
@ -80,7 +80,8 @@ export const FieryFalloutEncounter: IMysteryEncounter =
|
|||
repeat: true,
|
||||
hidden: true,
|
||||
hasShadow: true,
|
||||
x: -20
|
||||
x: -20,
|
||||
startFrame: 20
|
||||
},
|
||||
{
|
||||
spriteKey: volcaronaSpecies.getSpriteId(true ),
|
||||
|
|
|
@ -106,7 +106,7 @@ export const PokemonSalesmanEncounter: IMysteryEncounter =
|
|||
})
|
||||
.withOption(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL)
|
||||
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withSceneMoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
|
||||
.withDialogue({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import * as Utils from "#app/utils";
|
||||
import { Type } from "../type";
|
||||
|
@ -57,6 +57,10 @@ export default class MysteryEncounterOption implements MysteryEncounterOption {
|
|||
this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene);
|
||||
}
|
||||
|
||||
pokemonMeetsPrimaryRequirements?(scene: BattleScene, pokemon: Pokemon) {
|
||||
return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id));
|
||||
}
|
||||
|
||||
meetsPrimaryRequirementAndPrimaryPokemonSelected?(scene: BattleScene) {
|
||||
if (!this.primaryPokemonRequirements) {
|
||||
return true;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { ModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { ModifierType } from "#app/modifier/modifier-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
|
@ -744,20 +744,20 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
|
|||
}
|
||||
|
||||
export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemModifier: PokemonHeldItemModifierType[];
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
|
||||
constructor(heldItem: PokemonHeldItemModifierType | PokemonHeldItemModifierType[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemModifier = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
}
|
||||
|
||||
meetsRequirement(scene: BattleScene): boolean {
|
||||
const partyPokemon = scene.getParty();
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredHeldItemModifier?.length < 0) {
|
||||
if (isNullOrUndefined(partyPokemon) || this?.requiredHeldItemModifiers?.length < 0) {
|
||||
return false;
|
||||
}
|
||||
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
|
||||
|
@ -765,19 +765,26 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
|||
|
||||
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifier.filter((heldItem) => pokemon.getHeldItems().filter((it) => it.type.id === heldItem.id).length > 0).length > 0);
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
|
||||
return pokemon.getHeldItems().some((it) => {
|
||||
return it.constructor.name === heldItem;
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed heldItems
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifier.filter((heldItem) => pokemon.getHeldItems().filter((it) => it.type.id === heldItem.id).length === 0).length === 0);
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
||||
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
}).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = this.requiredHeldItemModifier.filter((a) => {
|
||||
pokemon.getHeldItems().filter((it) => it.type.id === a.id).length > 0;
|
||||
const requiredItems = pokemon.getHeldItems().filter((it) => {
|
||||
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
});
|
||||
if (requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].name];
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/f
|
|||
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
|
||||
import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter";
|
||||
import { OfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/offer-you-cant-refuse-encounter";
|
||||
import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter";
|
||||
|
||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||
|
@ -150,7 +151,8 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
|||
MysteryEncounterType.FIGHT_OR_FLIGHT,
|
||||
MysteryEncounterType.DARK_DEAL,
|
||||
MysteryEncounterType.MYSTERIOUS_CHEST,
|
||||
MysteryEncounterType.TRAINING_SESSION
|
||||
MysteryEncounterType.TRAINING_SESSION,
|
||||
MysteryEncounterType.DELIBIRDY,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -163,15 +165,20 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
|||
export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.TOWN, []],
|
||||
[Biome.PLAINS, [
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[Biome.GRASS, [
|
||||
MysteryEncounterType.SLUMBERING_SNORLAX,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[Biome.TALL_GRASS, [
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
[Biome.TALL_GRASS, []],
|
||||
[Biome.METROPOLIS, []],
|
||||
[Biome.FOREST, [
|
||||
MysteryEncounterType.SAFARI_ZONE
|
||||
MysteryEncounterType.SAFARI_ZONE,
|
||||
MysteryEncounterType.ABSOLUTE_AVARICE
|
||||
]],
|
||||
|
||||
[Biome.SEA, [
|
||||
|
@ -230,6 +237,8 @@ export function initMysteryEncounters() {
|
|||
allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.POKEMON_SALESMAN] = PokemonSalesmanEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.OFFER_YOU_CANT_REFUSE] = OfferYouCantRefuseEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.DELIBIRDY] = DelibirdyEncounter;
|
||||
// allMysteryEncounters[MysteryEncounterType.ABSOLUTE_AVARICE] = Abs;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
|
|
@ -13,5 +13,7 @@ export enum MysteryEncounterType {
|
|||
FIERY_FALLOUT,
|
||||
THE_STRONG_STUFF,
|
||||
POKEMON_SALESMAN,
|
||||
OFFER_YOU_CANT_REFUSE
|
||||
OFFER_YOU_CANT_REFUSE,
|
||||
DELIBIRDY,
|
||||
ABSOLUTE_AVARICE
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ export class MysteryEncounterSpriteConfig {
|
|||
disableAnimation?: boolean = false;
|
||||
/** Repeat the animation. Defaults to `false` */
|
||||
repeat?: boolean = false;
|
||||
/** What frame of the animation to start on. Defaults to 0 */
|
||||
startFrame?: number = 0;
|
||||
/** Hidden at start of encounter. Defaults to `false` */
|
||||
hidden?: boolean = false;
|
||||
/** Tint color. `0` - `1`. Higher means darker tint. */
|
||||
|
@ -279,7 +281,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
const trainerAnimConfig = {
|
||||
key: config.spriteKey,
|
||||
repeat: config?.repeat ? -1 : 0,
|
||||
startFrame: 0
|
||||
startFrame: config?.startFrame ?? 0
|
||||
};
|
||||
|
||||
this.tryPlaySprite(sprites[i], tintSprites[i], trainerAnimConfig);
|
||||
|
|
|
@ -13,6 +13,7 @@ import { trainingSessionDialogue } from "#app/locales/en/mystery-encounters/trai
|
|||
import { theStrongStuffDialogue } from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue";
|
||||
import { pokemonSalesmanDialogue } from "#app/locales/en/mystery-encounters/pokemon-salesman-dialogue";
|
||||
import { offerYouCantRefuseDialogue } from "#app/locales/en/mystery-encounters/offer-you-cant-refuse-dialogue";
|
||||
import { delibirdyDialogue } from "#app/locales/en/mystery-encounters/delibirdy-dialogue";
|
||||
|
||||
/**
|
||||
* Patterns that can be used:
|
||||
|
@ -50,5 +51,6 @@ export const mysteryEncounter = {
|
|||
fieryFallout: fieryFalloutDialogue,
|
||||
theStrongStuff: theStrongStuffDialogue,
|
||||
pokemonSalesman: pokemonSalesmanDialogue,
|
||||
offerYouCantRefuse: offerYouCantRefuseDialogue
|
||||
offerYouCantRefuse: offerYouCantRefuseDialogue,
|
||||
delibirdy: delibirdyDialogue,
|
||||
} as const;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
export const delibirdyDialogue = {
|
||||
intro: "A pack of Delibird have appeared!",
|
||||
title: "Delibird-y",
|
||||
description: "The Delibirds are looking at you expectantly, as if they want something. Perhaps giving them an item or some money would satisfy them?",
|
||||
query: "What will you give them?",
|
||||
invalid_selection: "Pokémon doesn't have that kind of item.",
|
||||
option: {
|
||||
1: {
|
||||
label: "Give Money",
|
||||
tooltip: "(-) Give the Delibirds {{money, money}}\n(+) Receive a Gift Item",
|
||||
selected: `You toss the money to the Delibirds,\nwho chatter amongst themselves excitedly.
|
||||
$They turn back to you and happily give you a present!`,
|
||||
},
|
||||
2: {
|
||||
label: "Give Food",
|
||||
tooltip: "(-) Give the Delibirds a Berry or Reviver Seed\n(+) Receive a Gift Item",
|
||||
select_prompt: "Select an item to give.",
|
||||
selected: `You toss the {{chosenItem}} to the Delibirds,\nwho chatter amongst themselves excitedly.
|
||||
$They turn back to you and happily give you a present!`,
|
||||
},
|
||||
3: {
|
||||
label: "Give an Item",
|
||||
tooltip: "(-) Give the Delibirds a Held Item\n(+) Receive a Gift Item",
|
||||
select_prompt: "Select an item to give.",
|
||||
selected: `You toss the {{chosenItem}} to the Delibirds,\nwho chatter amongst themselves excitedly.
|
||||
$They turn back to you and happily give you a present!`,
|
||||
},
|
||||
},
|
||||
outro: `The Delibird pack happily waddles off into the distance.
|
||||
$What a curious little exchange!`
|
||||
};
|
|
@ -12,14 +12,10 @@ export const shadyVitaminDealerDialogue = {
|
|||
1: {
|
||||
label: "The Cheap Deal",
|
||||
tooltip: "(-) Pay {{option1Money, money}}\n(-) Side Effects?\n(+) Chosen Pokémon Gains 2 Random Vitamins",
|
||||
selected: `{{option1PrimaryName}} swims ahead, guiding you back on track.
|
||||
\${{option1PrimaryName}} seems to also have gotten stronger in this time of need!`,
|
||||
},
|
||||
2: {
|
||||
label: "The Pricey Deal",
|
||||
tooltip: "(-) Pay {{option2Money, money}}\n(-) Side Effects?\n(+) Chosen Pokémon Gains 2 Random Vitamins",
|
||||
selected: `{{option2PrimaryName}} flies ahead of your boat, guiding you back on track.
|
||||
\${{option2PrimaryName}} seems to also have gotten stronger in this time of need!`,
|
||||
},
|
||||
3: {
|
||||
label: "Leave",
|
||||
|
|
|
@ -6,15 +6,21 @@ import { Mode } from "#app/ui/ui";
|
|||
import GameManager from "../utils/gameManager";
|
||||
import MessageUiHandler from "#app/ui/message-ui-handler";
|
||||
import { Status, StatusEffect } from "#app/data/status-effect";
|
||||
import { expect, vi } from "vitest";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import PartyUiHandler from "#app/ui/party-ui-handler";
|
||||
import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
|
||||
|
||||
/**
|
||||
* Runs a MysteryEncounter to either the start of a battle, or to the MysteryEncounterRewardsPhase, depending on the option selected
|
||||
* @param game
|
||||
* @param optionNo - human number, not index
|
||||
* @param secondaryOptionSelect -
|
||||
* @param isBattle - if selecting option should lead to battle, set to true
|
||||
*/
|
||||
export async function runMysteryEncounterToEnd(game: GameManager, optionNo: number, isBattle: boolean = false) {
|
||||
await runSelectMysteryEncounterOption(game, optionNo, isBattle);
|
||||
export async function runMysteryEncounterToEnd(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo: number } = null, isBattle: boolean = false) {
|
||||
vi.spyOn(EncounterPhaseUtils, "selectPokemonForOption");
|
||||
await runSelectMysteryEncounterOption(game, optionNo, secondaryOptionSelect);
|
||||
|
||||
// run the selected options phase
|
||||
game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => {
|
||||
|
@ -49,7 +55,7 @@ export async function runMysteryEncounterToEnd(game: GameManager, optionNo: numb
|
|||
}
|
||||
}
|
||||
|
||||
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, isBattle: boolean = false) {
|
||||
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, secondaryOptionSelect: { pokemonNo: number, optionNo: number } = null) {
|
||||
// Handle any eventual queued messages (e.g. weather phase, etc.)
|
||||
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
|
||||
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
|
||||
|
@ -73,6 +79,7 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
|
|||
uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that
|
||||
|
||||
switch (optionNo) {
|
||||
default:
|
||||
case 1:
|
||||
// no movement needed. Default cursor position
|
||||
break;
|
||||
|
@ -89,6 +96,42 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
|
|||
}
|
||||
|
||||
uiHandler.processInput(Button.ACTION);
|
||||
|
||||
if (!isNaN(secondaryOptionSelect?.pokemonNo)) {
|
||||
await handleSecondaryOptionSelect(game, secondaryOptionSelect.pokemonNo, secondaryOptionSelect.optionNo);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number, optionNo: number) {
|
||||
// Handle secondary option selections
|
||||
const partyUiHandler = game.scene.ui.handlers[Mode.PARTY] as PartyUiHandler;
|
||||
vi.spyOn(partyUiHandler, "show");
|
||||
await vi.waitFor(() => expect(partyUiHandler.show).toHaveBeenCalled());
|
||||
|
||||
for (let i = 1; i < pokemonNo; i++) {
|
||||
partyUiHandler.processInput(Button.DOWN);
|
||||
}
|
||||
|
||||
// Open options on Pokemon
|
||||
partyUiHandler.processInput(Button.ACTION);
|
||||
// Click "Select" on Pokemon options
|
||||
partyUiHandler.processInput(Button.ACTION);
|
||||
|
||||
// If there is a second choice to make after selecting a Pokemon
|
||||
if (!isNaN(optionNo)) {
|
||||
// Wait for Summary menu to close and second options to spawn
|
||||
const secondOptionUiHandler = game.scene.ui.handlers[Mode.OPTION_SELECT] as OptionSelectUiHandler;
|
||||
vi.spyOn(secondOptionUiHandler, "show");
|
||||
await vi.waitFor(() => expect(secondOptionUiHandler.show).toHaveBeenCalled());
|
||||
|
||||
// Navigate down to the correct option
|
||||
for (let i = 1; i < optionNo; i++) {
|
||||
secondOptionUiHandler.processInput(Button.DOWN);
|
||||
}
|
||||
|
||||
// Select the option
|
||||
secondOptionUiHandler.processInput(Button.ACTION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
import { Biome } from "#app/enums/biome";
|
||||
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter";
|
||||
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
|
||||
const namespace = "mysteryEncounter:delibirdy";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.CAVE;
|
||||
const defaultWave = 45;
|
||||
|
||||
describe("Delibird-y - 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.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.CAVE, [MysteryEncounterType.DELIBIRDY]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
expect(DelibirdyEncounter.encounterType).toBe(MysteryEncounterType.DELIBIRDY);
|
||||
expect(DelibirdyEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
|
||||
expect(DelibirdyEncounter.dialogue).toBeDefined();
|
||||
expect(DelibirdyEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}:intro` }]);
|
||||
expect(DelibirdyEncounter.dialogue.outro).toStrictEqual([{ text: `${namespace}:outro` }]);
|
||||
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
|
||||
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
|
||||
expect(DelibirdyEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
|
||||
expect(DelibirdyEncounter.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DELIBIRDY);
|
||||
});
|
||||
|
||||
it("should not run above wave 179", async () => {
|
||||
game.override.startingWave(181);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not spawn if player does not have enough money", async () => {
|
||||
scene.money = 0;
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DELIBIRDY);
|
||||
});
|
||||
|
||||
describe("Option 1 - Give them money", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = DelibirdyEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:1:label`,
|
||||
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:1:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("Should update the player's money properly", async () => {
|
||||
const initialMoney = 20000;
|
||||
scene.money = initialMoney;
|
||||
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
|
||||
const price = (scene.currentBattle.mysteryEncounter.options[0].requirements[0] as MoneyRequirement).requiredMoney;
|
||||
|
||||
expect(updateMoneySpy).toHaveBeenCalledWith(scene, -price, true, false);
|
||||
expect(scene.money).toBe(initialMoney - price);
|
||||
});
|
||||
|
||||
it("Should give the player a Hidden Ability Charm", async () => {
|
||||
scene.money = 200000;
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
|
||||
const itemModifier = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier;
|
||||
|
||||
expect(itemModifier).toBeDefined();
|
||||
expect(itemModifier.stackCount).toBe(1);
|
||||
});
|
||||
|
||||
it("should be disabled if player does not have enough money", async () => {
|
||||
scene.money = 0;
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
scene.money = 200000;
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Give Food", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DelibirdyEncounter.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`,
|
||||
secondOptionPrompt: `${namespace}:option:2:select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:2:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("Should decrease Berry stacks and give the player a Candy Jar", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 2 Sitrus berries on party lead
|
||||
scene.modifiers = [];
|
||||
const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type;
|
||||
const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier;
|
||||
sitrusMod.stackCount = 2;
|
||||
await scene.addModifier(sitrusMod, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1});
|
||||
|
||||
const sitrusAfter = scene.findModifier(m => m instanceof BerryModifier);
|
||||
const candyJarAfter = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier);
|
||||
|
||||
expect(sitrusAfter.stackCount).toBe(1);
|
||||
expect(candyJarAfter).toBeDefined();
|
||||
expect(candyJarAfter.stackCount).toBe(1);
|
||||
});
|
||||
|
||||
it("Should remove Reviver Seed and give the player a Healing Charm", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 1 Reviver Seed on party lead
|
||||
scene.modifiers = [];
|
||||
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
|
||||
const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
|
||||
modifier.stackCount = 1;
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1});
|
||||
|
||||
const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier);
|
||||
const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier);
|
||||
|
||||
expect(reviverSeedAfter).toBeUndefined();
|
||||
expect(healingCharmAfter).toBeDefined();
|
||||
expect(healingCharmAfter.stackCount).toBe(1);
|
||||
});
|
||||
|
||||
it("should be disabled if player does not have any proper items", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 1 Soul Dew on party lead
|
||||
scene.modifiers = [];
|
||||
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
|
||||
const modifier = soulDew.newModifier(scene.getParty()[0]);
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 1 Reviver Seed on party lead
|
||||
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
|
||||
const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
|
||||
modifier.stackCount = 1;
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1});
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Give Item", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DelibirdyEncounter.options[2];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:3:label`,
|
||||
buttonTooltip: `${namespace}:option:3:tooltip`,
|
||||
secondOptionPrompt: `${namespace}:option:3:select_prompt`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:3:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("Should decrease held item stacks and give the player a Berry Pouch", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 2 Soul Dew on party lead
|
||||
scene.modifiers = [];
|
||||
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
|
||||
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
|
||||
modifier.stackCount = 2;
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1});
|
||||
|
||||
const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier);
|
||||
const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier);
|
||||
|
||||
expect(soulDewAfter.stackCount).toBe(1);
|
||||
expect(berryPouchAfter).toBeDefined();
|
||||
expect(berryPouchAfter.stackCount).toBe(1);
|
||||
});
|
||||
|
||||
it("Should remove held item and give the player a Berry Pouch", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 1 Soul Dew on party lead
|
||||
scene.modifiers = [];
|
||||
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
|
||||
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
|
||||
modifier.stackCount = 1;
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1});
|
||||
|
||||
const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier);
|
||||
const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier);
|
||||
|
||||
expect(soulDewAfter).toBeUndefined();
|
||||
expect(berryPouchAfter).toBeDefined();
|
||||
expect(berryPouchAfter.stackCount).toBe(1);
|
||||
});
|
||||
|
||||
it("should be disabled if player does not have any proper items", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 1 Reviver Seed on party lead
|
||||
scene.modifiers = [];
|
||||
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
|
||||
const modifier = revSeed.newModifier(scene.getParty()[0]);
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
|
||||
|
||||
// Set 1 Soul Dew on party lead
|
||||
scene.modifiers = [];
|
||||
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
|
||||
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
|
||||
modifier.stackCount = 1;
|
||||
await scene.addModifier(modifier, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1});
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -152,7 +152,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
const phaseSpy = vi.spyOn(scene, "pushPhase");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, true);
|
||||
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||
|
@ -169,7 +169,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
|
||||
it("should give charcoal to lead pokemon", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, true);
|
||||
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||
|
|
|
@ -151,12 +151,17 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
|||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const continueEncounterSpy = vi.spyOn((encounterPhase as MysteryEncounterPhase), "continueEncounter");
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(continueEncounterSpy).not.toHaveBeenCalled();
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -210,12 +215,17 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
|||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const continueEncounterSpy = vi.spyOn((encounterPhase as MysteryEncounterPhase), "continueEncounter");
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(continueEncounterSpy).not.toHaveBeenCalled();
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { ShinyRateBoosterModifier } from "#app/modifier/modifier";
|
||||
|
||||
const namespace = "mysteryEncounter:offerYouCantRefuse";
|
||||
/** Gyarados for Indimidate */
|
||||
|
@ -144,6 +145,16 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
|
|||
expect(scene.money).toBe(initialMoney + price);
|
||||
});
|
||||
|
||||
it("Should give the player a Shiny Charm", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
|
||||
const itemModifier = scene.findModifier(m => m instanceof ShinyRateBoosterModifier) as ShinyRateBoosterModifier;
|
||||
|
||||
expect(itemModifier).toBeDefined();
|
||||
expect(itemModifier.stackCount).toBe(1);
|
||||
});
|
||||
|
||||
it("Should remove the Pokemon from the party", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Species } from "#app/enums/species";
|
|||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
|
@ -13,6 +13,7 @@ import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounter
|
|||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
|
||||
const namespace = "mysteryEncounter:pokemonSalesman";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
|
@ -108,12 +109,20 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
|||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
it("should not spawn if player does not have enough money", async () => {
|
||||
scene.money = 0;
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.POKEMON_SALESMAN);
|
||||
});
|
||||
|
||||
describe("Option 1 - Purchase the pokemon", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = PokemonSalesmanEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
const option = PokemonSalesmanEncounter.options[0];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:1:label`,
|
||||
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||
selected: [
|
||||
|
@ -139,6 +148,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
|||
});
|
||||
|
||||
it("Should add the Pokemon to the party", async () => {
|
||||
scene.money = 20000;
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||
|
||||
const initialPartySize = scene.getParty().length;
|
||||
|
@ -150,7 +160,28 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
|||
expect(scene.getParty().find(p => p.name === pokemonName) instanceof PlayerPokemon).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should be disabled if player does not have enough money", async () => {
|
||||
scene.money = 0;
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
scene.money = 20000;
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
|
||||
|
|
|
@ -196,7 +196,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
|||
const phaseSpy = vi.spyOn(scene, "pushPhase");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2, true);
|
||||
await runMysteryEncounterToEnd(game, 2, null, true);
|
||||
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||
|
@ -220,7 +220,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
|||
|
||||
it("should have Soul Dew in rewards", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2, true);
|
||||
await runMysteryEncounterToEnd(game, 2, null, true);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||
|
|
Loading…
Reference in New Issue