add unit tests for offer you can't refuse encounter

This commit is contained in:
ImperialSympathizer 2024-07-25 12:10:22 -04:00
parent 065ac0e779
commit c2e5a58f59
41 changed files with 727 additions and 315 deletions

View File

@ -66,10 +66,12 @@ import { Species } from "#enums/species";
import { UiTheme } from "#enums/ui-theme";
import { TimedEventManager } from "#app/timed-event-manager.js";
import i18next from "i18next";
import IMysteryEncounter, { MysteryEncounterTier, MysteryEncounterVariant } from "./data/mystery-encounters/mystery-encounter";
import IMysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -1184,7 +1186,7 @@ export default class BattleScene extends SceneBase {
this.arena.removeAllTags();
// If last battle was mystery encounter and no battle occurred, skip return phases
if (lastBattle?.mysteryEncounter?.encounterVariant !== MysteryEncounterVariant.NO_BATTLE) {
if (lastBattle?.mysteryEncounter?.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
playerField.forEach((_, p) => this.unshiftPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) {

View File

@ -14,7 +14,8 @@ import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import i18next from "#app/plugins/i18n";
import IMysteryEncounter, { MysteryEncounterVariant } from "./data/mystery-encounters/mystery-encounter";
import IMysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
export enum BattleType {
WILD,
@ -205,7 +206,7 @@ export default class Battle {
getBgmOverride(scene: BattleScene): string {
const battlers = this.enemyParty.slice(0, this.getBattlerCount());
if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) {
if (this.battleType === BattleType.TRAINER || this.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages().length) {
return `encounter_${this.trainer.getEncounterBgm()}`;
}

View File

@ -3,13 +3,15 @@ import { ModifierRewardPhase } from "#app/phases";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "../../../battle-scene";
import BattleScene from "#app/battle-scene";
import { modifierTypes } from "#app/modifier/modifier-type";
import { getPokemonSpecies } from "../../pokemon-species";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:darkDeal";
@ -105,7 +107,7 @@ export const DarkDealEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,

View File

@ -6,11 +6,11 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "../../../battle-scene";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, {
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:departmentStoreSale";

View File

@ -1,22 +1,15 @@
import { MoveCategory } from "#app/data/move";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import {
generateModifierTypeOption,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TempBattleStat } from "#app/data/temp-battle-stat";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, {
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieldTrip";
@ -62,7 +55,7 @@ export const FieldTripEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
@ -88,7 +81,7 @@ export const FieldTripEncounter: IMysteryEncounter =
encounter.options[0].dialogue.selected = [
{
text: `${namespace}:option:incorrect`,
speaker: `${namespace}:option:speaker`,
speaker: `${namespace}:speaker`,
},
{
text: `${namespace}:option:lesson_learned`,
@ -148,7 +141,7 @@ export const FieldTripEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`,
@ -174,7 +167,7 @@ export const FieldTripEncounter: IMysteryEncounter =
encounter.options[1].dialogue.selected = [
{
text: `${namespace}:option:incorrect`,
speaker: `${namespace}:option:speaker`,
speaker: `${namespace}:speaker`,
},
{
text: `${namespace}:option:lesson_learned`,
@ -240,7 +233,7 @@ export const FieldTripEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`,
@ -266,7 +259,7 @@ export const FieldTripEncounter: IMysteryEncounter =
encounter.options[2].dialogue.selected = [
{
text: `${namespace}:option:incorrect`,
speaker: `${namespace}:option:speaker`,
speaker: `${namespace}:speaker`,
},
{
text: `${namespace}:option:lesson_learned`,

View File

@ -1,9 +1,9 @@
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, initCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { TypeRequirement } from "../mystery-encounter-requirements";
import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
@ -18,6 +18,8 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { StatusEffect } from "#app/data/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieryFallout";
@ -202,7 +204,7 @@ export const FieryFalloutEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL)
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically
.withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically
.withDialogue({

View File

@ -1,5 +1,5 @@
import { BattleStat } from "#app/data/battle-stat";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import {
EnemyPartyConfig,
initBattleWithEnemyConfig,
@ -20,14 +20,13 @@ import { StatChangePhase } from "#app/phases";
import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, {
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fightOrFlight";
@ -132,7 +131,7 @@ export const FightOrFlightEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT_OR_SPECIAL)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}:option:2:label`,

View File

@ -2,11 +2,13 @@ import { getPokemonSpecies } from "#app/data/pokemon-species.js";
import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
const OPTION_1_REQUIRED_MOVE = Moves.SURF;
const OPTION_2_REQUIRED_MOVE = Moves.FLY;
@ -53,7 +55,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
// Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
new MysteryEncounterOptionBuilder()
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
disabledButtonLabel: `${namespace}:option:1:label_disabled`,
@ -72,7 +74,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
//Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
new MysteryEncounterOptionBuilder()
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:2:label`,
disabledButtonLabel: `${namespace}:option:2:label_disabled`,

View File

@ -13,12 +13,10 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "../../../battle-scene";
import * as Utils from "../../../utils";
import IMysteryEncounter, {
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:mysteriousChallengers";

View File

@ -4,9 +4,11 @@ import { getHighestLevelPlayerPokemon, koPlayerPokemon } from "#app/data/mystery
import { ModifierTier } from "#app/modifier/modifier-tier";
import { randSeedInt } from "#app/utils.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:mysteriousChest";
@ -42,7 +44,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,

View File

@ -2,14 +2,16 @@ import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { ModifierRewardPhase } from "#app/phases";
import { EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse";
@ -56,22 +58,36 @@ export const OfferYouCantRefuseEncounter: IMysteryEncounter =
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
const price = scene.getWaveMoneyAmount(10);
scene.currentBattle.mysteryEncounter.setDialogueToken("strongestPokemon", pokemon.name);
scene.currentBattle.mysteryEncounter.setDialogueToken("price", price.toString());
encounter.setDialogueToken("strongestPokemon", pokemon.name);
encounter.setDialogueToken("price", price.toString());
// Store pokemon and price
scene.currentBattle.mysteryEncounter.misc = {
encounter.misc = {
pokemon: pokemon,
price: price
};
// If player meets the combo OR requirements for option 2, populate the token
const opt2Req = encounter.options[1].primaryPokemonRequirements[0];
if (opt2Req.meetsRequirement(scene)) {
const abilityToken = encounter.dialogueTokens["option2PrimaryAbility"];
const moveToken = encounter.dialogueTokens["option2PrimaryMove"];
if (abilityToken) {
encounter.setDialogueToken("moveOrAbility", abilityToken);
} else if (moveToken) {
encounter.setDialogueToken("moveOrAbility", moveToken);
}
}
return true;
})
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`,
@ -98,14 +114,18 @@ export const OfferYouCantRefuseEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withPrimaryPokemonRequirement(new MoveRequirement(EXTORTION_MOVES))
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
new MoveRequirement(EXTORTION_MOVES),
new AbilityRequirement(EXTORTION_ABILITIES))
)
.withDialogue({
buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`,
disabledButtonTooltip: `${namespace}:option:2:tooltip_disabled`,
selected: [
{
speaker: `${namespace}:speaker`,
text: `${namespace}:option:2:selected`,
},
],
@ -129,7 +149,7 @@ export const OfferYouCantRefuseEncounter: IMysteryEncounter =
selected: [
{
speaker: `${namespace}:speaker`,
text: `${namespace}:option:2:selected`,
text: `${namespace}:option:3:selected`,
},
],
},

View File

@ -1,17 +1,19 @@
import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { PokeballType } from "#app/data/pokeball";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:pokemonSalesman";
@ -104,7 +106,7 @@ export const PokemonSalesmanEncounter: IMysteryEncounter =
})
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT_OR_SPECIAL)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL)
.withHasDexProgress(true)
.withSceneMoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({

View File

@ -1,8 +1,8 @@
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, MysteryEncounterVariant } from "../mystery-encounter";
import MysteryEncounterOption, { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import { ScanIvsPhase, SummonPhase, VictoryPhase } from "#app/phases";
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
@ -15,6 +15,9 @@ import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter
import { doPlayerFlee, doPokemonFlee, getRandomSpeciesByStarterTier, trainerThrowPokeball } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:safariZone";
@ -50,7 +53,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOption(new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, 2.75)) // Cost equal to 1 Max Revive
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
@ -64,7 +67,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter;
encounter.encounterVariant = MysteryEncounterVariant.CONTINUOUS_ENCOUNTER;
encounter.encounterMode = MysteryEncounterMode.CONTINUOUS_ENCOUNTER;
encounter.misc = {
safariPokemonRemaining: 3
};
@ -116,7 +119,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
*/
const safariZoneGameOptions: MysteryEncounterOption[] = [
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari:1:label`,
buttonTooltip: `${namespace}:safari:1:tooltip`,
@ -150,7 +153,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.build(),
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari:2:label`,
buttonTooltip: `${namespace}:safari:2:tooltip`,
@ -180,7 +183,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.build(),
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari:3:label`,
buttonTooltip: `${namespace}:safari:3:tooltip`,
@ -209,7 +212,7 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.build(),
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:safari:4:label`,
buttonTooltip: `${namespace}:safari:4:tooltip`,

View File

@ -5,12 +5,14 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:shadyVitaminDealer";
@ -59,7 +61,7 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, 2) // Wave scaling money multiplier of 2
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
@ -144,7 +146,7 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, 5) // Wave scaling money multiplier of 5
.withDialogue({
buttonLabel: `${namespace}:option:2:label`,

View File

@ -4,8 +4,8 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, initCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -14,6 +14,8 @@ import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { PartyHealPhase } from "#app/phases";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounter:slumberingSnorlax";
@ -121,7 +123,7 @@ export const SlumberingSnorlaxEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL)
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES))
.withDialogue({
buttonLabel: `${namespace}:option:3:label`,

View File

@ -1,8 +1,8 @@
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, initCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { Nature } from "#app/data/nature";
@ -15,6 +15,7 @@ import { StatChangePhase } from "#app/phases";
import { BattleStat } from "#app/data/battle-stat";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theStrongStuff";

View File

@ -13,10 +13,12 @@ import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { randSeedShuffle } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
/** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:trainingSession";
@ -52,7 +54,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option:1:label`,
@ -196,7 +198,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option:2:label`,
@ -289,7 +291,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}:option:3:label`,

View File

@ -1,7 +1,7 @@
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export class MysteryEncounterData {
encounteredEvents: [MysteryEncounterType, MysteryEncounterTier][] = [];

View File

@ -1,29 +1,19 @@
import { OptionTextDisplay } from "#app/data/mystery-encounters/mystery-encounter-dialogue";
import { Moves } from "#app/enums/moves";
import { PlayerPokemon } from "#app/field/pokemon";
import BattleScene from "../../battle-scene";
import * as Utils from "../../utils";
import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import { Type } from "../type";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined } from "../../utils";
export enum EncounterOptionMode {
/** Default style */
DEFAULT,
/** Disabled on requirements not met, default style on requirements met */
DISABLED_OR_DEFAULT,
/** Default style on requirements not met, special style on requirements met */
DEFAULT_OR_SPECIAL,
/** Disabled on requirements not met, special style on requirements met */
DISABLED_OR_SPECIAL
}
import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
export type OptionPhaseCallback = (scene: BattleScene) => Promise<void | boolean>;
export default interface MysteryEncounterOption {
optionMode: EncounterOptionMode;
optionMode: MysteryEncounterOptionMode;
hasDexProgress?: boolean;
@ -114,8 +104,8 @@ export default class MysteryEncounterOption implements MysteryEncounterOption {
return false;
}
} else {
// this means we CAN have the same pokemon be a primary and secondary pokemon, so just choose any qualifying one randomly.
this.primaryPokemon = qualified[Utils.randSeedInt(qualified.length, 0)];
// Just pick the first qualifying Pokemon
this.primaryPokemon = qualified[0];
return true;
}
}
@ -145,7 +135,7 @@ export default class MysteryEncounterOption implements MysteryEncounterOption {
export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOption> {
optionMode?: EncounterOptionMode;
optionMode?: MysteryEncounterOptionMode;
requirements?: EncounterSceneRequirement[] = [];
primaryPokemonRequirements?: EncounterPokemonRequirement[] = [];
secondaryPokemonRequirements ?: EncounterPokemonRequirement[] = [];
@ -156,7 +146,7 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
onPostOptionPhase?: OptionPhaseCallback;
dialogue?: OptionTextDisplay;
withOptionMode(optionMode: EncounterOptionMode): this & Pick<MysteryEncounterOption, "optionMode"> {
withOptionMode(optionMode: MysteryEncounterOptionMode): this & Pick<MysteryEncounterOption, "optionMode"> {
return Object.assign(this, { optionMode });
}
@ -165,6 +155,10 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
}
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounterOption, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement.");
}
this.requirements.push(requirement);
return Object.assign(this, { requirements: this.requirements });
}
@ -190,6 +184,10 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
}
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<MysteryEncounterOption, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.primaryPokemonRequirements.push(requirement);
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
}
@ -218,7 +216,11 @@ export class MysteryEncounterOptionBuilder implements Partial<MysteryEncounterOp
return this.withPrimaryPokemonRequirement(new CanLearnMoveRequirement(move, options));
}
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements?: boolean): this & Required<Pick<MysteryEncounterOption, "secondaryPokemonRequirements">> {
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = true): this & Required<Pick<MysteryEncounterOption, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSecondaryRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { secondaryPokemonRequirements: this.secondaryPokemonRequirements });

View File

@ -1,6 +1,6 @@
import { PlayerPokemon } from "#app/field/pokemon";
import { ModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import BattleScene from "../../battle-scene";
import BattleScene from "#app/battle-scene";
import { isNullOrUndefined } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
@ -21,12 +21,35 @@ export interface EncounterRequirement {
}
export abstract class EncounterSceneRequirement implements EncounterRequirement {
abstract meetsRequirement(scene: BattleScene): boolean;
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
}
export class CombinationSceneRequirement extends EncounterSceneRequirement {
orRequirements: EncounterSceneRequirement[];
constructor(... orRequirements: EncounterSceneRequirement[]) {
super();
this.orRequirements = orRequirements;
}
meetsRequirement(scene: BattleScene): boolean {
throw new Error("Method not implemented.");
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return true;
}
}
return false;
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["", ""];
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
}
}
return null;
}
}
@ -40,12 +63,49 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen
* Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned.
* @param partyPokemon
*/
abstract queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[];
abstract getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string];
}
export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
orRequirements: EncounterPokemonRequirement[];
constructor(...orRequirements: EncounterPokemonRequirement[]) {
super();
this.invertQuery = false;
this.minNumberOfPokemon = 1;
this.orRequirements = orRequirements;
}
meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return true;
}
}
return false;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
for (const req of this.orRequirements) {
const result = req.queryParty(partyPokemon);
if (result?.length > 0) {
return result;
}
}
return [];
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["", ""];
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
}
}
return null;
}
}
@ -56,7 +116,7 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement {
* Used for specifying an encounter that must be seen before this encounter can spawn
* @param previousEncounterRequirement
*/
constructor(previousEncounterRequirement) {
constructor(previousEncounterRequirement: MysteryEncounterType) {
super();
this.previousEncounterRequirement = previousEncounterRequirement;
}
@ -497,19 +557,16 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((abilities) => pokemon.hasAbility(abilities)).length > 0);
return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability));
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess
return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((abilities) => pokemon.hasAbility(abilities)).length === 0);
return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0);
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const reqAbilities = this.requiredAbilities.filter((a) => {
pokemon.hasAbility(a);
});
if (reqAbilities.length > 0) {
return ["ability", Abilities[reqAbilities[0]]];
if (this.requiredAbilities.some(a => pokemon.getAbility().id === a)) {
return ["ability", pokemon.getAbility().name];
}
return null;
}

View File

@ -2,14 +2,14 @@ import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-p
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../battle-scene";
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "../../field/mystery-encounter-intro";
import * as Utils from "../../utils";
import BattleScene from "#app/battle-scene";
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import * as Utils from "#app/utils";
import { StatusEffect } from "../status-effect";
import MysteryEncounterDialogue, {
OptionTextDisplay
} from "./mystery-encounter-dialogue";
import MysteryEncounterOption, { EncounterOptionMode, MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
import {
EncounterPokemonRequirement,
EncounterSceneRequirement,
@ -20,27 +20,9 @@ import {
} from "./mystery-encounter-requirements";
import { BattlerIndex } from "#app/battle";
import { EncounterAnim } from "#app/data/battle-anims";
export enum MysteryEncounterVariant {
DEFAULT,
TRAINER_BATTLE,
WILD_BATTLE,
BOSS_BATTLE,
NO_BATTLE,
/** For spawning new encounter queries instead of continuing to next wave */
CONTINUOUS_ENCOUNTER
}
/**
* Enum values are base spawn weights of each tier
*/
export enum MysteryEncounterTier {
COMMON = 64,
GREAT = 40,
ULTRA = 21,
ROGUE = 3,
MASTER = 0 // Not currently used
}
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
export interface StartOfBattleEffect {
sourcePokemon?: Pokemon;
@ -130,7 +112,7 @@ export default interface IMysteryEncounter {
* For example, if there is no battle as part of the encounter/selected option, should be set to NO_BATTLE
* Defaults to DEFAULT
*/
encounterVariant?: MysteryEncounterVariant;
encounterMode?: MysteryEncounterMode;
/**
* Flag for checking if it's the first time a shop is being shown for an encounter.
* Defaults to true so that the first shop does not override the specified rewards.
@ -181,7 +163,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
this.dialogue = this.dialogue ?? {};
// Default max is 1 for ROGUE encounters, 3 for others
this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? 1 : 3;
this.encounterVariant = MysteryEncounterVariant.DEFAULT;
this.encounterMode = MysteryEncounterMode.DEFAULT;
this.requirements = this.requirements ? this.requirements : [];
this.hideBattleIntroMessage = !isNullOrUndefined(this.hideBattleIntroMessage) ? this.hideBattleIntroMessage : false;
this.autoHideIntroVisuals = !isNullOrUndefined(this.autoHideIntroVisuals) ? this.autoHideIntroVisuals : true;
@ -314,23 +296,30 @@ export default class IMysteryEncounter implements IMysteryEncounter {
if (this.requirements?.length > 0) {
for (const req of this.requirements) {
const dialogueToken = req.getDialogueToken(scene);
if (dialogueToken?.length === 2) {
this.setDialogueToken(...dialogueToken);
}
}
}
if (this.primaryPokemon?.length > 0) {
this.setDialogueToken("primaryName", this.primaryPokemon.name);
for (const req of this.primaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.primaryPokemon);
if (value?.length === 2) {
this.setDialogueToken("primary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
}
}
}
if (this.secondaryPokemonRequirements?.length > 0 && this.secondaryPokemon?.length > 0) {
this.setDialogueToken("secondaryName", this.secondaryPokemon[0].name);
for (const req of this.secondaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]);
if (value?.length === 2) {
this.setDialogueToken("primary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
this.setDialogueToken("secondary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
}
@ -344,36 +333,42 @@ export default class IMysteryEncounter implements IMysteryEncounter {
if (opt.requirements?.length > 0) {
for (const req of opt.requirements) {
const dialogueToken = req.getDialogueToken(scene);
if (dialogueToken?.length === 2) {
this.setDialogueToken("option" + j + this.capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]);
}
}
}
if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) {
this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.name);
for (const req of opt.primaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.primaryPokemon);
if (value?.length === 2) {
this.setDialogueToken("option" + j + "Primary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
}
}
}
if (opt.secondaryPokemonRequirements?.length > 0 && opt.secondaryPokemon?.length > 0) {
this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].name);
for (const req of opt.secondaryPokemonRequirements) {
if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);
if (value?.length === 2) {
this.setDialogueToken("option" + j + "Secondary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
}
}
}
}
}
setDialogueToken?(key: string, value: string) {
this.dialogueTokens[key] = value;
}
/**
* If an encounter uses {@link MysteryEncounterVariant.CONTINUOUS_ENCOUNTER},
* If an encounter uses {@link MysteryEncounterMode.CONTINUOUS_ENCOUNTER},
* should rely on this value for seed offset instead of wave index.
*
* This offset is incremented for each new {@link MysteryEncounterPhase} that occurs,
@ -466,7 +461,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns
*/
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(EncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
}
/**
@ -481,7 +476,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
*/
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT)
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue(dialogue)
.withOptionPhase(callback).build());
@ -592,6 +587,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns
*/
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.primaryPokemonRequirements.push(requirement);
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements });
}
@ -624,6 +623,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
// ex. if your only grass type pokemon, a snivy, is chosen as primary, if the support pokemon requires a grass type, the event won't trigger because
// it's already been
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSupportRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements, secondaryPokemonRequirements: this.secondaryPokemonRequirements });

View File

@ -61,7 +61,7 @@ export const EXTORTION_MOVES = [
Moves.BEAT_UP,
Moves.COIL,
Moves.WRING_OUT,
Moves.STRING_SHOT
Moves.STRING_SHOT,
];
export const EXTORTION_ABILITIES = [
@ -69,5 +69,5 @@ export const EXTORTION_ABILITIES = [
Abilities.ARENA_TRAP,
Abilities.SHADOW_TAG,
Abilities.SUCTION_CUPS,
Abilities.STICKY_HOLD,
Abilities.STICKY_HOLD
];

View File

@ -18,17 +18,17 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { Biome } from "#enums/biome";
import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next";
import BattleScene from "../../../battle-scene";
import Trainer, { TrainerVariant } from "../../../field/trainer";
import * as Utils from "../../../utils";
import PokemonSpecies from "../../pokemon-species";
import { Status, StatusEffect } from "../../status-effect";
import { TrainerConfig, trainerConfigs, TrainerSlot } from "../../trainer-config";
import { MysteryEncounterVariant } from "../mystery-encounter";
import BattleScene from "#app/battle-scene";
import Trainer, { TrainerVariant } from "#app/field/trainer";
import * as Utils from "#app/utils";
import { Gender } from "#app/data/gender";
import { Nature } from "#app/data/nature";
import { Moves } from "#enums/moves";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { Status, StatusEffect } from "#app/data/status-effect";
import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config";
import PokemonSpecies from "#app/data/pokemon-species";
/**
* Animates exclamation sprite over trainer's head at start of encounter
@ -107,7 +107,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
const trainerType = partyConfig?.trainerType;
let trainerConfig = partyConfig?.trainerConfig;
if (trainerType || trainerConfig) {
scene.currentBattle.mysteryEncounter.encounterVariant = MysteryEncounterVariant.TRAINER_BATTLE;
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.TRAINER_BATTLE;
if (scene.currentBattle.trainer) {
scene.currentBattle.trainer.setVisible(false);
scene.currentBattle.trainer.destroy();
@ -128,7 +128,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex);
} else {
// Wild
scene.currentBattle.mysteryEncounter.encounterVariant = MysteryEncounterVariant.WILD_BATTLE;
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.WILD_BATTLE;
battle.enemyLevels = new Array(partyConfig?.pokemonConfigs?.length > 0 ? partyConfig?.pokemonConfigs?.length : doubleBattle ? 2 : 1).fill(null).map(() => scene.currentBattle.getLevelForWave());
}
@ -160,7 +160,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemySpecies = config.species;
isBoss = config.isBoss;
if (isBoss) {
scene.currentBattle.mysteryEncounter.encounterVariant = MysteryEncounterVariant.BOSS_BATTLE;
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.BOSS_BATTLE;
}
} else {
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true);
@ -508,7 +508,7 @@ export function setEncounterExp(scene: BattleScene, participantId: integer | int
let expValue = Math.floor(baseExpValue * (useWaveIndex ? scene.currentBattle.waveIndex : 1) / 5 + 1);
if (participantIds?.length > 0) {
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) {
if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
expValue = Math.floor(expValue * 1.5);
}
for (const partyMember of nonFaintedPartyMembers) {
@ -609,7 +609,7 @@ export function initSubsequentOptionSelect(scene: BattleScene, optionSelectSetti
* @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available
*/
export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false) {
scene.currentBattle.mysteryEncounter.encounterVariant = MysteryEncounterVariant.NO_BATTLE;
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.NO_BATTLE;
scene.clearPhaseQueue();
scene.clearPhaseQueueSplice();
handleMysteryEncounterVictory(scene, addHealPhase);
@ -626,14 +626,14 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
// If in repeated encounter variant, do nothing
// Variant must eventually be swapped in order to handle "true" end of the encounter
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.CONTINUOUS_ENCOUNTER) {
if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.CONTINUOUS_ENCOUNTER) {
return;
} else if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.NO_BATTLE) {
} else if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
scene.pushPhase(new EggLapsePhase(scene));
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
} else if (!scene.getEnemyParty().find(p => scene.currentBattle.mysteryEncounter.encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
} else if (!scene.getEnemyParty().find(p => scene.currentBattle.mysteryEncounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
scene.pushPhase(new BattleEndPhase(scene));
if (scene.currentBattle.mysteryEncounter.encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) {
if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
scene.pushPhase(new TrainerVictoryPhase(scene));
}
if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) {
@ -693,7 +693,7 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide:
*/
export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {
const encounter = scene.currentBattle?.mysteryEncounter;
if (scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && encounter.encounterVariant !== MysteryEncounterVariant.NO_BATTLE && !encounter.startOfBattleEffectsComplete) {
if (scene.currentBattle.battleType === BattleType.MYSTERY_ENCOUNTER && encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE && !encounter.startOfBattleEffectsComplete) {
const effects = encounter.startOfBattleEffects;
effects.forEach(effect => {
let source;

View File

@ -0,0 +1,9 @@
export enum MysteryEncounterMode {
DEFAULT,
TRAINER_BATTLE,
WILD_BATTLE,
BOSS_BATTLE,
NO_BATTLE,
/** For spawning new encounter queries instead of continuing to next wave */
CONTINUOUS_ENCOUNTER
}

View File

@ -0,0 +1,10 @@
export enum MysteryEncounterOptionMode {
/** Default style */
DEFAULT,
/** Disabled on requirements not met, default style on requirements met */
DISABLED_OR_DEFAULT,
/** Default style on requirements not met, special style on requirements met */
DEFAULT_OR_SPECIAL,
/** Disabled on requirements not met, special style on requirements met */
DISABLED_OR_SPECIAL
}

View File

@ -0,0 +1,10 @@
/**
* Enum values are base spawn weights of each tier
*/
export enum MysteryEncounterTier {
COMMON = 64,
GREAT = 40,
ULTRA = 21,
ROGUE = 3,
MASTER = 0 // Not currently used
}

View File

@ -1,10 +1,10 @@
export const offerYouCantRefuseDialogue = {
intro: "You're stopped by a rich looking boy.",
speaker: "Rich Kid",
speaker: "Rich Boy",
intro_dialogue: `Good day to you.
$I can't help but notice that your\n{{strongestPokemon}} looks positively divine!
$I've always wanted to have a pet like that!
$I'd pay you handsomely, and\nI'll even give you this old bauble!`,
$I'd pay you handsomely,\nand also give you this old bauble!`,
title: "An Offer You Can't Refuse",
description: "You're being offered a @[TOOLTIP_TITLE]{Shiny Charm} and {{price, money}} for your {{strongestPokemon}}!\n\nIt's is an extremely good deal, but can you really bear to part with such a strong team member?",
query: "What will you do?",
@ -18,11 +18,10 @@ export const offerYouCantRefuseDialogue = {
},
2: {
label: "Extort the Kid",
tooltip: "(+) Gain {{price, money}}",
tooltip: "(+) {{option2PrimaryName}} uses {{moveOrAbility}}\n(+) Gain {{price, money}}",
tooltip_disabled: "Your Pokémon need to have certain moves or abilities to choose this",
selected: `My word, we're being robbed, Liepard!
$How wonderful!
$Now I'll have an amazing\nstory for the yacht club!`,
$You'll be hearing from my lawyers for this!`,
},
3: {
label: "Leave",

View File

@ -9,7 +9,7 @@ import { PokeballType } from "./data/pokeball";
import { Gender } from "./data/gender";
import { StatusEffect } from "./data/status-effect";
import { modifierTypes, SpeciesStatBoosterItem } from "./modifier/modifier-type";
import { VariantTier } from "./enums/variant-tiers";
import { VariantTier } from "#enums/variant-tiers";
import { EggTier } from "#enums/egg-type";
import { Abilities } from "#enums/abilities";
import { BerryType } from "#enums/berry-type";
@ -18,7 +18,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; // eslint-disable-line @typescript-eslint/no-unused-vars
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter"; // eslint-disable-line @typescript-eslint/no-unused-vars
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; // eslint-disable-line @typescript-eslint/no-unused-vars
/**
* Overrides for testing different in game situations
@ -117,9 +117,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;
*/
// 1 to 256, set to null to ignore
export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256;
export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null;
export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.OFFER_YOU_CANT_REFUSE;
export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null;
/**
* MODIFIER / ITEM OVERRIDES

View File

@ -65,11 +65,11 @@ import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { doTrainerExclamation, handleMysteryEncounterBattleStartEffects, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
const { t } = i18next;
@ -866,7 +866,7 @@ export class EncounterPhase extends BattlePhase {
}
if (!this.loaded) {
this.scene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER || battle?.mysteryEncounter?.encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE);
this.scene.gameData.setPokemonSeen(enemyPokemon, true, battle.battleType === BattleType.TRAINER || battle?.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE);
}
if (enemyPokemon.species.speciesId === Species.ETERNATUS) {
@ -1550,7 +1550,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
onComplete: () => this.scene.trainer.setVisible(false)
});
this.scene.time.delayedCall(750, () => this.summon());
} else if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene?.currentBattle?.mysteryEncounter?.encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) {
} else if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene?.currentBattle?.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
const trainerName = this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
const pokemonName = this.getPokemon().name;
const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName });

View File

@ -5,7 +5,6 @@ import { Mode } from "../ui/ui";
import { transitionMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterVariant } from "../data/mystery-encounters/mystery-encounter";
import { getCharVariantFromDialogue } from "../data/dialogue";
import { TrainerSlot } from "../data/trainer-config";
import { BattleSpec } from "#enums/battle-spec";
@ -15,6 +14,7 @@ import * as Utils from "../utils";
import { isNullOrUndefined } from "../utils";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { BattlerTagLapseType } from "#app/data/battler-tags";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
/**
* Will handle (in order):
@ -210,13 +210,13 @@ export class MysteryEncounterBattlePhase extends Phase {
getBattleMessage(scene: BattleScene): string {
const enemyField = scene.getEnemyField();
const encounterVariant = scene.currentBattle.mysteryEncounter.encounterVariant;
const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
if (scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
return i18next.t("battle:bossAppeared", { bossName: enemyField[0].name });
}
if (encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) {
if (encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (scene.currentBattle.double) {
return i18next.t("battle:trainerAppearedDouble", { trainerName: scene.currentBattle.trainer.getName(TrainerSlot.NONE, true) });
@ -231,10 +231,10 @@ export class MysteryEncounterBattlePhase extends Phase {
}
doMysteryEncounterBattle(scene: BattleScene) {
const encounterVariant = scene.currentBattle.mysteryEncounter.encounterVariant;
if (encounterVariant === MysteryEncounterVariant.WILD_BATTLE || encounterVariant === MysteryEncounterVariant.BOSS_BATTLE) {
const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
if (encounterMode === MysteryEncounterMode.WILD_BATTLE || encounterMode === MysteryEncounterMode.BOSS_BATTLE) {
// Summons the wild/boss Pokemon
if (encounterVariant === MysteryEncounterVariant.BOSS_BATTLE) {
if (encounterMode === MysteryEncounterMode.BOSS_BATTLE) {
scene.playBgm(undefined);
}
const availablePartyMembers = scene.getEnemyParty().filter(p => !p.isFainted()).length;
@ -248,7 +248,7 @@ export class MysteryEncounterBattlePhase extends Phase {
} else {
this.endBattleSetup(scene);
}
} else if (encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) {
} else if (encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
this.showEnemyTrainer();
const doSummon = () => {
scene.currentBattle.started = true;
@ -296,11 +296,11 @@ export class MysteryEncounterBattlePhase extends Phase {
endBattleSetup(scene: BattleScene) {
const enemyField = scene.getEnemyField();
const encounterVariant = scene.currentBattle.mysteryEncounter.encounterVariant;
const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
// PostSummon and ShinySparkle phases are handled by SummonPhase
if (encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE) {
if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE) {
const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))));
@ -327,7 +327,7 @@ export class MysteryEncounterBattlePhase extends Phase {
scene.pushPhase(new ToggleDoublePositionPhase(scene, false));
}
if (encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE && !this.disableSwitch) {
if (encounterMode !== MysteryEncounterMode.TRAINER_BATTLE && !this.disableSwitch) {
const minPartySize = scene.currentBattle.double ? 2 : 1;
if (availablePartyMembers.length > minPartySize) {
scene.pushPhase(new CheckSwitchPhase(scene, 0, scene.currentBattle.double));

View File

@ -13,6 +13,53 @@ import { Status, StatusEffect } from "#app/data/status-effect";
* @param optionNo - human number, not index
* @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);
// run the selected options phase
game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// If a battle is started, fast forward to end of the battle
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.clearPhaseQueue();
game.scene.clearPhaseQueueSplice();
game.scene.unshiftPhase(new VictoryPhase(game.scene, 0));
game.endPhase();
});
// Handle end of battle trainer messages
game.onNextPrompt("TrainerVictoryPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// Handle egg hatch dialogue
game.onNextPrompt("EggLapsePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// Handle modifier reward dialogue
game.onNextPrompt("ModifierRewardPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
game.onNextPrompt("SelectModifier", Mode.MODIFIER_SELECT, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
if (isBattle) {
await game.phaseInterceptor.to(CommandPhase);
} else {
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
}
}
export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, isBattle: boolean = false) {
// Handle any eventual queued messages (e.g. weather phase, etc.)
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
@ -53,38 +100,6 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
}
uiHandler.processInput(Button.ACTION);
// run the selected options phase
game.onNextPrompt("MysteryEncounterOptionSelectedPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MysteryEncounterUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// If a battle is started, fast forward to end of the battle
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.clearPhaseQueue();
game.scene.clearPhaseQueueSplice();
game.scene.unshiftPhase(new VictoryPhase(game.scene, 0));
game.endPhase();
});
// Handle end of battle trainer messages
game.onNextPrompt("TrainerVictoryPhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
// Handle egg hatch dialogue
game.onNextPrompt("EggLapsePhase", Mode.MESSAGE, () => {
const uiHandler = game.scene.ui.getHandler<MessageUiHandler>();
uiHandler.processInput(Button.ACTION);
});
if (isBattle) {
await game.phaseInterceptor.to(CommandPhase);
} else {
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
}
}
/**

View File

@ -5,15 +5,15 @@ 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 { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import { runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
import { SelectModifierPhase } from "#app/phases";
import BattleScene from "#app/battle-scene";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale-encounter";
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
const namespace = "mysteryEncounter:departmentStoreSale";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -98,7 +98,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 1 - TM Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[0];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
@ -108,7 +108,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only TMs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -124,7 +124,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -133,7 +133,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 2 - Vitamin Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[1];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
@ -143,7 +143,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Vitamins", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -160,7 +160,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -169,7 +169,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 3 - X Item Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[2];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`,
@ -179,7 +179,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only X Items", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 3);
await runMysteryEncounterToEnd(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -196,7 +196,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 3);
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -205,7 +205,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 4 - Pokeball Shop", () => {
it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[3];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:4:label`,
@ -215,7 +215,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Pokeballs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 4);
await runMysteryEncounterToEnd(game, 4);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
@ -231,7 +231,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 4);
await runMysteryEncounterToEnd(game, 4);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});

View File

@ -5,20 +5,21 @@ import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
import Battle from "#app/battle";
import { Gender } from "#app/data/gender";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import { runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import BattleScene from "#app/battle-scene";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { Type } from "#app/data/type";
import { Status, StatusEffect } from "#app/data/status-effect";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
const namespace = "mysteryEncounter:fieryFallout";
/** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */
@ -94,7 +95,8 @@ describe("Fiery Fallout - Mystery Encounter", () => {
});
it("should initialize fully ", async () => {
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: FieryFalloutEncounter } as Battle);
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = FieryFalloutEncounter;
const weatherSpy = vi.spyOn(scene.arena, "trySetWeather").mockReturnValue(true);
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
@ -103,6 +105,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
expect(FieryFalloutEncounter.onInit).toBeDefined();
FieryFalloutEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
expect(FieryFalloutEncounter.enemyPartyConfigs).toEqual([
@ -132,7 +135,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
describe("Option 1 - Fight 2 Volcarona", () => {
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
@ -149,7 +152,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 1, true);
await runMysteryEncounterToEnd(game, 1, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
@ -166,7 +169,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
it("should give charcoal to lead pokemon", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 1, true);
await runMysteryEncounterToEnd(game, 1, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
@ -182,7 +185,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
describe("Option 2 - Suffer the weather", () => {
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[1];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
@ -204,7 +207,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE));
const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE));
@ -220,7 +223,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -229,7 +232,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
describe("Option 3 - use FIRE types", () => {
it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[2];
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_SPECIAL);
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`,
@ -245,7 +248,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
it("should give charcoal to lead pokemon", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 3);
await runMysteryEncounterToEnd(game, 3);
// await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
@ -261,9 +264,23 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 3);
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
it("should be disabled if not enough FIRE types are in party", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, [Species.MAGIKARP, Species.ARCANINE]);
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
const encounterPhase = scene.getCurrentPhase();
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
const continueEncounterSpy = vi.spyOn((encounterPhase as MysteryEncounterPhase), "continueEncounter");
await runSelectMysteryEncounterOption(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
expect(continueEncounterSpy).not.toHaveBeenCalled();
});
});
});

View File

@ -1,6 +1,4 @@
import Battle from "#app/battle";
import { LostAtSeaEncounter } from "#app/data/mystery-encounters/encounters/lost-at-sea-encounter";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species.js";
@ -10,8 +8,11 @@ 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 { runSelectMysteryEncounterOption } from "../encounterTestUtils";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { runMysteryEncounterToEnd } from "../encounterTestUtils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import BattleScene from "#app/battle-scene";
const namespace = "mysteryEncounter:lostAtSea";
/** Blastoise for surf. Pidgeot for fly. Abra for none. */
@ -22,6 +23,7 @@ const defaultWave = 33;
describe("Lost at Sea - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
@ -29,6 +31,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
@ -83,14 +86,16 @@ describe("Lost at Sea - Mystery Encounter", () => {
expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should set the correct dialog tokens during initialization", () => {
vi.spyOn(game.scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: LostAtSeaEncounter } as Battle);
it("should initialize fully", () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = LostAtSeaEncounter;
const { onInit } = LostAtSeaEncounter;
expect(LostAtSeaEncounter.onInit).toBeDefined();
const onInitResult = onInit(game.scene);
LostAtSeaEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25");
expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(Moves[Moves.SURF]);
@ -101,7 +106,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
describe("Option 1 - Surf", () => {
it("should have the correct properties", () => {
const option1 = LostAtSeaEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT);
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
@ -124,7 +129,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
const expBefore = blastoise.exp;
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
expect(blastoise.exp).toBe(expBefore + Math.floor(laprasSpecies.baseExp * defaultWave / 5 + 1));
});
@ -134,7 +139,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -148,7 +153,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
it("should have the correct properties", () => {
const option2 = LostAtSeaEncounter.options[1];
expect(option2.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT);
expect(option2.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT);
expect(option2.dialogue).toBeDefined();
expect(option2.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
@ -173,7 +178,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
const expBefore = pidgeot.exp;
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
expect(pidgeot.exp).toBe(expBefore + Math.floor(laprasBaseExp * defaultWave / 5 + 1));
});
@ -183,7 +188,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -197,7 +202,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
it("should have the correct properties", () => {
const option3 = LostAtSeaEncounter.options[2];
expect(option3.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option3.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option3.dialogue).toBeDefined();
expect(option3.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`,
@ -219,7 +224,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
await runSelectMysteryEncounterOption(game, 3);
await runMysteryEncounterToEnd(game, 3);
const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle());
const notAllowedPkm = party.filter((pkm) => !pkm.isAllowedInBattle());
@ -235,7 +240,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
await runSelectMysteryEncounterOption(game, 3);
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});

View File

@ -0,0 +1,244 @@
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
import { Biome } from "#app/enums/biome";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
import BattleScene from "#app/battle-scene";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { OfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/offer-you-cant-refuse-encounter";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
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";
const namespace = "mysteryEncounter:offerYouCantRefuse";
/** Gyarados for Indimidate */
const defaultParty = [Species.GYARADOS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.CAVE;
const defaultWave = 45;
describe("An Offer You Can't Refuse - 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);
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
]);
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
biomeMap.set(biome, [MysteryEncounterType.OFFER_YOU_CANT_REFUSE]);
});
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
expect(OfferYouCantRefuseEncounter.encounterType).toBe(MysteryEncounterType.OFFER_YOU_CANT_REFUSE);
expect(OfferYouCantRefuseEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT);
expect(OfferYouCantRefuseEncounter.dialogue).toBeDefined();
expect(OfferYouCantRefuseEncounter.dialogue.intro).toStrictEqual([
{ text: `${namespace}:intro` },
{ speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue` }
]);
expect(OfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
expect(OfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
expect(OfferYouCantRefuseEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
expect(OfferYouCantRefuseEncounter.options.length).toBe(3);
});
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
game.override.startingBiome(Biome.VOLCANO);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.OFFER_YOU_CANT_REFUSE);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.OFFER_YOU_CANT_REFUSE);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully ", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = OfferYouCantRefuseEncounter;
const { onInit } = OfferYouCantRefuseEncounter;
expect(OfferYouCantRefuseEncounter.onInit).toBeDefined();
OfferYouCantRefuseEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
expect(OfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined();
expect(OfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined();
expect(OfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe("Intimidate");
expect(OfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe("Intimidate");
expect(OfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy();
expect(OfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(OfferYouCantRefuseEncounter.dialogueTokens?.price);
expect(onInitResult).toBe(true);
});
describe("Option 1 - Sell your Pokemon for money and a Shiny Charm", () => {
it("should have the correct properties", () => {
const option = OfferYouCantRefuseEncounter.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 update the player's money properly", async () => {
const initialMoney = 20000;
scene.money = initialMoney;
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
const price = scene.currentBattle.mysteryEncounter.misc.price;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, price);
expect(scene.money).toBe(initialMoney + price);
});
it("Should remove the Pokemon from the party", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
const initialPartySize = scene.getParty().length;
const pokemonName = scene.currentBattle.mysteryEncounter.misc.pokemon.name;
await runMysteryEncounterToEnd(game, 1);
expect(scene.getParty().length).toBe(initialPartySize - 1);
expect(scene.getParty().find(p => p.name === pokemonName)).toBeUndefined();
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 2 - Extort the Kid", () => {
it("should have the correct properties", () => {
const option = OfferYouCantRefuseEncounter.options[1];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`,
disabledButtonTooltip: `${namespace}:option:2:tooltip_disabled`,
selected: [
{
speaker: `${namespace}:speaker`,
text: `${namespace}:option:2:selected`,
},
],
});
});
it("should award EXP to a pokemon with an ability in EXTORTION_ABILITIES", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
const party = scene.getParty();
const gyarados = party.find((pkm) => pkm.species.speciesId === Species.GYARADOS);
const expBefore = gyarados.exp;
await runMysteryEncounterToEnd(game, 2);
expect(gyarados.exp).toBe(expBefore + Math.floor(getPokemonSpecies(Species.LIEPARD).baseExp * defaultWave / 5 + 1));
});
it("should award EXP to a pokemon with a move in EXTORTION_MOVES", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, [Species.ABRA]);
const party = scene.getParty();
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
abra.moveset = [new PokemonMove(Moves.BEAT_UP)];
const expBefore = abra.exp;
await runMysteryEncounterToEnd(game, 2);
expect(abra.exp).toBe(expBefore + Math.floor(getPokemonSpecies(Species.LIEPARD).baseExp * defaultWave / 5 + 1));
});
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.OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
const price = scene.currentBattle.mysteryEncounter.misc.price;
expect(updateMoneySpy).toHaveBeenCalledWith(scene, price);
expect(scene.money).toBe(initialMoney + price);
});
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
describe("Option 3 - Leave", () => {
it("should leave encounter without battle", async () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.OFFER_YOU_CANT_REFUSE, defaultParty);
await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
});
});

View File

@ -4,15 +4,15 @@ 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 Battle from "#app/battle";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import { runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
import BattleScene from "#app/battle-scene";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { PlayerPokemon } from "#app/field/pokemon";
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
const namespace = "mysteryEncounter:pokemonSalesman";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -59,7 +59,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
expect(PokemonSalesmanEncounter.dialogue).toBeDefined();
expect(PokemonSalesmanEncounter.dialogue.intro).toStrictEqual([
{ text: `${namespace}:intro` },
{ speaker: "mysteryEncounter:pokemonSalesman:speaker", text: "mysteryEncounter:pokemonSalesman:intro_dialogue" }
{ speaker: `${namespace}:speaker`, text: `${namespace}:intro_dialogue` }
]);
expect(PokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
expect(PokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
@ -91,12 +91,14 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
});
it("should initialize fully ", async () => {
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: PokemonSalesmanEncounter } as Battle);
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = PokemonSalesmanEncounter;
const { onInit } = PokemonSalesmanEncounter;
expect(PokemonSalesmanEncounter.onInit).toBeDefined();
PokemonSalesmanEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
expect(PokemonSalesmanEncounter.dialogueTokens?.purchasePokemon).toBeDefined();
@ -109,7 +111,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
describe("Option 1 - Purchase the pokemon", () => {
it("should have the correct properties", () => {
const option1 = PokemonSalesmanEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT_OR_SPECIAL);
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
@ -128,7 +130,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
const price = scene.currentBattle.mysteryEncounter.misc.price;
@ -142,7 +144,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
const initialPartySize = scene.getParty().length;
const pokemonName = scene.currentBattle.mysteryEncounter.misc.pokemon.name;
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
expect(scene.getParty().length).toBe(initialPartySize + 1);
expect(scene.getParty().find(p => p.name === pokemonName) instanceof PlayerPokemon).toBeTruthy();
@ -152,7 +154,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -163,7 +165,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
await runSelectMysteryEncounterOption(game, 2);
await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});

View File

@ -4,17 +4,14 @@ 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 Battle from "#app/battle";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
import { runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import BattleScene from "#app/battle-scene";
import * as Modifiers from "#app/modifier/modifier";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
import { Nature } from "#app/data/nature";
import { BerryType } from "#enums/berry-type";
@ -23,6 +20,9 @@ import { PokemonMove } from "#app/field/pokemon";
import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { PokemonBaseStatTotalModifier } from "#app/modifier/modifier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
const namespace = "mysteryEncounter:theStrongStuff";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -97,7 +97,8 @@ describe("The Strong Stuff - Mystery Encounter", () => {
});
it("should initialize fully ", async () => {
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: TheStrongStuffEncounter } as Battle);
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = TheStrongStuffEncounter;
const moveInitSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
@ -105,6 +106,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
expect(TheStrongStuffEncounter.onInit).toBeDefined();
TheStrongStuffEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
expect(TheStrongStuffEncounter.enemyPartyConfigs).toEqual([
@ -134,7 +136,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
describe("Option 1 - Power Swap BSTs", () => {
it("should have the correct properties", () => {
const option1 = TheStrongStuffEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`,
@ -151,7 +153,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
const bstsPrior = scene.getParty().map(p => p.getSpeciesForm().getBaseStatTotal());
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
const bstsAfter = scene.getParty().map(p => {
const baseStats = p.getSpeciesForm().baseStats.slice(0);
@ -168,7 +170,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runSelectMysteryEncounterOption(game, 1);
await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
});
@ -177,7 +179,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
describe("Option 2 - battle the Shuckle", () => {
it("should have the correct properties", () => {
const option1 = TheStrongStuffEncounter.options[1];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`,
@ -194,7 +196,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
await runSelectMysteryEncounterOption(game, 2, true);
await runMysteryEncounterToEnd(game, 2, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
@ -218,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 runSelectMysteryEncounterOption(game, 2, true);
await runMysteryEncounterToEnd(game, 2, true);
await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);

View File

@ -7,8 +7,8 @@ import {Mode} from "#app/ui/ui";
import {Button} from "#enums/buttons";
import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler";
import {MysteryEncounterType} from "#enums/mystery-encounter-type";
import {MysteryEncounterTier} from "#app/data/mystery-encounters/mystery-encounter";
import MessageUiHandler from "#app/ui/message-ui-handler";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
describe("Mystery Encounter Phases", () => {
let phaserGame: Phaser.Game;

View File

@ -5,9 +5,9 @@ import { MockInstance, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import * as overrides from "#app/overrides";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import * as GameMode from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/**
* Helper to handle overrides in tests

View File

@ -11,13 +11,15 @@ import {
LearnMovePhase,
LoginPhase,
MessagePhase,
ModifierRewardPhase,
MoveEffectPhase,
MoveEndPhase,
MovePhase,
NewBattlePhase,
NextEncounterPhase,
PostSummonPhase,
SelectGenderPhase, SelectModifierPhase,
SelectGenderPhase,
SelectModifierPhase,
SelectStarterPhase,
SelectTargetPhase,
ShinySparklePhase,
@ -105,6 +107,7 @@ export default class PhaseInterceptor {
[MysteryEncounterRewardsPhase, this.startPhase],
[PostMysteryEncounterPhase, this.startPhase],
[LearnMovePhase, this.startPhase],
[ModifierRewardPhase, this.startPhase],
// [CommonAnimPhase, this.startPhase]
];

View File

@ -6,14 +6,15 @@ import { Button } from "#enums/buttons";
import { addWindow, WindowVariant } from "./ui-theme";
import { MysteryEncounterPhase } from "../phases/mystery-encounter-phases";
import { PartyUiMode } from "./party-ui-handler";
import MysteryEncounterOption, { EncounterOptionMode } from "../data/mystery-encounters/mystery-encounter-option";
import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option";
import * as Utils from "../utils";
import { isNullOrUndefined } from "../utils";
import { getPokeballAtlasKey } from "../data/pokeball";
import { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import i18next from "i18next";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export default class MysteryEncounterUiHandler extends UiHandler {
private cursorContainer: Phaser.GameObjects.Container;
@ -146,7 +147,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.unblockInput();
}, 300);
});
} else if (this.blockInput || (!this.optionsMeetsReqs[cursor] && (selected.optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || selected.optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL))) {
} else if (this.blockInput || (!this.optionsMeetsReqs[cursor] && (selected.optionMode === MysteryEncounterOptionMode.DISABLED_OR_DEFAULT || selected.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL))) {
success = false;
} else {
if ((this.scene.getCurrentPhase() as MysteryEncounterPhase).handleOptionSelect(selected, cursor)) {
@ -290,7 +291,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
this.blockInput = false;
for (let i = 0; i < this.optionsContainer.length - 1; i++) {
const optionMode = this.encounterOptions[i].optionMode;
if (!this.optionsMeetsReqs[i] && (optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL)) {
if (!this.optionsMeetsReqs[i] && (optionMode === MysteryEncounterOptionMode.DISABLED_OR_DEFAULT || optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) {
continue;
}
(this.optionsContainer.getAt(i) as Phaser.GameObjects.Text).setAlpha(1);
@ -363,7 +364,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
const optionDialogue = option.dialogue;
const label = !this.optionsMeetsReqs[i] && optionDialogue.disabledButtonLabel ? optionDialogue.disabledButtonLabel : optionDialogue.buttonLabel;
let text: string;
if (option.hasRequirements() && this.optionsMeetsReqs[i] && (option.optionMode === EncounterOptionMode.DEFAULT_OR_SPECIAL || option.optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL)) {
if (option.hasRequirements() && this.optionsMeetsReqs[i] && (option.optionMode === MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL || option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) {
// Options with special requirements that are met are automatically colored green
text = getEncounterText(this.scene, label, TextStyle.SUMMARY_GREEN);
} else {
@ -374,7 +375,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
optionText.setText(text);
}
if (!this.optionsMeetsReqs[i] && (option.optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || option.optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL)) {
if (!this.optionsMeetsReqs[i] && (option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_DEFAULT || option.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)) {
optionText.setAlpha(0.5);
}
if (this.blockInput) {
@ -468,7 +469,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
let text: string;
const cursorOption = this.encounterOptions[cursor];
const optionDialogue = cursorOption.dialogue;
if (!this.optionsMeetsReqs[cursor] && (cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_DEFAULT || cursorOption.optionMode === EncounterOptionMode.DISABLED_OR_SPECIAL) && optionDialogue.disabledButtonTooltip) {
if (!this.optionsMeetsReqs[cursor] && (cursorOption.optionMode === MysteryEncounterOptionMode.DISABLED_OR_DEFAULT || cursorOption.optionMode === MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) && optionDialogue.disabledButtonTooltip) {
text = getEncounterText(this.scene, optionDialogue.disabledButtonTooltip, TextStyle.TOOLTIP_CONTENT);
} else {
text = getEncounterText(this.scene, optionDialogue.buttonTooltip, TextStyle.TOOLTIP_CONTENT);