diff --git a/public/images/mystery-encounters/mad_scientist_m.json b/public/images/mystery-encounters/mad_scientist_m.json index 0cb3f904cb0..10aa3d6f42a 100644 --- a/public/images/mystery-encounters/mad_scientist_m.json +++ b/public/images/mystery-encounters/mad_scientist_m.json @@ -4,8 +4,8 @@ "image": "mad_scientist_m.png", "format": "RGBA8888", "size": { - "w": 44, - "h": 74 + "w": 46, + "h": 76 }, "scale": 1, "frames": [ @@ -24,8 +24,8 @@ "h": 74 }, "frame": { - "x": 0, - "y": 0, + "x": 1, + "y": 1, "w": 44, "h": 74 } @@ -36,6 +36,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$" + "smartupdate": "$TexturePacker:SmartUpdate:a7f8ff2bbb362868f51125c254eb6681:cf76e61ddd31a8f46af67ced168c44a2:4fc09abe16c0608828269e5da81d0744$" } } diff --git a/public/images/mystery-encounters/mad_scientist_m.png b/public/images/mystery-encounters/mad_scientist_m.png index 84349b46c8a..453cb767ec1 100644 Binary files a/public/images/mystery-encounters/mad_scientist_m.png and b/public/images/mystery-encounters/mad_scientist_m.png differ diff --git a/public/images/mystery-encounters/teacher.json b/public/images/mystery-encounters/teacher.json new file mode 100644 index 00000000000..457d440a010 --- /dev/null +++ b/public/images/mystery-encounters/teacher.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "teacher.png", + "format": "RGBA8888", + "size": { + "w": 43, + "h": 74 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 19, + "y": 8, + "w": 41, + "h": 72 + }, + "frame": { + "x": 1, + "y": 1, + "w": 41, + "h": 72 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:506e5a4ce79c134a7b4af90a90aef244:1b81d3d84bf12cedc419805eaff82548:59bc5dd000b5e72588320b473e31c312$" + } +} diff --git a/public/images/mystery-encounters/teacher.png b/public/images/mystery-encounters/teacher.png new file mode 100644 index 00000000000..b4332bc0032 Binary files /dev/null and b/public/images/mystery-encounters/teacher.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f67adce1e02..d3eac7fc973 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2696,7 +2696,7 @@ export default class BattleScene extends SceneBase { encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)]; // New encounter object to not dirty flags encounter = new MysteryEncounter(encounter); - encounter.meetsRequirements(this); + encounter.populateDialogueTokensFromRequirements(this); return encounter; } } diff --git a/src/data/mystery-encounters/dialogue/field-trip-dialogue.ts b/src/data/mystery-encounters/dialogue/field-trip-dialogue.ts new file mode 100644 index 00000000000..dca3f48f5bf --- /dev/null +++ b/src/data/mystery-encounters/dialogue/field-trip-dialogue.ts @@ -0,0 +1,50 @@ +import MysteryEncounterDialogue from "#app/data/mystery-encounters/mystery-encounter-dialogue"; + +export const FieldTripDialogue: MysteryEncounterDialogue = { + intro: [ + { + text: "mysteryEncounter:field_trip_intro_message" + }, + { + text: "mysteryEncounter:field_trip_intro_dialogue", + speaker: "mysteryEncounter:field_trip_speaker" + } + ], + encounterOptionsDialogue: { + title: "mysteryEncounter:field_trip_title", + description: "mysteryEncounter:field_trip_description", + query: "mysteryEncounter:field_trip_query", + options: [ + { + buttonLabel: "mysteryEncounter:field_trip_option_1_label", + buttonTooltip: "mysteryEncounter:field_trip_option_1_tooltip", + secondOptionPrompt: "mysteryEncounter:field_trip_second_option_prompt", + selected: [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ] + }, + { + buttonLabel: "mysteryEncounter:field_trip_option_2_label", + buttonTooltip: "mysteryEncounter:field_trip_option_2_tooltip", + secondOptionPrompt: "mysteryEncounter:field_trip_second_option_prompt", + selected: [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ] + }, + { + buttonLabel: "mysteryEncounter:field_trip_option_3_label", + buttonTooltip: "mysteryEncounter:field_trip_option_3_tooltip", + secondOptionPrompt: "mysteryEncounter:field_trip_second_option_prompt", + selected: [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ] + } + ] + } +}; diff --git a/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts b/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts index cb0c5ac130e..52b9741caf8 100644 --- a/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts +++ b/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts @@ -21,7 +21,7 @@ export const ShadyVitaminDealerDialogue: MysteryEncounterDialogue = { selected: [ { text: "mysteryEncounter:shady_vitamin_dealer_option_selected" - }, + } ] }, { @@ -30,7 +30,7 @@ export const ShadyVitaminDealerDialogue: MysteryEncounterDialogue = { selected: [ { text: "mysteryEncounter:shady_vitamin_dealer_option_selected" - }, + } ] }, { diff --git a/src/data/mystery-encounters/encounters/department-store-sale.ts b/src/data/mystery-encounters/encounters/department-store-sale.ts index 1d44c2b3af8..19269a774f3 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale.ts @@ -1,5 +1,5 @@ import { - leaveEncounterWithoutBattle, setEncounterExp, + leaveEncounterWithoutBattle, setEncounterRewards, } from "#app/data/mystery-encounters/mystery-encounter-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; @@ -46,7 +46,6 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu i++; } - setEncounterExp(scene, scene.getParty().map(p => p.id), 300); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false }); leaveEncounterWithoutBattle(scene); }) diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts new file mode 100644 index 00000000000..410df127b6e --- /dev/null +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -0,0 +1,220 @@ +import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, } from "#app/data/mystery-encounters/mystery-encounter-utils"; +import { modifierTypes } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import BattleScene from "../../../battle-scene"; +import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; +import { MoveCategory } from "#app/data/move"; +import { TempBattleStat } from "#app/data/temp-battle-stat"; + +export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder + .withEncounterType(MysteryEncounterType.FIELD_TRIP) + .withEncounterTier(MysteryEncounterTier.COMMON) + .withIntroSpriteConfigs([ + { + spriteKey: "preschooler_m", + fileRoot: "trainer", + hasShadow: true + }, + { + spriteKey: "teacher", + fileRoot: "mystery-encounters", + hasShadow: true + }, + { + spriteKey: "preschooler_f", + fileRoot: "trainer", + hasShadow: true + }, + ]) + .withHideIntroVisuals(false) + .withSceneWaveRangeRequirement(10, 180) + .withOption(new MysteryEncounterOptionBuilder() + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Return the options for Pokemon move valid for this option + return pokemon.moveset.map((move: PokemonMove) => { + const option: OptionSelectItem = { + label: move.getName(), + handler: () => { + // Pokemon and move selected + const correctMove = move.getMove().category === MoveCategory.PHYSICAL; + encounter.setDialogueToken("moveCategory", "Physical"); + if (!correctMove) { + encounter.dialogue.encounterOptionsDialogue.options[0].selected = [ + { + text: "mysteryEncounter:field_trip_option_incorrect", + speaker: "mysteryEncounter:field_trip_speaker" + }, + { + text: "mysteryEncounter:field_trip_lesson_learned", + } + ]; + setEncounterExp(scene, scene.getParty().map(p => p.id), 50); + } else { + encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("move", move.getName()); + encounter.dialogue.encounterOptionsDialogue.options[0].selected = [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ]; + setEncounterExp(scene, [pokemon.id], 100); + } + encounter.misc = { + correctMove: correctMove + }; + return true; + } + }; + return option; + }); + }; + + return selectPokemonForOption(scene, onPokemonSelected); + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + if (encounter.misc.correctMove) { + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ATK]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.DEF]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]), + generateModifierTypeOption(scene, modifierTypes.DIRE_HIT) + ]; + + setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + } + + leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + }) + .build() + ) + .withOption(new MysteryEncounterOptionBuilder() + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Return the options for Pokemon move valid for this option + return pokemon.moveset.map((move: PokemonMove) => { + const option: OptionSelectItem = { + label: move.getName(), + handler: () => { + // Pokemon and move selected + const correctMove = move.getMove().category === MoveCategory.SPECIAL; + encounter.setDialogueToken("moveCategory", "Special"); + if (!correctMove) { + encounter.dialogue.encounterOptionsDialogue.options[1].selected = [ + { + text: "mysteryEncounter:field_trip_option_incorrect", + speaker: "mysteryEncounter:field_trip_speaker" + }, + { + text: "mysteryEncounter:field_trip_lesson_learned", + } + ]; + setEncounterExp(scene, scene.getParty().map(p => p.id), 50); + } else { + encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("move", move.getName()); + encounter.dialogue.encounterOptionsDialogue.options[1].selected = [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ]; + setEncounterExp(scene, [pokemon.id], 100); + } + encounter.misc = { + correctMove: correctMove + }; + return true; + } + }; + return option; + }); + }; + + return selectPokemonForOption(scene, onPokemonSelected); + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + if (encounter.misc.correctMove) { + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPATK]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPDEF]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]), + generateModifierTypeOption(scene, modifierTypes.DIRE_HIT) + ]; + + setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + } + + leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + }) + .build() + ) + .withOption(new MysteryEncounterOptionBuilder() + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Return the options for Pokemon move valid for this option + return pokemon.moveset.map((move: PokemonMove) => { + const option: OptionSelectItem = { + label: move.getName(), + handler: () => { + // Pokemon and move selected + const correctMove = move.getMove().category === MoveCategory.STATUS; + encounter.setDialogueToken("moveCategory", "Status"); + if (!correctMove) { + encounter.dialogue.encounterOptionsDialogue.options[2].selected = [ + { + text: "mysteryEncounter:field_trip_option_incorrect", + speaker: "mysteryEncounter:field_trip_speaker" + }, + { + text: "mysteryEncounter:field_trip_lesson_learned", + } + ]; + setEncounterExp(scene, scene.getParty().map(p => p.id), 50); + } else { + encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("move", move.getName()); + encounter.dialogue.encounterOptionsDialogue.options[2].selected = [ + { + text: "mysteryEncounter:field_trip_option_selected" + } + ]; + setEncounterExp(scene, [pokemon.id], 100); + } + encounter.misc = { + correctMove: correctMove + }; + return true; + } + }; + return option; + }); + }; + + return selectPokemonForOption(scene, onPokemonSelected); + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + if (encounter.misc.correctMove) { + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.ACC]), + generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_BOOSTER, [TempBattleStat.SPD]), + generateModifierTypeOption(scene, modifierTypes.GREAT_BALL), + generateModifierTypeOption(scene, modifierTypes.IV_SCANNER) + ]; + + setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false }); + } + + leaveEncounterWithoutBattle(scene, !encounter.misc.correctMove); + }) + .build() + ) + .build(); diff --git a/src/data/mystery-encounters/encounters/fight-or-flight.ts b/src/data/mystery-encounters/encounters/fight-or-flight.ts index 506b450aa98..8a82439772c 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight.ts @@ -16,24 +16,14 @@ import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { StatChangePhase } from "#app/phases"; -import { TextStyle } from "#app/ui/text"; import { randSeedInt } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { Moves } from "#enums/moves"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "../../../battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter"; import { MoveRequirement } from "../mystery-encounter-requirements"; - -const validMovesForSteal = [ - Moves.PLUCK, - Moves.COVET, - Moves.FAKE_OUT, - Moves.THIEF, - Moves.TRICK, - Moves.SWITCHEROO, - Moves.GIGA_DRAIN -]; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder .withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT) @@ -82,18 +72,13 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder ]; // If player has a stealing move, they succeed automatically - const moveRequirement = new MoveRequirement(validMovesForSteal); - const validPokemon = moveRequirement.queryParty(scene.getParty()); - if (validPokemon?.length > 0) { - // Use first valid pokemon to execute the theivery - const pokemon = validPokemon[0]; - encounter.setDialogueToken("thiefPokemon", pokemon.name); - encounter.setDialogueToken(...moveRequirement.getDialogueToken(scene, pokemon)); + encounter.options[1].meetsRequirements(scene); + const primaryPokemon = encounter.options[1].primaryPokemon; + if (primaryPokemon) { + // Use primaryPokemon to execute the thievery encounter.dialogue.encounterOptionsDialogue.options[1].buttonTooltip = "mysteryEncounter:fight_or_flight_option_2_steal_tooltip"; - encounter.dialogue.encounterOptionsDialogue.options[1].style = TextStyle.SUMMARY_GREEN; } else { encounter.dialogue.encounterOptionsDialogue.options[1].buttonTooltip = "mysteryEncounter:fight_or_flight_option_2_tooltip"; - encounter.dialogue.encounterOptionsDialogue.options[1].style = null; } return true; @@ -104,44 +89,44 @@ export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false }); await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]); }) - .withOptionPhase(async (scene: BattleScene) => { - // Pick steal - const encounter = scene.currentBattle.mysteryEncounter; - const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption; - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false }); + .withOption(new MysteryEncounterOptionBuilder() + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withDisabledOnRequirementsNotMet(false) + .withOptionPhase(async (scene: BattleScene) => { + // Pick steal + const encounter = scene.currentBattle.mysteryEncounter; + const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption; + setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false }); - // If player has a stealing move, they succeed automatically - const moveRequirement = new MoveRequirement(validMovesForSteal); - const validPokemon = moveRequirement.queryParty(scene.getParty()); - if (validPokemon?.length > 0) { - // Use first valid pokemon to execute the theivery - const pokemon = validPokemon[0]; - encounter.setDialogueToken("thiefPokemon", pokemon.name); - encounter.setDialogueToken(...moveRequirement.getDialogueToken(scene, pokemon)); - await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_steal_result"); - leaveEncounterWithoutBattle(scene); - return; - } + // If player has a stealing move, they succeed automatically + const primaryPokemon = encounter.options[1].primaryPokemon; + if (primaryPokemon) { + // Use primaryPokemon to execute the thievery + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_steal_result"); + leaveEncounterWithoutBattle(scene); + return; + } - const roll = randSeedInt(16); - if (roll > 6) { - // Noticed and attacked by boss, gets +1 to all stats at start of fight (62.5%) - const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]; - config.pokemonConfigs[0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; - config.pokemonConfigs[0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { - pokemon.scene.currentBattle.mysteryEncounter.setDialogueToken("enemyPokemon", pokemon.name); - queueEncounterMessage(pokemon.scene, "mysteryEncounter:fight_or_flight_boss_enraged"); - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1)); - }; - await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_bad_result"); - await initBattleWithEnemyConfig(scene, config); - } else { - // Steal item (37.5%) - // Display result message then proceed to rewards - await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_good_result"); - leaveEncounterWithoutBattle(scene); - } - }) + const roll = randSeedInt(16); + if (roll > 6) { + // Noticed and attacked by boss, gets +1 to all stats at start of fight (62.5%) + const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]; + config.pokemonConfigs[0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; + config.pokemonConfigs[0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { + pokemon.scene.currentBattle.mysteryEncounter.setDialogueToken("enemyPokemon", pokemon.name); + queueEncounterMessage(pokemon.scene, "mysteryEncounter:fight_or_flight_boss_enraged"); + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1)); + }; + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_bad_result"); + await initBattleWithEnemyConfig(scene, config); + } else { + // Steal item (37.5%) + // Display result message then proceed to rewards + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_good_result"); + leaveEncounterWithoutBattle(scene); + } + }) + .build()) .withOptionPhase(async (scene: BattleScene) => { // Leave encounter with no rewards or exp leaveEncounterWithoutBattle(scene, true); diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts index d58bc44daf7..21e1b199e04 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer.ts @@ -1,9 +1,8 @@ import { - generateModifierType, + generateModifierTypeOption, leaveEncounterWithoutBattle, queueEncounterMessage, - selectPokemonForOption, - setEncounterRewards, + selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/mystery-encounter-utils"; import { StatusEffect } from "#app/data/status-effect"; @@ -18,6 +17,7 @@ import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MoneyRequirement } from "../mystery-encounter-requirements"; +import i18next from "i18next"; export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder .withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) @@ -43,7 +43,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui .withPrimaryPokemonStatusEffectRequirement([StatusEffect.NONE]) // Pokemon must not have status .withPrimaryPokemonHealthRatioRequirement([0.34, 1]) // Pokemon must have above 1/3rd HP .withOption(new MysteryEncounterOptionBuilder() - .withSceneMoneyRequirement(0, 2) // Wave scaling multiplier of 2 for cost + .withSceneMoneyRequirement(0, 2) // Wave scaling money multiplier of 2 .withPreOptionPhase(async (scene: BattleScene): Promise => { const encounter = scene.currentBattle.mysteryEncounter; const onPokemonSelected = (pokemon: PlayerPokemon) => { @@ -51,8 +51,8 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens const modifiers = [ - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER), - generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER) + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type ]; encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost2", modifiers[1].name); @@ -62,12 +62,12 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui }; }; - // Only Pokemon that can gain benefits are unfainted with no status + // Only Pokemon that can gain benefits are above 1/3rd HP with no status const selectableFilter = (pokemon: Pokemon) => { // If pokemon meets primary pokemon reqs, it can be selected const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon); if (!meetsReqs) { - return "Pokémon must be healthy enough."; + return i18next.t("mysteryEncounter:shady_vitamin_dealer_invalid_selection"); } return null; @@ -99,7 +99,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui chosenPokemon.hp = Math.max(chosenPokemon.hp - damage, 0); // Roll for poison (80%) - if (randSeedInt(10) < 10) { + if (randSeedInt(10) < 8) { if (chosenPokemon.trySetStatus(StatusEffect.TOXIC)) { // Toxic applied queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_bad_poison"); @@ -111,32 +111,81 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBui queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_damage_only"); } + setEncounterExp(scene, [chosenPokemon.id], 100); + chosenPokemon.updateInfo(); }) .build()) - .withOption(new MysteryEncounterOptionBuilder() - .withSceneMoneyRequirement(0, 5) // Wave scaling multiplier of 2 for cost + .withSceneMoneyRequirement(0, 5) // Wave scaling money multiplier of 5 + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Update money + updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); + // Calculate modifiers and dialogue tokens + const modifiers = [ + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, + generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type + ]; + encounter.setDialogueToken("boost1", modifiers[0].name); + encounter.setDialogueToken("boost2", modifiers[1].name); + encounter.misc = { + chosenPokemon: pokemon, + modifiers: modifiers + }; + }; + + // Only Pokemon that can gain benefits are above 1/3rd HP with no status + const selectableFilter = (pokemon: Pokemon) => { + // If pokemon meets primary pokemon reqs, it can be selected + const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon); + if (!meetsReqs) { + return i18next.t("mysteryEncounter:shady_vitamin_dealer_invalid_selection"); + } + + return null; + }; + + return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter); + }) .withOptionPhase(async (scene: BattleScene) => { // Choose Expensive Option - const modifiers = []; - let i = 0; - while (i < 3) { - // 2/1 weight on base stat booster vs PP Up - const roll = randSeedInt(3); - if (roll === 0) { - modifiers.push(modifierTypes.PP_UP); - } else { + const encounter = scene.currentBattle.mysteryEncounter; + const chosenPokemon = encounter.misc.chosenPokemon; + const modifiers = encounter.misc.modifiers; - } - i++; + for (const modType of modifiers) { + const modifier = modType.newModifier(chosenPokemon); + await scene.addModifier(modifier, true, false, false, true); } + scene.updateModifiers(true); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false }); leaveEncounterWithoutBattle(scene); }) - .build() - ) + .withPostOptionPhase(async (scene: BattleScene) => { + // Status applied after dealer leaves (to make thematic sense) + const encounter = scene.currentBattle.mysteryEncounter; + const chosenPokemon = encounter.misc.chosenPokemon; + + // Roll for poison (20%) + if (randSeedInt(10) < 2) { + if (chosenPokemon.trySetStatus(StatusEffect.POISON)) { + // Poison applied + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_poison"); + } else { + // Pokemon immune or something else prevents status + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_no_bad_effects"); + } + } else { + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_no_bad_effects"); + } + + setEncounterExp(scene, [chosenPokemon.id], 100); + + chosenPokemon.updateInfo(); + }) + .build()) .withOptionPhase(async (scene: BattleScene) => { // Leave encounter with no rewards or exp leaveEncounterWithoutBattle(scene, true); diff --git a/src/data/mystery-encounters/encounters/sleeping-snorlax.ts b/src/data/mystery-encounters/encounters/sleeping-snorlax.ts index 1a011bb05a0..444ce5a6581 100644 --- a/src/data/mystery-encounters/encounters/sleeping-snorlax.ts +++ b/src/data/mystery-encounters/encounters/sleeping-snorlax.ts @@ -1,9 +1,7 @@ import { - ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; import { BerryType } from "#enums/berry-type"; -import { Moves } from "#enums/moves"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "../../../battle-scene"; @@ -15,11 +13,12 @@ import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MoveRequirement } from "../mystery-encounter-requirements"; import { EnemyPartyConfig, - EnemyPokemonConfig, generateModifierType, + EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, - leaveEncounterWithoutBattle, queueEncounterMessage, + leaveEncounterWithoutBattle, queueEncounterMessage, setEncounterExp, setEncounterRewards } from "../mystery-encounter-utils"; +import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder .withEncounterType(MysteryEncounterType.SLEEPING_SNORLAX) @@ -30,7 +29,9 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilde fileRoot: "pokemon", hasShadow: true, tint: 0.25, - repeat: true + scale: 1.5, + repeat: true, + y: 5 } ]) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 @@ -66,16 +67,19 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilde scene.executeWithSeedOffset(() => { roll = Utils.randSeedInt(16, 0); }, scene.currentBattle.waveIndex); - console.log(roll); + + // Half Snorlax exp to entire party + setEncounterExp(scene, scene.getParty().map(p => p.id), 98); + if (roll > 4) { // Fall asleep and get a sitrus berry (75%) const p = instance.primaryPokemon; p.status = new Status(StatusEffect.SLEEP, 0, 3); p.updateInfo(true); // const sitrus = (modifierTypes.BERRY?.() as ModifierTypeGenerator).generateType(scene.getParty(), [BerryType.SITRUS]); - const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]); + const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]); - setEncounterRewards(scene, { guaranteedModifierTypeOptions: [new ModifierTypeOption(sitrus, 0)], fillRemaining: false }); + setEncounterRewards(scene, { guaranteedModifierTypeOptions: [sitrus], fillRemaining: false }); queueEncounterMessage(scene, "mysteryEncounter:sleeping_snorlax_option_2_bad_result"); leaveEncounterWithoutBattle(scene); } else { @@ -94,11 +98,14 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilde } }) .withOption(new MysteryEncounterOptionBuilder() - .withPrimaryPokemonRequirement(new MoveRequirement([Moves.PLUCK, Moves.COVET, Moves.KNOCK_OFF, Moves.THIEF, Moves.TRICK, Moves.SWITCHEROO])) + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) .withOptionPhase(async (scene: BattleScene) => { - // Leave encounter with no rewards or exp + // Steal the Snorlax's Leftovers + const instance = scene.currentBattle.mysteryEncounter; setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.LEFTOVERS], fillRemaining: false }); queueEncounterMessage(scene, "mysteryEncounter:sleeping_snorlax_option_3_good_result"); + // Snorlax exp to Pokemon that did the stealing + setEncounterExp(scene, [instance.primaryPokemon.id], 189); leaveEncounterWithoutBattle(scene); }) .build() diff --git a/src/data/mystery-encounters/mystery-encounter-dialogue.ts b/src/data/mystery-encounters/mystery-encounter-dialogue.ts index 1e2dfe85045..5129f3bf23e 100644 --- a/src/data/mystery-encounters/mystery-encounter-dialogue.ts +++ b/src/data/mystery-encounters/mystery-encounter-dialogue.ts @@ -8,6 +8,7 @@ import { SleepingSnorlaxDialogue } from "./dialogue/sleeping-snorlax-dialogue"; import { DepartmentStoreSaleDialogue } from "#app/data/mystery-encounters/dialogue/department-store-sale-dialogue"; import { ShadyVitaminDealerDialogue } from "#app/data/mystery-encounters/dialogue/shady-vitamin-dealer"; import { TextStyle } from "#app/ui/text"; +import { FieldTripDialogue } from "#app/data/mystery-encounters/dialogue/field-trip-dialogue"; export class TextDisplay { speaker?: TemplateStringsArray | `mysteryEncounter:${string}`; @@ -92,4 +93,5 @@ export function initMysteryEncounterDialogue() { allMysteryEncounterDialogue[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxDialogue; allMysteryEncounterDialogue[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleDialogue; allMysteryEncounterDialogue[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerDialogue; + allMysteryEncounterDialogue[MysteryEncounterType.FIELD_TRIP] = FieldTripDialogue; } diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index f1316ff64f1..7731468a977 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -3,6 +3,7 @@ import { PlayerPokemon } from "#app/field/pokemon"; import BattleScene from "../../battle-scene"; import * as Utils from "../../utils"; import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement } from "./mystery-encounter-requirements"; +import { isNullOrUndefined } from "../../utils"; export type OptionPhaseCallback = (scene: BattleScene) => Promise; @@ -14,6 +15,12 @@ export default interface MysteryEncounterOption { primaryPokemon?: PlayerPokemon; secondaryPokemon?: PlayerPokemon[]; excludePrimaryFromSecondaryRequirements?: boolean; + /** + * There are two modes of option requirements: + * 1 (DEFAULT): Option is completely disabled if requirements are not met (unselectable and greyed out) + * 2: Option is *NOT* disabled if requirements are not met + */ + isDisabledOnRequirementsNotMet?: boolean; /** * Dialogue object containing all the dialogue, messages, tooltips, etc. for this option @@ -33,12 +40,19 @@ export default class MysteryEncounterOption implements MysteryEncounterOption { constructor(option: MysteryEncounterOption) { Object.assign(this, option); this.requirements = this.requirements ? this.requirements : []; + this.primaryPokemonRequirements = this.primaryPokemonRequirements ? this.primaryPokemonRequirements : []; + this.secondaryPokemonRequirements = this.secondaryPokemonRequirements ? this.secondaryPokemonRequirements : []; + this.isDisabledOnRequirementsNotMet = isNullOrUndefined(this.isDisabledOnRequirementsNotMet) ? true : this.isDisabledOnRequirementsNotMet; + } + + hasRequirements?() { + return this.requirements.length > 0 || this.primaryPokemonRequirements.length > 0 || this.secondaryPokemonRequirements.length > 0; } meetsRequirements?(scene: BattleScene) { return !this.requirements.some(requirement => !requirement.meetsRequirement(scene)) && - this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene) && - this.meetsSupportingRequirementAndSupportingPokemonSelected(scene); + this.meetsSupportingRequirementAndSupportingPokemonSelected(scene) && + this.meetsPrimaryRequirementAndPrimaryPokemonSelected(scene); } meetsPrimaryRequirementAndPrimaryPokemonSelected?(scene: BattleScene) { @@ -124,6 +138,7 @@ export class MysteryEncounterOptionBuilder implements Partial> { + this.isDisabledOnRequirementsNotMet = disabled; + return Object.assign(this, { isDisabledOnRequirementsNotMet: this.isDisabledOnRequirementsNotMet }); + } } diff --git a/src/data/mystery-encounters/mystery-encounter-utils.ts b/src/data/mystery-encounters/mystery-encounter-utils.ts index 2d8d7bfbf66..d2e1a5eba2f 100644 --- a/src/data/mystery-encounters/mystery-encounter-utils.ts +++ b/src/data/mystery-encounters/mystery-encounter-utils.ts @@ -8,7 +8,7 @@ import { TrainerConfig, trainerConfigs, TrainerSlot } from "../trainer-config"; import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; import Trainer, { TrainerVariant } from "../../field/trainer"; import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier"; -import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases"; import { MysteryEncounterBattlePhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase"; import * as Utils from "../../utils"; @@ -443,7 +443,7 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS * @param modifier * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. */ -export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType { +export function generateModifierTypeOption(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption { const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier); let result: ModifierType = modifierTypes[modifierId]?.(); @@ -463,7 +463,7 @@ export function generateModifierType(scene: BattleScene, modifier: () => Modifie }); result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result; - return result; + return new ModifierTypeOption(result, 0); } /** @@ -593,6 +593,7 @@ export function setEncounterRewards(scene: BattleScene, customShopRewards?: Cust * 290 - trio legendaries * 340 - box legendaries * 608 - Blissey (highest in game) + * https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_effort_value_yield_(Generation_IX) * @param useWaveIndex - set to false when directly passing the the full exp value instead of baseExpValue */ export function setEncounterExp(scene: BattleScene, participantIds: integer[], baseExpValue: number, useWaveIndex: boolean = true) { diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 87b80aba321..0d656988009 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -120,6 +120,7 @@ export default interface MysteryEncounter { /** * Generic property to set any custom data required for the encounter + * Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase */ misc?: any; } @@ -276,6 +277,7 @@ export default class MysteryEncounter implements MysteryEncounter { * For multiple support pokemon in the dialogue token, it will have to be overridden. */ populateDialogueTokensFromRequirements?(scene: BattleScene) { + this.meetsRequirements(scene); if (this.requirements?.length > 0) { for (const req of this.requirements) { const dialogueToken = req.getDialogueToken(scene); @@ -304,6 +306,7 @@ export default class MysteryEncounter implements MysteryEncounter { // Dialogue tokens for options for (let i = 0; i < this.options.length; i++) { const opt = this.options[i]; + opt.meetsRequirements(scene); const j = i + 1; if (opt.requirements?.length > 0) { for (const req of opt.requirements) { @@ -316,7 +319,7 @@ export default class MysteryEncounter implements MysteryEncounter { for (const req of opt.primaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, opt.primaryPokemon); - this.setDialogueToken("option" + j + "Primary", value[1]); + this.setDialogueToken("option" + j + "Primary" + this.capitalizeFirstLetter(value[0]), value[1]); } } } @@ -325,7 +328,7 @@ export default class MysteryEncounter implements MysteryEncounter { for (const req of opt.secondaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]); - this.setDialogueToken("option" + j + "Secondary", value[1]); + this.setDialogueToken("option" + j + "Secondary" + this.capitalizeFirstLetter(value[0]), value[1]); } } } diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index e4a748d3a23..1651430e59d 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -9,42 +9,164 @@ import { SleepingSnorlaxEncounter } from "./encounters/sleeping-snorlax"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale"; import { ShadyVitaminDealerEncounter } from "#app/data/mystery-encounters/encounters/shady-vitamin-dealer"; +import { FieldTripEncounter } from "#app/data/mystery-encounters/encounters/field-trip-encounter"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; export const WIGHT_INCREMENT_ON_SPAWN_MISS = 5; export const AVERAGE_ENCOUNTERS_PER_RUN_TARGET = 15; +export const EXTREME_ENCOUNTER_BIOMES = [ + Biome.SEA, + Biome.SEABED, + Biome.BADLANDS, + Biome.DESERT, + Biome.ICE_CAVE, + Biome.VOLCANO, + Biome.WASTELAND, + Biome.ABYSS, + Biome.SPACE, + Biome.END +]; + +export const NON_EXTREME_ENCOUNTER_BIOMES = [ + Biome.TOWN, + Biome.PLAINS, + Biome.GRASS, + Biome.TALL_GRASS, + Biome.METROPOLIS, + Biome.FOREST, + Biome.SWAMP, + Biome.BEACH, + Biome.LAKE, + Biome.MOUNTAIN, + Biome.CAVE, + Biome.MEADOW, + Biome.POWER_PLANT, + Biome.GRAVEYARD, + Biome.DOJO, + Biome.FACTORY, + Biome.RUINS, + Biome.CONSTRUCTION_SITE, + Biome.JUNGLE, + Biome.FAIRY_CAVE, + Biome.TEMPLE, + Biome.SLUM, + Biome.SNOWY_FOREST, + Biome.ISLAND, + Biome.LABORATORY +]; + +/** + * Places where you could very reasonably expect to encounter a single human + * + * Diff from NON_EXTREME_ENCOUNTER_BIOMES: + * + BADLANDS + * + DESERT + * + ICE_CAVE + */ +export const HUMAN_TRANSITABLE_BIOMES = [ + Biome.TOWN, + Biome.PLAINS, + Biome.GRASS, + Biome.TALL_GRASS, + Biome.METROPOLIS, + Biome.FOREST, + Biome.SWAMP, + Biome.BEACH, + Biome.LAKE, + Biome.MOUNTAIN, + Biome.BADLANDS, + Biome.CAVE, + Biome.DESERT, + Biome.ICE_CAVE, + Biome.MEADOW, + Biome.POWER_PLANT, + Biome.GRAVEYARD, + Biome.DOJO, + Biome.FACTORY, + Biome.RUINS, + Biome.CONSTRUCTION_SITE, + Biome.JUNGLE, + Biome.FAIRY_CAVE, + Biome.TEMPLE, + Biome.SLUM, + Biome.SNOWY_FOREST, + Biome.ISLAND, + Biome.LABORATORY +]; + +/** + * Places where you could expect a town or city, some form of large civilization + */ +export const CIVILIZATION_ENCOUNTER_BIOMES = [ + Biome.TOWN, + Biome.PLAINS, + Biome.GRASS, + Biome.TALL_GRASS, + Biome.METROPOLIS, + Biome.BEACH, + Biome.LAKE, + Biome.MEADOW, + Biome.POWER_PLANT, + Biome.GRAVEYARD, + Biome.DOJO, + Biome.FACTORY, + Biome.CONSTRUCTION_SITE, + Biome.SLUM, + Biome.ISLAND +]; + export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {}; -// Add MysteryEncounterType to biomes to enable it exclusively for those biomes -// To enable an encounter in all biomes, do not add to this map + +const extremeBiomeEncounters: MysteryEncounterType[] = []; + +const nonExtremeBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.FIELD_TRIP +]; + +const humanTransitableBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.MYSTERIOUS_CHALLENGERS, + MysteryEncounterType.SHADY_VITAMIN_DEALER +]; + +const civilizationBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.DEPARTMENT_STORE_SALE +]; + + +/** + * To add an encounter to every biome possible, use this array + */ +const anyBiomeEncounters: MysteryEncounterType[] = [ + MysteryEncounterType.FIGHT_OR_FLIGHT, + MysteryEncounterType.DARK_DEAL, + MysteryEncounterType.MYSTERIOUS_CHEST, + MysteryEncounterType.TRAINING_SESSION +]; + +/** + * ENCOUNTER BIOME MAPPING + * To add an Encounter to a biome group, instead of cluttering the map, use the biome group arrays above + * + * Adding specific Encounters to the mysteryEncountersByBiome map is for specific cases and special circumstances + * that biome groups do not cover + */ export const mysteryEncountersByBiome = new Map([ - [Biome.TOWN, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.PLAINS, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.TOWN, []], + [Biome.PLAINS, []], [Biome.GRASS, [ MysteryEncounterType.SLEEPING_SNORLAX, - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.TALL_GRASS, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.METROPOLIS, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE ]], + [Biome.TALL_GRASS, []], + [Biome.METROPOLIS, []], [Biome.FOREST, [ MysteryEncounterType.SLEEPING_SNORLAX ]], - [Biome.SEA, []], [Biome.SWAMP, []], - [Biome.BEACH, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.BEACH, []], [Biome.LAKE, []], [Biome.SEABED, []], [Biome.MOUNTAIN, [ @@ -56,31 +178,21 @@ export const mysteryEncountersByBiome = new Map([ ]], [Biome.DESERT, []], [Biome.ICE_CAVE, []], - [Biome.MEADOW, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], - [Biome.POWER_PLANT, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.MEADOW, []], + [Biome.POWER_PLANT, []], [Biome.VOLCANO, []], [Biome.GRAVEYARD, []], [Biome.DOJO, []], - [Biome.FACTORY, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.FACTORY, []], [Biome.RUINS, []], [Biome.WASTELAND, []], [Biome.ABYSS, []], [Biome.SPACE, []], - [Biome.CONSTRUCTION_SITE, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.CONSTRUCTION_SITE, []], [Biome.JUNGLE, []], [Biome.FAIRY_CAVE, []], [Biome.TEMPLE, []], - [Biome.SLUM, [ - MysteryEncounterType.DEPARTMENT_STORE_SALE - ]], + [Biome.SLUM, []], [Biome.SNOWY_FOREST, []], [Biome.ISLAND, []], [Biome.LABORATORY, []] @@ -95,16 +207,51 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxEncounter; allMysteryEncounters[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleEncounter; allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter; + allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter; - // Append encounters that can occur in any biome to biome map - const anyBiomeEncounters: MysteryEncounterType[] = Object.keys(MysteryEncounterType).filter(e => !isNaN(Number(e))).map(k => Number(k) as MysteryEncounterType); - mysteryEncountersByBiome.forEach(biomeEncounters => { - biomeEncounters.forEach(e => { - if (anyBiomeEncounters.includes(e)) { - anyBiomeEncounters.splice(anyBiomeEncounters.indexOf(e), 1); + // Add extreme encounters to biome map + extremeBiomeEncounters.forEach(encounter => { + EXTREME_ENCOUNTER_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); + } + }); + }); + // Add non-extreme encounters to biome map + nonExtremeBiomeEncounters.forEach(encounter => { + NON_EXTREME_ENCOUNTER_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); + } + }); + }); + // Add human encounters to biome map + humanTransitableBiomeEncounters.forEach(encounter => { + HUMAN_TRANSITABLE_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); + } + }); + }); + // Add civilization encounters to biome map + civilizationBiomeEncounters.forEach(encounter => { + CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => { + const encountersForBiome = mysteryEncountersByBiome.get(biome); + if (!encountersForBiome.includes(encounter)) { + encountersForBiome.push(encounter); } }); }); - mysteryEncountersByBiome.forEach(biomeEncounters => biomeEncounters.push(...anyBiomeEncounters)); + // Add ANY biome encounters to biome map + mysteryEncountersByBiome.forEach(biomeEncounters => { + anyBiomeEncounters.forEach(encounter => { + if (!biomeEncounters.includes(encounter)) { + biomeEncounters.push(encounter); + } + }); + }); } diff --git a/src/data/mystery-encounters/requirements/requirement-groups.ts b/src/data/mystery-encounters/requirements/requirement-groups.ts new file mode 100644 index 00000000000..c4d1c592df4 --- /dev/null +++ b/src/data/mystery-encounters/requirements/requirement-groups.ts @@ -0,0 +1,41 @@ +import { Moves } from "#enums/moves"; + +export const STEALING_MOVES = [ + Moves.PLUCK, + Moves.COVET, + Moves.KNOCK_OFF, + Moves.THIEF, + Moves.TRICK, + Moves.SWITCHEROO +]; + +export const DISTRACTION_MOVES = [ + Moves.FAKE_OUT, + Moves.FOLLOW_ME, + Moves.TAUNT, + Moves.ROAR, + Moves.TELEPORT, + Moves.CHARM, + Moves.FAKE_TEARS, + Moves.TICKLE, + Moves.CAPTIVATE, + Moves.RAGE_POWDER, + Moves.SUBSTITUTE, + Moves.SHED_TAIL +]; + +export const PROTECTING_MOVES = [ + Moves.PROTECT, + Moves.WIDE_GUARD, + Moves.MAX_GUARD, + Moves.SAFEGUARD, + Moves.REFLECT, + Moves.BARRIER, + Moves.QUICK_GUARD, + Moves.FLOWER_SHIELD, + Moves.KINGS_SHIELD, + Moves.CRAFTY_SHIELD, + Moves.SPIKY_SHIELD, + Moves.OBSTRUCT, + Moves.DETECT +]; diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index 6e2815babca..b5b0144c3be 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -6,5 +6,6 @@ export enum MysteryEncounterType { SLEEPING_SNORLAX, TRAINING_SESSION, DEPARTMENT_STORE_SALE, - SHADY_VITAMIN_DEALER + SHADY_VITAMIN_DEALER, + FIELD_TRIP } diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index ac04eed2688..f944615452a 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -17,6 +17,7 @@ export const mysteryEncounter: SimpleTranslationEntries = { "unit_test_dialogue": "@ec{test}@ec{test} @ec{test@ec{test}} @ec{test1} @ec{test\} @ec{test\\} @ec{test\\\} {test}", // Mystery Encounters -- Common Tier + "mysterious_chest_intro_message": "You found...@d{32} a chest?", "mysterious_chest_title": "The Mysterious Chest", "mysterious_chest_description": "A beautifully ornamented chest stands on the ground. There must be something good inside... right?", @@ -42,14 +43,14 @@ export const mysteryEncounter: SimpleTranslationEntries = { "fight_or_flight_option_1_tooltip": "(-) Hard Battle\n(+) New Item", "fight_or_flight_option_2_label": "Steal the item", "fight_or_flight_option_2_tooltip": "@[SUMMARY_GREEN]{(35%) Steal Item}\n@[SUMMARY_BLUE]{(65%) Harder Battle}", - "fight_or_flight_option_2_steal_tooltip": "@[SUMMARY_GREEN]{(?) Use a Pokémon Move}", + "fight_or_flight_option_2_steal_tooltip": "(+) @ec{option2PrimaryName} uses @ec{option2PrimaryMove}", "fight_or_flight_option_3_label": "Leave", "fight_or_flight_option_3_tooltip": "(-) No Rewards", "fight_or_flight_option_1_selected_message": "You approach the\nPokémon without fear.", "fight_or_flight_option_2_good_result": `.@d{32}.@d{32}.@d{32} $You manage to sneak your way\npast and grab the item!`, "fight_or_flight_option_2_steal_result": `.@d{32}.@d{32}.@d{32} - $Your @ec{thiefPokemon} helps you out and uses @ec{move}! + $Your @ec{option2PrimaryName} helps you out and uses @ec{option2PrimaryMove}! $ You nabbed the item!`, "fight_or_flight_option_2_bad_result": `.@d{32}.@d{32}.@d{32} $The Pokémon catches you\nas you try to sneak around!`, @@ -82,6 +83,7 @@ export const mysteryEncounter: SimpleTranslationEntries = { "shady_vitamin_dealer_title": "The Vitamin Dealer", "shady_vitamin_dealer_description": "The man opens his jacket to reveal some Pokémon vitamins. The numbers he quotes seem like a really good deal. Almost too good...\nHe offers two package deals to choose from.", "shady_vitamin_dealer_query": "Which deal will choose?", + "shady_vitamin_dealer_invalid_selection": "Pokémon must be healthy enough.", "shady_vitamin_dealer_option_1_label": "The Cheap Deal", "shady_vitamin_dealer_option_1_tooltip": "(-) Pay @ec{option1Money}\n(-) Side Effects?\n(+) Chosen Pokémon Gains 2 Random Vitamins", "shady_vitamin_dealer_option_2_label": "The Pricey Deal", @@ -94,9 +96,33 @@ export const mysteryEncounter: SimpleTranslationEntries = { $Your @ec{selectedPokemon} takes some damage\nand becomes badly poisoned...`, "shady_vitamin_dealer_poison": `But the medicine had some side effects! $Your @ec{selectedPokemon} becomes poisoned...`, + "shady_vitamin_dealer_no_bad_effects": "Looks like there were no side-effects this time.", "shady_vitamin_dealer_option_3_label": "Leave", "shady_vitamin_dealer_option_3_tooltip": "(-) No Rewards", - "shady_vitamin_dealer_outro_good": "Looks like there were no side-effects this time.", + + "field_trip_intro_message": "It's a teacher and some school children!", + "field_trip_speaker": "Teacher", + "field_trip_intro_dialogue": `Hello, there! Would you be able to\nspare a minute for my students? + $I'm teaching them about Pokémon moves\nand would love to show them a demonstration. + $Would you mind showing us one of\nthe moves your Pokémon can use?`, + "field_trip_title": "Field Trip", + "field_trip_description": "A teacher is requesting a move demonstration from a Pokémon. Depending on the move you choose, she might have something useful for you in exchange.", + "field_trip_query": "Which move category will you show off?", + "field_trip_option_1_label": "A Physical Move", + "field_trip_option_1_tooltip": "(+) Physical Item Rewards", + "field_trip_option_2_label": "A Special Move", + "field_trip_option_2_tooltip": "(+) Special Item Rewards", + "field_trip_option_3_label": "A Status Move", + "field_trip_option_3_tooltip": "(+) Status Item Rewards", + "field_trip_second_option_prompt": "Choose a move for your Pokémon to use.", + "field_trip_option_selected": "@ec{pokeName} shows off an awesome display of @ec{move}!", + "field_trip_option_incorrect": `... + $That isn't a @ec{moveCategory} move! + $I'm sorry, but I can't give you anything.`, + "field_trip_lesson_learned": `Looks like you learned a valuable lesson? + $Your Pokémon also gained some knowledge.`, + "field_trip_outro_good": "Thank you so much for your kindness!\nI hope the items I had were helpful!", + "field_trip_outro_bad": "Come along children, we'll\nfind a better demonstration elsewhere.", // Mystery Encounters -- Uncommon Tier @@ -114,6 +140,7 @@ export const mysteryEncounter: SimpleTranslationEntries = { "mysterious_challengers_outro_win": "The mysterious challenger was defeated!", // Mystery Encounters -- Rare Tier + "training_session_intro_message": "You've come across some\ntraining tools and supplies.", "training_session_title": "Training Session", "training_session_description": "These supplies look like they could be used to train a member of your party! There are a few ways you could train your Pokémon, by battling against it with the rest of your team.", @@ -146,8 +173,8 @@ export const mysteryEncounter: SimpleTranslationEntries = { "dark_deal_title": "Dark Deal", "dark_deal_description": "The disturbing fellow holds up some Pokéballs.\n\"I'll make it worth your while! You can have these strong Pokéballs as payment, All I need is a Pokémon from your team! Hehe...\"", "dark_deal_query": "What will you do?", - "dark_deal_option_1_label": "Accept", // Give player 10 rogue balls. Remove a random Pokémon from player's party. Fight a legendary Pokémon as a boss - "dark_deal_option_1_tooltip": "(+) 5 Rogue Balls\n(?) Enhance a Random Pokémon", // Give player 10 rogue balls. Remove a random Pokémon from player's party. Fight a legendary Pokémon as a boss + "dark_deal_option_1_label": "Accept", + "dark_deal_option_1_tooltip": "(+) 5 Rogue Balls\n(?) Enhance a Random Pokémon", "dark_deal_option_2_label": "Refuse", "dark_deal_option_2_tooltip": "(-) No Rewards", "dark_deal_option_1_selected": `Let's see, that @ec{pokeName} will do nicely! @@ -162,14 +189,14 @@ export const mysteryEncounter: SimpleTranslationEntries = { "sleeping_snorlax_intro_message": `As you walk down a narrow pathway, you see a towering silhouette blocking your path. $You get closer to see a Snorlax sleeping peacefully.\nIt seems like there's no way around it.`, "sleeping_snorlax_title": "Sleeping Snorlax", - "sleeping_snorlax_description": "You could attack it to try and get it to move, or simply wait for it to wake up.", + "sleeping_snorlax_description": "You could attack it to try and get it to move, or simply wait for it to wake up. Who knows how long that could take, though...", "sleeping_snorlax_query": "What will you do?", "sleeping_snorlax_option_1_label": "Fight it", "sleeping_snorlax_option_1_tooltip": "(-) Fight Sleeping Snorlax", "sleeping_snorlax_option_2_label": "Wait for it to move", "sleeping_snorlax_option_2_tooltip": "@[SUMMARY_BLUE]{(75%) Wait a short time}\n@[SUMMARY_BLUE]{(25%) Wait a long time}", - "sleeping_snorlax_option_3_label": "Steal", - "sleeping_snorlax_option_3_tooltip": "(+) Leftovers", + "sleeping_snorlax_option_3_label": "Steal its item", + "sleeping_snorlax_option_3_tooltip": "(+) @ec{option3PrimaryName} uses @ec{option3PrimaryMove}\n(+) Leftovers", "sleeping_snorlax_option_3_disabled_tooltip": "Your Pokémon need to know certain moves to choose this", "sleeping_snorlax_option_1_selected_message": "You approach the\nPokémon without fear.", "sleeping_snorlax_option_2_selected_message": `.@d{32}.@d{32}.@d{32} @@ -179,6 +206,5 @@ export const mysteryEncounter: SimpleTranslationEntries = { $But on the bright side, the Snorlax left something behind... $@s{item_fanfare}You gained a Berry!`, "sleeping_snorlax_option_3_good_result": "Your @ec{option3PrimaryName} uses @ec{option3PrimaryMove}! @s{item_fanfare}It steals Leftovers off the sleeping Snorlax and you make out like bandits!", - // "sleeping_snorlax_outro_win": "The mysterious challengers were defeated!", } as const; diff --git a/src/overrides.ts b/src/overrides.ts index ce706173691..e19a5bf20dd 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -8,18 +8,17 @@ import { PokeballCounts } from "./battle-scene"; import { PokeballType } from "./data/pokeball"; import { Gender } from "./data/gender"; import { StatusEffect } from "./data/status-effect"; -import { SpeciesStatBoosterItem, modifierTypes } from "./modifier/modifier-type"; +import { modifierTypes, SpeciesStatBoosterItem } from "./modifier/modifier-type"; import { VariantTier } from "./enums/variant-tiers"; import { EggTier } from "#enums/egg-type"; -import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars import { Abilities } from "#enums/abilities"; import { BerryType } from "#enums/berry-type"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; -import {MysteryEncounterType} from "#enums/mystery-encounter-type"; // eslint-disable-line @typescript-eslint/no-unused-vars -import {MysteryEncounterTier} from "#app/data/mystery-encounters/mystery-encounter"; // eslint-disable-line @typescript-eslint/no-unused-vars +import { 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 /** * Overrides for testing different in game situations diff --git a/src/phases.ts b/src/phases.ts index 091629b31d5..6c64cc9454f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -5363,6 +5363,7 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.revertMode(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); + break; } modifierType = typeOptions[cursor].type; break; diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 163d79784f8..cad678acf87 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -1,16 +1,16 @@ import BattleScene from "../battle-scene"; -import {addBBCodeTextObject, getBBCodeFrag, TextStyle} from "./text"; -import {Mode} from "./ui"; +import { addBBCodeTextObject, getBBCodeFrag, TextStyle } from "./text"; +import { Mode } from "./ui"; import UiHandler from "./ui-handler"; -import {Button} from "#enums/buttons"; -import {addWindow, WindowVariant} from "./ui-theme"; -import {MysteryEncounterPhase} from "../phases/mystery-encounter-phase"; -import {PartyUiMode} from "./party-ui-handler"; +import { Button } from "#enums/buttons"; +import { addWindow, WindowVariant } from "./ui-theme"; +import { MysteryEncounterPhase } from "../phases/mystery-encounter-phase"; +import { PartyUiMode } from "./party-ui-handler"; import MysteryEncounterOption from "../data/mystery-encounters/mystery-encounter-option"; import * as Utils from "../utils"; -import {isNullOrUndefined} from "../utils"; -import {getPokeballAtlasKey} from "../data/pokeball"; -import {getEncounterText} from "#app/data/mystery-encounters/mystery-encounter-utils"; +import { isNullOrUndefined } from "../utils"; +import { getPokeballAtlasKey } from "../data/pokeball"; +import { getEncounterText } from "#app/data/mystery-encounters/mystery-encounter-utils"; export default class MysteryEncounterUiHandler extends UiHandler { private cursorContainer: Phaser.GameObjects.Container; @@ -110,7 +110,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { this.unblockInput(); }, 300); }); - } else if (this.blockInput || !this.optionsMeetsReqs[cursor]) { + } else if (this.blockInput || (!this.optionsMeetsReqs[cursor] && this.filteredEncounterOptions[cursor].isDisabledOnRequirementsNotMet)) { success = false; } else { const selected = this.filteredEncounterOptions[cursor]; @@ -253,7 +253,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { if (this.blockInput) { this.blockInput = false; for (let i = 0; i < this.optionsContainer.length - 1; i++) { - if (!this.optionsMeetsReqs[i]) { + if (!this.optionsMeetsReqs[i] && this.filteredEncounterOptions[i].isDisabledOnRequirementsNotMet) { continue; } (this.optionsContainer.getAt(i) as Phaser.GameObjects.Text).setAlpha(1); @@ -319,15 +319,23 @@ export default class MysteryEncounterUiHandler extends UiHandler { optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); break; } - const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[i]; - const text = getEncounterText(this.scene, option.buttonLabel, option.style ? option.style : TextStyle.WINDOW); + this.optionsMeetsReqs.push(this.filteredEncounterOptions[i].meetsRequirements(this.scene)); + + const optionDialogue = mysteryEncounter.dialogue.encounterOptionsDialogue.options[i]; + let text; + if (this.filteredEncounterOptions[i].hasRequirements() && this.optionsMeetsReqs[i]) { + // Options with special requirements that are met are automatically colored green + // In cases where isDisabledOnRequirementsNotMet = false and requirements are not met, option will not be auto-colored + text = getEncounterText(this.scene, optionDialogue.buttonLabel, TextStyle.SUMMARY_GREEN); + } else { + text = getEncounterText(this.scene, optionDialogue.buttonLabel, optionDialogue.style ? optionDialogue.style : TextStyle.WINDOW); + } + if (text) { optionText.setText(text); } - this.optionsMeetsReqs.push(this.filteredEncounterOptions[i].meetsRequirements(this.scene)); - - if (!this.optionsMeetsReqs[i]) { + if (!this.optionsMeetsReqs[i] && this.filteredEncounterOptions[i].isDisabledOnRequirementsNotMet) { optionText.setAlpha(0.5); } if (this.blockInput) { @@ -415,7 +423,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { const mysteryEncounter = this.scene.currentBattle.mysteryEncounter; let text; const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor]; - if (!this.optionsMeetsReqs[cursor] && option.disabledTooltip) { + if (!this.optionsMeetsReqs[cursor] && this.filteredEncounterOptions[cursor].isDisabledOnRequirementsNotMet && option.disabledTooltip) { text = getEncounterText(this.scene, option.disabledTooltip, TextStyle.TOOLTIP_CONTENT); } else { text = getEncounterText(this.scene, option.buttonTooltip, TextStyle.TOOLTIP_CONTENT);