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 { UiTheme } from "#enums/ui-theme";
import { TimedEventManager } from "#app/timed-event-manager.js"; import { TimedEventManager } from "#app/timed-event-manager.js";
import i18next from "i18next"; 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 { 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 { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; 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"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -1184,7 +1186,7 @@ export default class BattleScene extends SceneBase {
this.arena.removeAllTags(); this.arena.removeAllTags();
// If last battle was mystery encounter and no battle occurred, skip return phases // 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))); playerField.forEach((_, p) => this.unshiftPhase(new ReturnPhase(this, p)));
for (const pokemon of this.getParty()) { for (const pokemon of this.getParty()) {

View File

@ -14,7 +14,8 @@ import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import i18next from "#app/plugins/i18n"; 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 { export enum BattleType {
WILD, WILD,
@ -205,7 +206,7 @@ export default class Battle {
getBgmOverride(scene: BattleScene): string { getBgmOverride(scene: BattleScene): string {
const battlers = this.enemyParty.slice(0, this.getBattlerCount()); 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) { if (!this.started && this.trainer.config.encounterBgm && this.trainer.getEncounterMessages().length) {
return `encounter_${this.trainer.getEncounterBgm()}`; return `encounter_${this.trainer.getEncounterBgm()}`;
} }

View File

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

View File

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

View File

@ -1,22 +1,15 @@
import { MoveCategory } from "#app/data/move"; import { MoveCategory } from "#app/data/move";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
generateModifierTypeOption,
leaveEncounterWithoutBattle,
selectPokemonForOption,
setEncounterExp,
setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { TempBattleStat } from "#app/data/temp-battle-stat"; import { TempBattleStat } from "#app/data/temp-battle-stat";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
MysteryEncounterBuilder, import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
MysteryEncounterTier, import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
} from "../mystery-encounter";
/** i18n namespace for the encounter */ /** i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieldTrip"; const namespace = "mysteryEncounter:fieldTrip";
@ -62,7 +55,7 @@ export const FieldTripEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`, buttonTooltip: `${namespace}:option:1:tooltip`,
@ -88,7 +81,7 @@ export const FieldTripEncounter: IMysteryEncounter =
encounter.options[0].dialogue.selected = [ encounter.options[0].dialogue.selected = [
{ {
text: `${namespace}:option:incorrect`, text: `${namespace}:option:incorrect`,
speaker: `${namespace}:option:speaker`, speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}:option:lesson_learned`, text: `${namespace}:option:lesson_learned`,
@ -148,7 +141,7 @@ export const FieldTripEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:2:label`, buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`, buttonTooltip: `${namespace}:option:2:tooltip`,
@ -174,7 +167,7 @@ export const FieldTripEncounter: IMysteryEncounter =
encounter.options[1].dialogue.selected = [ encounter.options[1].dialogue.selected = [
{ {
text: `${namespace}:option:incorrect`, text: `${namespace}:option:incorrect`,
speaker: `${namespace}:option:speaker`, speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}:option:lesson_learned`, text: `${namespace}:option:lesson_learned`,
@ -240,7 +233,7 @@ export const FieldTripEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:3:label`, buttonLabel: `${namespace}:option:3:label`,
buttonTooltip: `${namespace}:option:3:tooltip`, buttonTooltip: `${namespace}:option:3:tooltip`,
@ -266,7 +259,7 @@ export const FieldTripEncounter: IMysteryEncounter =
encounter.options[2].dialogue.selected = [ encounter.options[2].dialogue.selected = [
{ {
text: `${namespace}:option:incorrect`, text: `${namespace}:option:incorrect`,
speaker: `${namespace}:option:speaker`, speaker: `${namespace}:speaker`,
}, },
{ {
text: `${namespace}:option:lesson_learned`, 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 { 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 { modifierTypes, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { TypeRequirement } from "../mystery-encounter-requirements"; import { TypeRequirement } from "../mystery-encounter-requirements";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-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 { StatusEffect } from "#app/data/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-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 */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieryFallout"; const namespace = "mysteryEncounter:fieryFallout";
@ -202,7 +204,7 @@ export const FieryFalloutEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() 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 .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 .withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically
.withDialogue({ .withDialogue({

View File

@ -1,5 +1,5 @@
import { BattleStat } from "#app/data/battle-stat"; 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 { import {
EnemyPartyConfig, EnemyPartyConfig,
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
@ -20,14 +20,13 @@ import { StatChangePhase } from "#app/phases";
import { randSeedInt } from "#app/utils"; import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
MysteryEncounterBuilder,
MysteryEncounterTier,
} from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements"; import { MoveRequirement } from "../mystery-encounter-requirements";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages"; 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 */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fightOrFlight"; const namespace = "mysteryEncounter:fightOrFlight";
@ -132,7 +131,7 @@ export const FightOrFlightEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() 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 .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:2:label`, 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 { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species.js"; import { Species } from "#app/enums/species.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter"; import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils"; import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-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_1_REQUIRED_MOVE = Moves.SURF;
const OPTION_2_REQUIRED_MOVE = Moves.FLY; 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/ // Option 1: Use a (non fainted) pokemon that can learn Surf to guide you back/
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE) .withPokemonCanLearnMoveRequirement(OPTION_1_REQUIRED_MOVE)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
disabledButtonLabel: `${namespace}:option:1:label_disabled`, 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. //Option 2: Use a (non fainted) pokemon that can learn fly to guide you back.
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE) .withPokemonCanLearnMoveRequirement(OPTION_2_REQUIRED_MOVE)
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:2:label`, buttonLabel: `${namespace}:option:2:label`,
disabledButtonLabel: `${namespace}:option:2:label_disabled`, 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 { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import * as Utils from "../../../utils"; import * as Utils from "#app/utils";
import IMysteryEncounter, { import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
MysteryEncounterBuilder, import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
MysteryEncounterTier,
} from "../mystery-encounter";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:mysteriousChallengers"; 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 { ModifierTier } from "#app/modifier/modifier-tier";
import { randSeedInt } from "#app/utils.js"; import { randSeedInt } from "#app/utils.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; 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 */ /** i18n namespace for encounter */
const namespace = "mysteryEncounter:mysteriousChest"; const namespace = "mysteryEncounter:mysteriousChest";
@ -42,7 +44,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`, buttonTooltip: `${namespace}:option:1:tooltip`,

View File

@ -2,14 +2,16 @@ import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements"; import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { ModifierRewardPhase } from "#app/phases"; 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 { 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 */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse"; const namespace = "mysteryEncounter:offerYouCantRefuse";
@ -56,22 +58,36 @@ export const OfferYouCantRefuseEncounter: IMysteryEncounter =
.withDescription(`${namespace}:description`) .withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = getHighestStatTotalPlayerPokemon(scene, false); const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
const price = scene.getWaveMoneyAmount(10); const price = scene.getWaveMoneyAmount(10);
scene.currentBattle.mysteryEncounter.setDialogueToken("strongestPokemon", pokemon.name); encounter.setDialogueToken("strongestPokemon", pokemon.name);
scene.currentBattle.mysteryEncounter.setDialogueToken("price", price.toString()); encounter.setDialogueToken("price", price.toString());
// Store pokemon and price // Store pokemon and price
scene.currentBattle.mysteryEncounter.misc = { encounter.misc = {
pokemon: pokemon, pokemon: pokemon,
price: price 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; return true;
}) })
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
buttonTooltip: `${namespace}:option:1:tooltip`, buttonTooltip: `${namespace}:option:1:tooltip`,
@ -98,14 +114,18 @@ export const OfferYouCantRefuseEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(EXTORTION_MOVES)) .withPrimaryPokemonRequirement(new CombinationPokemonRequirement(
new MoveRequirement(EXTORTION_MOVES),
new AbilityRequirement(EXTORTION_ABILITIES))
)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:2:label`, buttonLabel: `${namespace}:option:2:label`,
buttonTooltip: `${namespace}:option:2:tooltip`, buttonTooltip: `${namespace}:option:2:tooltip`,
disabledButtonTooltip: `${namespace}:option:2:tooltip_disabled`, disabledButtonTooltip: `${namespace}:option:2:tooltip_disabled`,
selected: [ selected: [
{ {
speaker: `${namespace}:speaker`,
text: `${namespace}:option:2:selected`, text: `${namespace}:option:2:selected`,
}, },
], ],
@ -129,7 +149,7 @@ export const OfferYouCantRefuseEncounter: IMysteryEncounter =
selected: [ selected: [
{ {
speaker: `${namespace}:speaker`, 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 { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoneyRequirement } from "../mystery-encounter-requirements"; import { MoneyRequirement } from "../mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { catchPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { PokeballType } from "#app/data/pokeball"; import { PokeballType } from "#app/data/pokeball";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; 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 { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data"; 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 */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:pokemonSalesman"; const namespace = "mysteryEncounter:pokemonSalesman";
@ -104,7 +106,7 @@ export const PokemonSalesmanEncounter: IMysteryEncounter =
}) })
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT_OR_SPECIAL) .withOptionMode(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL)
.withHasDexProgress(true) .withHasDexProgress(true)
.withSceneMoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2 .withSceneMoneyRequirement(null, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({ .withDialogue({

View File

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

View File

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

View File

@ -4,8 +4,8 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect"; import { StatusEffect } from "#app/data/status-effect";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements"; import { MoveRequirement } from "../mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, initCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, initCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-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 { PokemonMove } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { PartyHealPhase } from "#app/phases"; 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 */ /** i18n namespace for the encounter */
const namespace = "mysteryEncounter:slumberingSnorlax"; const namespace = "mysteryEncounter:slumberingSnorlax";
@ -121,7 +123,7 @@ export const SlumberingSnorlaxEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL) .withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES))
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:3:label`, 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 { 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 { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Nature } from "#app/data/nature"; import { Nature } from "#app/data/nature";
@ -15,6 +15,7 @@ import { StatChangePhase } from "#app/phases";
import { BattleStat } from "#app/data/battle-stat"; import { BattleStat } from "#app/data/battle-stat";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theStrongStuff"; 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 { randSeedShuffle } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 */ /** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:trainingSession"; const namespace = "mysteryEncounter:trainingSession";
@ -52,7 +54,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
.withQuery(`${namespace}:query`) .withQuery(`${namespace}:query`)
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
@ -196,7 +198,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:2:label`, buttonLabel: `${namespace}:option:2:label`,
@ -289,7 +291,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
) )
.withOption( .withOption(
new MysteryEncounterOptionBuilder() new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}:option:3:label`, 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters"; import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export class MysteryEncounterData { export class MysteryEncounterData {
encounteredEvents: [MysteryEncounterType, MysteryEncounterTier][] = []; encounteredEvents: [MysteryEncounterType, MysteryEncounterTier][] = [];

View File

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

View File

@ -1,6 +1,6 @@
import { PlayerPokemon } from "#app/field/pokemon"; import { PlayerPokemon } from "#app/field/pokemon";
import { ModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; 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 { isNullOrUndefined } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -21,12 +21,35 @@ export interface EncounterRequirement {
} }
export abstract class EncounterSceneRequirement implements 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 { 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] { 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. * Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned.
* @param partyPokemon * @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[] { queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
for (const req of this.orRequirements) {
const result = req.queryParty(partyPokemon);
if (result?.length > 0) {
return result;
}
}
return []; return [];
} }
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { 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 * Used for specifying an encounter that must be seen before this encounter can spawn
* @param previousEncounterRequirement * @param previousEncounterRequirement
*/ */
constructor(previousEncounterRequirement) { constructor(previousEncounterRequirement: MysteryEncounterType) {
super(); super();
this.previousEncounterRequirement = previousEncounterRequirement; this.previousEncounterRequirement = previousEncounterRequirement;
} }
@ -497,19 +557,16 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) { 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 { } else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess // 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] { getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const reqAbilities = this.requiredAbilities.filter((a) => { if (this.requiredAbilities.some(a => pokemon.getAbility().id === a)) {
pokemon.hasAbility(a); return ["ability", pokemon.getAbility().name];
});
if (reqAbilities.length > 0) {
return ["ability", Abilities[reqAbilities[0]]];
} }
return null; 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 Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "../../battle-scene"; import BattleScene from "#app/battle-scene";
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "../../field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import * as Utils from "../../utils"; import * as Utils from "#app/utils";
import { StatusEffect } from "../status-effect"; import { StatusEffect } from "../status-effect";
import MysteryEncounterDialogue, { import MysteryEncounterDialogue, {
OptionTextDisplay OptionTextDisplay
} from "./mystery-encounter-dialogue"; } from "./mystery-encounter-dialogue";
import MysteryEncounterOption, { EncounterOptionMode, MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option"; import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
import { import {
EncounterPokemonRequirement, EncounterPokemonRequirement,
EncounterSceneRequirement, EncounterSceneRequirement,
@ -20,27 +20,9 @@ import {
} from "./mystery-encounter-requirements"; } from "./mystery-encounter-requirements";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { EncounterAnim } from "#app/data/battle-anims"; import { EncounterAnim } from "#app/data/battle-anims";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export enum MysteryEncounterVariant { import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
DEFAULT, import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
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
}
export interface StartOfBattleEffect { export interface StartOfBattleEffect {
sourcePokemon?: Pokemon; 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 * For example, if there is no battle as part of the encounter/selected option, should be set to NO_BATTLE
* Defaults to DEFAULT * Defaults to DEFAULT
*/ */
encounterVariant?: MysteryEncounterVariant; encounterMode?: MysteryEncounterMode;
/** /**
* Flag for checking if it's the first time a shop is being shown for an encounter. * 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. * 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 ?? {}; this.dialogue = this.dialogue ?? {};
// Default max is 1 for ROGUE encounters, 3 for others // Default max is 1 for ROGUE encounters, 3 for others
this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? 1 : 3; 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.requirements = this.requirements ? this.requirements : [];
this.hideBattleIntroMessage = !isNullOrUndefined(this.hideBattleIntroMessage) ? this.hideBattleIntroMessage : false; this.hideBattleIntroMessage = !isNullOrUndefined(this.hideBattleIntroMessage) ? this.hideBattleIntroMessage : false;
this.autoHideIntroVisuals = !isNullOrUndefined(this.autoHideIntroVisuals) ? this.autoHideIntroVisuals : true; this.autoHideIntroVisuals = !isNullOrUndefined(this.autoHideIntroVisuals) ? this.autoHideIntroVisuals : true;
@ -314,7 +296,9 @@ export default class IMysteryEncounter implements IMysteryEncounter {
if (this.requirements?.length > 0) { if (this.requirements?.length > 0) {
for (const req of this.requirements) { for (const req of this.requirements) {
const dialogueToken = req.getDialogueToken(scene); const dialogueToken = req.getDialogueToken(scene);
this.setDialogueToken(...dialogueToken); if (dialogueToken?.length === 2) {
this.setDialogueToken(...dialogueToken);
}
} }
} }
if (this.primaryPokemon?.length > 0) { if (this.primaryPokemon?.length > 0) {
@ -322,7 +306,9 @@ export default class IMysteryEncounter implements IMysteryEncounter {
for (const req of this.primaryPokemonRequirements) { for (const req of this.primaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.primaryPokemon); const value = req.getDialogueToken(scene, this.primaryPokemon);
this.setDialogueToken("primary" + this.capitalizeFirstLetter(value[0]), value[1]); if (value?.length === 2) {
this.setDialogueToken("primary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
} }
} }
} }
@ -331,6 +317,9 @@ export default class IMysteryEncounter implements IMysteryEncounter {
for (const req of this.secondaryPokemonRequirements) { for (const req of this.secondaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]); 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]); this.setDialogueToken("secondary" + this.capitalizeFirstLetter(value[0]), value[1]);
} }
} }
@ -344,7 +333,9 @@ export default class IMysteryEncounter implements IMysteryEncounter {
if (opt.requirements?.length > 0) { if (opt.requirements?.length > 0) {
for (const req of opt.requirements) { for (const req of opt.requirements) {
const dialogueToken = req.getDialogueToken(scene); const dialogueToken = req.getDialogueToken(scene);
this.setDialogueToken("option" + j + this.capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]); if (dialogueToken?.length === 2) {
this.setDialogueToken("option" + j + this.capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]);
}
} }
} }
if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) { if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) {
@ -352,7 +343,9 @@ export default class IMysteryEncounter implements IMysteryEncounter {
for (const req of opt.primaryPokemonRequirements) { for (const req of opt.primaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.primaryPokemon); const value = req.getDialogueToken(scene, opt.primaryPokemon);
this.setDialogueToken("option" + j + "Primary" + this.capitalizeFirstLetter(value[0]), value[1]); if (value?.length === 2) {
this.setDialogueToken("option" + j + "Primary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
} }
} }
} }
@ -361,7 +354,9 @@ export default class IMysteryEncounter implements IMysteryEncounter {
for (const req of opt.secondaryPokemonRequirements) { for (const req of opt.secondaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]); const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);
this.setDialogueToken("option" + j + "Secondary" + this.capitalizeFirstLetter(value[0]), value[1]); if (value?.length === 2) {
this.setDialogueToken("option" + j + "Secondary" + this.capitalizeFirstLetter(value[0]), value[1]);
}
} }
} }
} }
@ -373,7 +368,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
} }
/** /**
* 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. * should rely on this value for seed offset instead of wave index.
* *
* This offset is incremented for each new {@link MysteryEncounterPhase} that occurs, * This offset is incremented for each new {@link MysteryEncounterPhase} that occurs,
@ -466,7 +461,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns * @returns
*/ */
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> { 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"> { withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
return this.withOption(new MysteryEncounterOptionBuilder() return this.withOption(new MysteryEncounterOptionBuilder()
.withOptionMode(EncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withDialogue(dialogue) .withDialogue(dialogue)
.withOptionPhase(callback).build()); .withOptionPhase(callback).build());
@ -592,6 +587,10 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @returns * @returns
*/ */
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> { withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
this.primaryPokemonRequirements.push(requirement); this.primaryPokemonRequirements.push(requirement);
return Object.assign(this, { primaryPokemonRequirements: this.primaryPokemonRequirements }); 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 // 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 // it's already been
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> { 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.secondaryPokemonRequirements.push(requirement);
this.excludePrimaryFromSupportRequirements = excludePrimaryFromSecondaryRequirements; this.excludePrimaryFromSupportRequirements = excludePrimaryFromSecondaryRequirements;
return Object.assign(this, { excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements, secondaryPokemonRequirements: this.secondaryPokemonRequirements }); return Object.assign(this, { excludePrimaryFromSecondaryRequirements: this.excludePrimaryFromSupportRequirements, secondaryPokemonRequirements: this.secondaryPokemonRequirements });

View File

@ -61,7 +61,7 @@ export const EXTORTION_MOVES = [
Moves.BEAT_UP, Moves.BEAT_UP,
Moves.COIL, Moves.COIL,
Moves.WRING_OUT, Moves.WRING_OUT,
Moves.STRING_SHOT Moves.STRING_SHOT,
]; ];
export const EXTORTION_ABILITIES = [ export const EXTORTION_ABILITIES = [
@ -69,5 +69,5 @@ export const EXTORTION_ABILITIES = [
Abilities.ARENA_TRAP, Abilities.ARENA_TRAP,
Abilities.SHADOW_TAG, Abilities.SHADOW_TAG,
Abilities.SUCTION_CUPS, 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 { Biome } from "#enums/biome";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next"; import i18next from "i18next";
import BattleScene from "../../../battle-scene"; import BattleScene from "#app/battle-scene";
import Trainer, { TrainerVariant } from "../../../field/trainer"; import Trainer, { TrainerVariant } from "#app/field/trainer";
import * as Utils from "../../../utils"; import * as Utils from "#app/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 { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { Nature } from "#app/data/nature"; import { Nature } from "#app/data/nature";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; 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 * 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; const trainerType = partyConfig?.trainerType;
let trainerConfig = partyConfig?.trainerConfig; let trainerConfig = partyConfig?.trainerConfig;
if (trainerType || trainerConfig) { if (trainerType || trainerConfig) {
scene.currentBattle.mysteryEncounter.encounterVariant = MysteryEncounterVariant.TRAINER_BATTLE; scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.TRAINER_BATTLE;
if (scene.currentBattle.trainer) { if (scene.currentBattle.trainer) {
scene.currentBattle.trainer.setVisible(false); scene.currentBattle.trainer.setVisible(false);
scene.currentBattle.trainer.destroy(); scene.currentBattle.trainer.destroy();
@ -128,7 +128,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex); battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex);
} else { } else {
// Wild // 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()); 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; enemySpecies = config.species;
isBoss = config.isBoss; isBoss = config.isBoss;
if (isBoss) { if (isBoss) {
scene.currentBattle.mysteryEncounter.encounterVariant = MysteryEncounterVariant.BOSS_BATTLE; scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.BOSS_BATTLE;
} }
} else { } else {
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true); 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); let expValue = Math.floor(baseExpValue * (useWaveIndex ? scene.currentBattle.waveIndex : 1) / 5 + 1);
if (participantIds?.length > 0) { 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); expValue = Math.floor(expValue * 1.5);
} }
for (const partyMember of nonFaintedPartyMembers) { 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 * @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) { 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.clearPhaseQueue();
scene.clearPhaseQueueSplice(); scene.clearPhaseQueueSplice();
handleMysteryEncounterVictory(scene, addHealPhase); handleMysteryEncounterVictory(scene, addHealPhase);
@ -626,14 +626,14 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
// If in repeated encounter variant, do nothing // If in repeated encounter variant, do nothing
// Variant must eventually be swapped in order to handle "true" end of the encounter // 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; 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 EggLapsePhase(scene));
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); 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)); 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)); scene.pushPhase(new TrainerVictoryPhase(scene));
} }
if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) { 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) { export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {
const encounter = scene.currentBattle?.mysteryEncounter; 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; const effects = encounter.startOfBattleEffects;
effects.forEach(effect => { effects.forEach(effect => {
let source; 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 = { export const offerYouCantRefuseDialogue = {
intro: "You're stopped by a rich looking boy.", intro: "You're stopped by a rich looking boy.",
speaker: "Rich Kid", speaker: "Rich Boy",
intro_dialogue: `Good day to you. intro_dialogue: `Good day to you.
$I can't help but notice that your\n{{strongestPokemon}} looks positively divine! $I can't help but notice that your\n{{strongestPokemon}} looks positively divine!
$I've always wanted to have a pet like that! $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", 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?", 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?", query: "What will you do?",
@ -18,11 +18,10 @@ export const offerYouCantRefuseDialogue = {
}, },
2: { 2: {
label: "Extort the Kid", 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", tooltip_disabled: "Your Pokémon need to have certain moves or abilities to choose this",
selected: `My word, we're being robbed, Liepard! selected: `My word, we're being robbed, Liepard!
$How wonderful! $You'll be hearing from my lawyers for this!`,
$Now I'll have an amazing\nstory for the yacht club!`,
}, },
3: { 3: {
label: "Leave", label: "Leave",

View File

@ -9,7 +9,7 @@ import { PokeballType } from "./data/pokeball";
import { Gender } from "./data/gender"; import { Gender } from "./data/gender";
import { StatusEffect } from "./data/status-effect"; import { StatusEffect } from "./data/status-effect";
import { modifierTypes, SpeciesStatBoosterItem } from "./modifier/modifier-type"; 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 { EggTier } from "#enums/egg-type";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
@ -18,7 +18,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; // eslint-disable-line @typescript-eslint/no-unused-vars 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 * 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 // 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_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 * MODIFIER / ITEM OVERRIDES

View File

@ -65,11 +65,11 @@ import { Moves } from "#enums/moves";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { doTrainerExclamation, handleMysteryEncounterBattleStartEffects, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; 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 ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
const { t } = i18next; const { t } = i18next;
@ -866,7 +866,7 @@ export class EncounterPhase extends BattlePhase {
} }
if (!this.loaded) { 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) { if (enemyPokemon.species.speciesId === Species.ETERNATUS) {
@ -1550,7 +1550,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
onComplete: () => this.scene.trainer.setVisible(false) onComplete: () => this.scene.trainer.setVisible(false)
}); });
this.scene.time.delayedCall(750, () => this.summon()); 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 trainerName = this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
const pokemonName = this.getPokemon().name; const pokemonName = this.getPokemon().name;
const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); 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 { transitionMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases"; import { CheckSwitchPhase, NewBattlePhase, ReturnPhase, ScanIvsPhase, SelectModifierPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases";
import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option"; import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterVariant } from "../data/mystery-encounters/mystery-encounter";
import { getCharVariantFromDialogue } from "../data/dialogue"; import { getCharVariantFromDialogue } from "../data/dialogue";
import { TrainerSlot } from "../data/trainer-config"; import { TrainerSlot } from "../data/trainer-config";
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
@ -15,6 +14,7 @@ import * as Utils from "../utils";
import { isNullOrUndefined } from "../utils"; import { isNullOrUndefined } from "../utils";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { BattlerTagLapseType } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#app/data/battler-tags";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
/** /**
* Will handle (in order): * Will handle (in order):
@ -210,13 +210,13 @@ export class MysteryEncounterBattlePhase extends Phase {
getBattleMessage(scene: BattleScene): string { getBattleMessage(scene: BattleScene): string {
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
const encounterVariant = scene.currentBattle.mysteryEncounter.encounterVariant; const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
if (scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { if (scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
return i18next.t("battle:bossAppeared", { bossName: enemyField[0].name }); return i18next.t("battle:bossAppeared", { bossName: enemyField[0].name });
} }
if (encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) { if (encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (scene.currentBattle.double) { if (scene.currentBattle.double) {
return i18next.t("battle:trainerAppearedDouble", { trainerName: scene.currentBattle.trainer.getName(TrainerSlot.NONE, true) }); 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) { doMysteryEncounterBattle(scene: BattleScene) {
const encounterVariant = scene.currentBattle.mysteryEncounter.encounterVariant; const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
if (encounterVariant === MysteryEncounterVariant.WILD_BATTLE || encounterVariant === MysteryEncounterVariant.BOSS_BATTLE) { if (encounterMode === MysteryEncounterMode.WILD_BATTLE || encounterMode === MysteryEncounterMode.BOSS_BATTLE) {
// Summons the wild/boss Pokemon // Summons the wild/boss Pokemon
if (encounterVariant === MysteryEncounterVariant.BOSS_BATTLE) { if (encounterMode === MysteryEncounterMode.BOSS_BATTLE) {
scene.playBgm(undefined); scene.playBgm(undefined);
} }
const availablePartyMembers = scene.getEnemyParty().filter(p => !p.isFainted()).length; const availablePartyMembers = scene.getEnemyParty().filter(p => !p.isFainted()).length;
@ -248,7 +248,7 @@ export class MysteryEncounterBattlePhase extends Phase {
} else { } else {
this.endBattleSetup(scene); this.endBattleSetup(scene);
} }
} else if (encounterVariant === MysteryEncounterVariant.TRAINER_BATTLE) { } else if (encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
this.showEnemyTrainer(); this.showEnemyTrainer();
const doSummon = () => { const doSummon = () => {
scene.currentBattle.started = true; scene.currentBattle.started = true;
@ -296,11 +296,11 @@ export class MysteryEncounterBattlePhase extends Phase {
endBattleSetup(scene: BattleScene) { endBattleSetup(scene: BattleScene) {
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
const encounterVariant = scene.currentBattle.mysteryEncounter.encounterVariant; const encounterMode = scene.currentBattle.mysteryEncounter.encounterMode;
// PostSummon and ShinySparkle phases are handled by SummonPhase // 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); const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) { if (ivScannerModifier) {
enemyField.map(p => this.scene.pushPhase(new ScanIvsPhase(this.scene, p.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6)))); 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)); 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; const minPartySize = scene.currentBattle.double ? 2 : 1;
if (availablePartyMembers.length > minPartySize) { if (availablePartyMembers.length > minPartySize) {
scene.pushPhase(new CheckSwitchPhase(scene, 0, scene.currentBattle.double)); 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 optionNo - human number, not index
* @param isBattle - if selecting option should lead to battle, set to true * @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) { export async function runSelectMysteryEncounterOption(game: GameManager, optionNo: number, isBattle: boolean = false) {
// Handle any eventual queued messages (e.g. weather phase, etc.) // Handle any eventual queued messages (e.g. weather phase, etc.)
game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => { game.onNextPrompt("MessagePhase", Mode.MESSAGE, () => {
@ -53,38 +100,6 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
} }
uiHandler.processInput(Button.ACTION); 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 GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
import { runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
import { SelectModifierPhase } from "#app/phases"; import { SelectModifierPhase } from "#app/phases";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale-encounter"; import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale-encounter";
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; 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 namespace = "mysteryEncounter:departmentStoreSale";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -98,7 +98,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 1 - TM Shop", () => { describe("Option 1 - TM Shop", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[0]; const option = DepartmentStoreSaleEncounter.options[0];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT); expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined(); expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({ expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
@ -108,7 +108,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only TMs", async () => { it("should have shop with only TMs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 1); await runMysteryEncounterToEnd(game, 1);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase); await game.phaseInterceptor.run(SelectModifierPhase);
@ -124,7 +124,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 1); await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });
@ -133,7 +133,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 2 - Vitamin Shop", () => { describe("Option 2 - Vitamin Shop", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[1]; const option = DepartmentStoreSaleEncounter.options[1];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT); expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined(); expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({ expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`, buttonLabel: `${namespace}:option:2:label`,
@ -143,7 +143,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Vitamins", async () => { it("should have shop with only Vitamins", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase); await game.phaseInterceptor.run(SelectModifierPhase);
@ -160,7 +160,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });
@ -169,7 +169,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 3 - X Item Shop", () => { describe("Option 3 - X Item Shop", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[2]; const option = DepartmentStoreSaleEncounter.options[2];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT); expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined(); expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({ expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`, buttonLabel: `${namespace}:option:3:label`,
@ -179,7 +179,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only X Items", async () => { it("should have shop with only X Items", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 3); await runMysteryEncounterToEnd(game, 3);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase); await game.phaseInterceptor.run(SelectModifierPhase);
@ -196,7 +196,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 3); await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });
@ -205,7 +205,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
describe("Option 4 - Pokeball Shop", () => { describe("Option 4 - Pokeball Shop", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option = DepartmentStoreSaleEncounter.options[3]; const option = DepartmentStoreSaleEncounter.options[3];
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT); expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined(); expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({ expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:4:label`, buttonLabel: `${namespace}:option:4:label`,
@ -215,7 +215,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
it("should have shop with only Pokeballs", async () => { it("should have shop with only Pokeballs", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 4); await runMysteryEncounterToEnd(game, 4);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase); await game.phaseInterceptor.run(SelectModifierPhase);
@ -231,7 +231,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
await runSelectMysteryEncounterOption(game, 4); await runMysteryEncounterToEnd(game, 4);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });

View File

@ -5,20 +5,21 @@ import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
import Battle from "#app/battle";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims"; import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option"; import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases"; import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
import { Status, StatusEffect } from "#app/data/status-effect"; 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"; const namespace = "mysteryEncounter:fieryFallout";
/** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ /** 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 () => { 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 weatherSpy = vi.spyOn(scene.arena, "trySetWeather").mockReturnValue(true);
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim"); const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets"); const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
@ -103,6 +105,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
expect(FieryFalloutEncounter.onInit).toBeDefined(); expect(FieryFalloutEncounter.onInit).toBeDefined();
FieryFalloutEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene); const onInitResult = onInit(scene);
expect(FieryFalloutEncounter.enemyPartyConfigs).toEqual([ expect(FieryFalloutEncounter.enemyPartyConfigs).toEqual([
@ -132,7 +135,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
describe("Option 1 - Fight 2 Volcarona", () => { describe("Option 1 - Fight 2 Volcarona", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[0]; const option1 = FieryFalloutEncounter.options[0];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT); expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined(); expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({ expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
@ -149,7 +152,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const phaseSpy = vi.spyOn(scene, "pushPhase"); const phaseSpy = vi.spyOn(scene, "pushPhase");
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 1, true); await runMysteryEncounterToEnd(game, 1, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); 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 () => { it("should give charcoal to lead pokemon", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 1, true); await runMysteryEncounterToEnd(game, 1, true);
await skipBattleRunMysteryEncounterRewardsPhase(game); await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
@ -182,7 +185,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
describe("Option 2 - Suffer the weather", () => { describe("Option 2 - Suffer the weather", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[1]; const option1 = FieryFalloutEncounter.options[1];
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT); expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option1.dialogue).toBeDefined(); expect(option1.dialogue).toBeDefined();
expect(option1.dialogue).toStrictEqual({ expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`, buttonLabel: `${namespace}:option:2:label`,
@ -204,7 +207,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA); const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false); 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 burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE));
const notBurnablePokemon = 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"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });
@ -229,7 +232,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
describe("Option 3 - use FIRE types", () => { describe("Option 3 - use FIRE types", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option1 = FieryFalloutEncounter.options[2]; 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).toBeDefined();
expect(option1.dialogue).toStrictEqual({ expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`, buttonLabel: `${namespace}:option:3:label`,
@ -245,7 +248,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
it("should give charcoal to lead pokemon", async () => { it("should give charcoal to lead pokemon", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 3); await runMysteryEncounterToEnd(game, 3);
// await skipBattleRunMysteryEncounterRewardsPhase(game); // await skipBattleRunMysteryEncounterRewardsPhase(game);
await game.phaseInterceptor.to(SelectModifierPhase, false); await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name); expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
@ -261,9 +264,23 @@ describe("Fiery Fallout - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
await runSelectMysteryEncounterOption(game, 3); await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); 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 { 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 MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species.js"; 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 { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { runSelectMysteryEncounterOption } from "../encounterTestUtils"; import { runMysteryEncounterToEnd } from "../encounterTestUtils";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter"; 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"; const namespace = "mysteryEncounter:lostAtSea";
/** Blastoise for surf. Pidgeot for fly. Abra for none. */ /** Blastoise for surf. Pidgeot for fly. Abra for none. */
@ -22,6 +23,7 @@ const defaultWave = 33;
describe("Lost at Sea - Mystery Encounter", () => { describe("Lost at Sea - Mystery Encounter", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
let scene: BattleScene;
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS }); phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
@ -29,6 +31,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
beforeEach(async () => { beforeEach(async () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100); game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave); game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome); game.override.startingBiome(defaultBiome);
@ -83,14 +86,16 @@ describe("Lost at Sea - Mystery Encounter", () => {
expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined(); expect(game.scene.currentBattle.mysteryEncounter).toBeUndefined();
}); });
it("should set the correct dialog tokens during initialization", () => { it("should initialize fully", () => {
vi.spyOn(game.scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: LostAtSeaEncounter } as Battle); initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = LostAtSeaEncounter;
const { onInit } = LostAtSeaEncounter; const { onInit } = LostAtSeaEncounter;
expect(LostAtSeaEncounter.onInit).toBeDefined(); 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?.damagePercentage).toBe("25");
expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(Moves[Moves.SURF]); expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(Moves[Moves.SURF]);
@ -101,7 +106,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
describe("Option 1 - Surf", () => { describe("Option 1 - Surf", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option1 = LostAtSeaEncounter.options[0]; 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).toBeDefined();
expect(option1.dialogue).toStrictEqual({ expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`, 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 blastoise = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
const expBefore = blastoise.exp; const expBefore = blastoise.exp;
await runSelectMysteryEncounterOption(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(blastoise.exp).toBe(expBefore + Math.floor(laprasSpecies.baseExp * defaultWave / 5 + 1)); 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"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
await runSelectMysteryEncounterOption(game, 1); await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });
@ -148,7 +153,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option2 = LostAtSeaEncounter.options[1]; 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).toBeDefined();
expect(option2.dialogue).toStrictEqual({ expect(option2.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:2:label`, 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 pidgeot = party.find((pkm) => pkm.species.speciesId === Species.PIDGEOT);
const expBefore = pidgeot.exp; const expBefore = pidgeot.exp;
await runSelectMysteryEncounterOption(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(pidgeot.exp).toBe(expBefore + Math.floor(laprasBaseExp * defaultWave / 5 + 1)); 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"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
await runSelectMysteryEncounterOption(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });
@ -197,7 +202,7 @@ describe("Lost at Sea - Mystery Encounter", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option3 = LostAtSeaEncounter.options[2]; const option3 = LostAtSeaEncounter.options[2];
expect(option3.optionMode).toBe(EncounterOptionMode.DEFAULT); expect(option3.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option3.dialogue).toBeDefined(); expect(option3.dialogue).toBeDefined();
expect(option3.dialogue).toStrictEqual({ expect(option3.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:3:label`, 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); const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA);
vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false); vi.spyOn(abra, "isAllowedInBattle").mockReturnValue(false);
await runSelectMysteryEncounterOption(game, 3); await runMysteryEncounterToEnd(game, 3);
const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle()); const allowedPkm = party.filter((pkm) => pkm.isAllowedInBattle());
const notAllowedPkm = 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"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
await runSelectMysteryEncounterOption(game, 3); await runMysteryEncounterToEnd(game, 3);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); 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 { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; 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 * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option"; import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
import { runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
import { PlayerPokemon } from "#app/field/pokemon"; import { PlayerPokemon } from "#app/field/pokemon";
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter"; 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 namespace = "mysteryEncounter:pokemonSalesman";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; 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).toBeDefined();
expect(PokemonSalesmanEncounter.dialogue.intro).toStrictEqual([ expect(PokemonSalesmanEncounter.dialogue.intro).toStrictEqual([
{ text: `${namespace}:intro` }, { 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.title).toBe(`${namespace}:title`);
expect(PokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`); expect(PokemonSalesmanEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
@ -91,12 +91,14 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
}); });
it("should initialize fully ", async () => { 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; const { onInit } = PokemonSalesmanEncounter;
expect(PokemonSalesmanEncounter.onInit).toBeDefined(); expect(PokemonSalesmanEncounter.onInit).toBeDefined();
PokemonSalesmanEncounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene); const onInitResult = onInit(scene);
expect(PokemonSalesmanEncounter.dialogueTokens?.purchasePokemon).toBeDefined(); expect(PokemonSalesmanEncounter.dialogueTokens?.purchasePokemon).toBeDefined();
@ -109,7 +111,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
describe("Option 1 - Purchase the pokemon", () => { describe("Option 1 - Purchase the pokemon", () => {
it("should have the correct properties", () => { it("should have the correct properties", () => {
const option1 = PokemonSalesmanEncounter.options[0]; 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).toBeDefined();
expect(option1.dialogue).toStrictEqual({ expect(option1.dialogue).toStrictEqual({
buttonLabel: `${namespace}:option:1:label`, buttonLabel: `${namespace}:option:1:label`,
@ -128,7 +130,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney"); const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
await runSelectMysteryEncounterOption(game, 1); await runMysteryEncounterToEnd(game, 1);
const price = scene.currentBattle.mysteryEncounter.misc.price; const price = scene.currentBattle.mysteryEncounter.misc.price;
@ -142,7 +144,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
const initialPartySize = scene.getParty().length; const initialPartySize = scene.getParty().length;
const pokemonName = scene.currentBattle.mysteryEncounter.misc.pokemon.name; 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().length).toBe(initialPartySize + 1);
expect(scene.getParty().find(p => p.name === pokemonName) instanceof PlayerPokemon).toBeTruthy(); 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"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
await runSelectMysteryEncounterOption(game, 1); await runMysteryEncounterToEnd(game, 1);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });
@ -163,7 +165,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.POKEMON_SALESMAN, defaultParty);
await runSelectMysteryEncounterOption(game, 2); await runMysteryEncounterToEnd(game, 2);
expect(leaveEncounterWithoutBattleSpy).toBeCalled(); expect(leaveEncounterWithoutBattleSpy).toBeCalled();
}); });

View File

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

View File

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

View File

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

View File

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