diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 1aba405745a..16c125806b2 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -4,8 +4,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "../../../battle-scene"; -import { AddPokeballModifierType } from "#app/modifier/modifier-type"; -import { PokeballType } from "../../pokeball"; +import { modifierTypes } from "#app/modifier/modifier-type"; import { getPokemonSpecies } from "../../pokemon-species"; import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; @@ -138,7 +137,7 @@ export const DarkDealEncounter: IMysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { // Give the player 5 Rogue Balls - scene.unshiftPhase(new ModifierRewardPhase(scene, () => new AddPokeballModifierType("rb", PokeballType.ROGUE_BALL, 5))); + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL)); // Start encounter with random legendary (7-10 starter strength) that has level additive const bossTypes = scene.currentBattle.mysteryEncounter.misc as Type[]; diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 5e7d3704fc0..ad693d457d9 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -3,7 +3,7 @@ import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/my import { EnemyPartyConfig, initBattleWithEnemyConfig, - leaveEncounterWithoutBattle, + leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; @@ -38,9 +38,7 @@ const namespace = "mysteryEncounter:fightOrFlight"; * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const FightOrFlightEncounter: IMysteryEncounter = - MysteryEncounterBuilder.withEncounterType( - MysteryEncounterType.FIGHT_OR_FLIGHT - ) + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withCatchAllowed(true) @@ -151,6 +149,7 @@ export const FightOrFlightEncounter: IMysteryEncounter = if (primaryPokemon) { // Use primaryPokemon to execute the thievery await showEncounterText(scene, `${namespace}:option:2:special_result`); + setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp, true); leaveEncounterWithoutBattle(scene); return; } diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index f18d7655936..76bbc58a9d9 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -29,9 +29,7 @@ const namespace = "mysteryEncounter:mysteriousChallengers"; * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const MysteriousChallengersEncounter: IMysteryEncounter = - MysteryEncounterBuilder.withEncounterType( - MysteryEncounterType.MYSTERIOUS_CHALLENGERS - ) + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withIntroSpriteConfigs([]) // These are set in onInit() diff --git a/src/data/mystery-encounters/encounters/offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/offer-you-cant-refuse-encounter.ts new file mode 100644 index 00000000000..694acfbdb94 --- /dev/null +++ b/src/data/mystery-encounters/encounters/offer-you-cant-refuse-encounter.ts @@ -0,0 +1,142 @@ +import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { modifierTypes } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import BattleScene from "../../../battle-scene"; +import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; +import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import { MoveRequirement } from "../mystery-encounter-requirements"; +import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { ModifierRewardPhase } from "#app/phases"; +import { EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; + +/** the i18n namespace for this encounter */ +const namespace = "mysteryEncounter:offerYouCantRefuse"; + +/** + * An Offer You Can't Refuse encounter. + * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/72 | GitHub Issue #72} + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} + */ +export const OfferYouCantRefuseEncounter: IMysteryEncounter = + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.OFFER_YOU_CANT_REFUSE) + .withEncounterTier(MysteryEncounterTier.GREAT) + .withSceneWaveRangeRequirement(10, 180) + .withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party + .withIntroSpriteConfigs([ + { + spriteKey: Species.LIEPARD.toString(), + fileRoot: "pokemon", + hasShadow: true, + repeat: true, + x: 0, + y: -4, + yShadow: -4 + }, + { + spriteKey: "rich_kid_m", + fileRoot: "trainer", + hasShadow: true, + x: 2, + y: 5, + yShadow: 5 + }, + ]) + .withIntroDialogue([ + { + text: `${namespace}:intro`, + }, + { + text: `${namespace}:intro_dialogue`, + speaker: `${namespace}:speaker`, + }, + ]) + .withTitle(`${namespace}:title`) + .withDescription(`${namespace}:description`) + .withQuery(`${namespace}:query`) + .withOnInit((scene: BattleScene) => { + const pokemon = getHighestStatTotalPlayerPokemon(scene, false); + const price = scene.getWaveMoneyAmount(10); + + scene.currentBattle.mysteryEncounter.setDialogueToken("strongestPokemon", pokemon.name); + scene.currentBattle.mysteryEncounter.setDialogueToken("price", price.toString()); + + // Store pokemon and price + scene.currentBattle.mysteryEncounter.misc = { + pokemon: pokemon, + price: price + }; + return true; + }) + .withOption( + new MysteryEncounterOptionBuilder() + .withOptionMode(EncounterOptionMode.DEFAULT) + .withDialogue({ + buttonLabel: `${namespace}:option:1:label`, + buttonTooltip: `${namespace}:option:1:tooltip`, + selected: [ + { + text: `${namespace}:option:1:selected`, + speaker: `${namespace}:speaker`, + }, + ], + }) + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + // Update money and remove pokemon from party + updatePlayerMoney(scene, encounter.misc.price); + scene.removePokemonFromPlayerParty(encounter.misc.pokemon); + return true; + }) + .withOptionPhase(async (scene: BattleScene) => { + // Give the player a Shiny charm + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM)); + leaveEncounterWithoutBattle(scene, true); + }) + .build() + ) + .withOption( + new MysteryEncounterOptionBuilder() + .withOptionMode(EncounterOptionMode.DISABLED_OR_DEFAULT) + .withPrimaryPokemonRequirement(new MoveRequirement(EXTORTION_MOVES)) + .withDialogue({ + buttonLabel: `${namespace}:option:2:label`, + buttonTooltip: `${namespace}:option:2:tooltip`, + disabledButtonTooltip: `${namespace}:option:2:tooltip_disabled`, + selected: [ + { + text: `${namespace}:option:2:selected`, + }, + ], + }) + .withOptionPhase(async (scene: BattleScene) => { + // Extort the rich kid for money + const encounter = scene.currentBattle.mysteryEncounter; + // Update money and remove pokemon from party + updatePlayerMoney(scene, encounter.misc.price); + + setEncounterExp(scene, encounter.options[1].primaryPokemon.id, getPokemonSpecies(Species.LIEPARD).baseExp, true); + + leaveEncounterWithoutBattle(scene, true); + }) + .build() + ) + .withSimpleOption( + { + buttonLabel: `${namespace}:option:3:label`, + buttonTooltip: `${namespace}:option:3:tooltip`, + selected: [ + { + speaker: `${namespace}:speaker`, + text: `${namespace}:option:2:selected`, + }, + ], + }, + async (scene: BattleScene) => { + // Leave encounter with no rewards or exp + leaveEncounterWithoutBattle(scene, true); + return true; + } + ) + .build(); diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index 0325eb2f2a6..6b208cabb61 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -21,9 +21,7 @@ const namespace = "mysteryEncounter:shadyVitaminDealer"; * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const ShadyVitaminDealerEncounter: IMysteryEncounter = - MysteryEncounterBuilder.withEncounterType( - MysteryEncounterType.SHADY_VITAMIN_DEALER - ) + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) .withPrimaryPokemonStatusEffectRequirement([StatusEffect.NONE]) // Pokemon must not have status @@ -230,6 +228,12 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter = { buttonLabel: `${namespace}:option:3:label`, buttonTooltip: `${namespace}:option:3:tooltip`, + selected: [ + { + text: `${namespace}:option:3:selected`, + speaker: `${namespace}:speaker` + } + ] }, async (scene: BattleScene) => { // Leave encounter with no rewards or exp diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index c905e0bd222..269b4ab8c21 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -24,9 +24,7 @@ const namespace = "mysteryEncounter:slumberingSnorlax"; * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ export const SlumberingSnorlaxEncounter: IMysteryEncounter = - MysteryEncounterBuilder.withEncounterType( - MysteryEncounterType.SLUMBERING_SNORLAX - ) + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withCatchAllowed(true) diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 9c26f54a128..dacce43919e 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -15,6 +15,7 @@ import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/saf import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter"; import { PokemonSalesmanEncounter } from "#app/data/mystery-encounters/encounters/pokemon-salesman-encounter"; +import { OfferYouCantRefuseEncounter } from "#app/data/mystery-encounters/encounters/offer-you-cant-refuse-encounter"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; @@ -134,7 +135,8 @@ const nonExtremeBiomeEncounters: MysteryEncounterType[] = [ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.MYSTERIOUS_CHALLENGERS, MysteryEncounterType.SHADY_VITAMIN_DEALER, - MysteryEncounterType.POKEMON_SALESMAN + MysteryEncounterType.POKEMON_SALESMAN, + MysteryEncounterType.OFFER_YOU_CANT_REFUSE ]; const civilizationBiomeEncounters: MysteryEncounterType[] = [ @@ -227,6 +229,7 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter; allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter; allMysteryEncounters[MysteryEncounterType.POKEMON_SALESMAN] = PokemonSalesmanEncounter; + allMysteryEncounters[MysteryEncounterType.OFFER_YOU_CANT_REFUSE] = OfferYouCantRefuseEncounter; // Add extreme encounters to biome map extremeBiomeEncounters.forEach(encounter => { diff --git a/src/data/mystery-encounters/requirements/requirement-groups.ts b/src/data/mystery-encounters/requirements/requirement-groups.ts index df905f311db..4e9eb7ad4e5 100644 --- a/src/data/mystery-encounters/requirements/requirement-groups.ts +++ b/src/data/mystery-encounters/requirements/requirement-groups.ts @@ -41,6 +41,29 @@ export const PROTECTING_MOVES = [ Moves.DETECT ]; +export const EXTORTION_MOVES = [ + Moves.BIND, + Moves.CLAMP, + Moves.INFESTATION, + Moves.SAND_TOMB, + Moves.SNAP_TRAP, + Moves.THUNDER_CAGE, + Moves.WRAP, + Moves.SPIRIT_SHACKLE, + Moves.MEAN_LOOK, + Moves.JAW_LOCK, + Moves.BLOCK, + Moves.SPIDER_WEB, + Moves.ANCHOR_SHOT, + Moves.OCTOLOCK, + Moves.PURSUIT, + Moves.CONSTRICT, + Moves.BEAT_UP, + Moves.COIL, + Moves.WRING_OUT, + Moves.STRING_SHOT +]; + export const EXTORTION_ABILITIES = [ Abilities.INTIMIDATE, Abilities.ARENA_TRAP, diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 43e25793f86..b33607c6443 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -95,6 +95,27 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boole return pokemon; } +/** + * Ties are broken by whatever mon is closer to the front of the party + * @param scene + * @param unfainted - default false. If true, only picks from unfainted mons. + * @returns + */ +export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon { + const party = scene.getParty(); + let pokemon: PlayerPokemon; + party.every(p => { + if (unfainted && p.isFainted()) { + return true; + } + + pokemon = pokemon ? pokemon?.stats.reduce((a, b) => a + b) < p?.stats.reduce((a, b) => a + b) ? p : pokemon : p; + return true; + }); + + return pokemon; +} + /** * * NOTE: This returns ANY random species, including those locked behind eggs, etc. diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index f13f246bac0..8ec7d4967d3 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -12,5 +12,6 @@ export enum MysteryEncounterType { LOST_AT_SEA, //might be generalized later on FIERY_FALLOUT, THE_STRONG_STUFF, - POKEMON_SALESMAN + POKEMON_SALESMAN, + OFFER_YOU_CANT_REFUSE } diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index f58f18dd65a..2454859884b 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -12,6 +12,7 @@ import { slumberingSnorlaxDialogue } from "#app/locales/en/mystery-encounters/sl import { trainingSessionDialogue } from "#app/locales/en/mystery-encounters/training-session-dialogue"; import { theStrongStuffDialogue } from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue"; import { pokemonSalesmanDialogue } from "#app/locales/en/mystery-encounters/pokemon-salesman-dialogue"; +import { offerYouCantRefuseDialogue } from "#app/locales/en/mystery-encounters/offer-you-cant-refuse-dialogue"; /** * Patterns that can be used: @@ -48,5 +49,6 @@ export const mysteryEncounter = { lostAtSea: lostAtSeaDialogue, fieryFallout: fieryFalloutDialogue, theStrongStuff: theStrongStuffDialogue, - pokemonSalesman: pokemonSalesmanDialogue + pokemonSalesman: pokemonSalesmanDialogue, + offerYouCantRefuse: offerYouCantRefuseDialogue } as const; diff --git a/src/locales/en/mystery-encounters/offer-you-cant-refuse-dialogue.ts b/src/locales/en/mystery-encounters/offer-you-cant-refuse-dialogue.ts new file mode 100644 index 00000000000..dda5810b0a4 --- /dev/null +++ b/src/locales/en/mystery-encounters/offer-you-cant-refuse-dialogue.ts @@ -0,0 +1,34 @@ +export const offerYouCantRefuseDialogue = { + intro: "You're stopped by a rich looking boy.", + speaker: "Rich Kid", + intro_dialogue: `Good day to you. + $I can't help but notice that your\n{{strongestPokemon}} looks positively divine! + $I've always wanted to have a pet like that! + $I'd pay you handsomely, and\nI'll even give you this old bauble!`, + title: "An Offer You Can't Refuse", + description: "You're being offered a @[TOOLTIP_TITLE]{Shiny Charm} and {{price, money}} for your {{strongestPokemon}}!\n\nIt's is an extremely good deal, but can you really bear to part with such a strong team member?", + query: "What will you do?", + option: { + 1: { + label: "Accept the Deal", + tooltip: "(-) Lose {{strongestPokemon}}\n(+) Gain a @[TOOLTIP_TITLE]{Shiny Charm}\n(+) Gain {{price, money}}", + selected: `Wonderful!@d{32} Come along, {{strongestPokemon}}! + $It's time to show you off to everyone at the yacht club! + $They'll be so jealous!`, + }, + 2: { + label: "Extort the Kid", + tooltip: "(+) Gain {{price, money}}", + tooltip_disabled: "Your Pokémon need to have certain moves or abilities to choose this", + selected: `My word, we're being robbed, Liepard! + $How wonderful! + $Now I'll have an amazing\nstory for the yacht club!`, + }, + 3: { + label: "Leave", + tooltip: "(-) No Rewards", + selected: `What a rotten day... + $Ah, well. Let's return to the yacht club then, Liepard.`, + } + }, +}; diff --git a/src/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.ts b/src/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.ts index ecf88577c75..1f2d62751ea 100644 --- a/src/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.ts +++ b/src/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.ts @@ -24,8 +24,7 @@ export const shadyVitaminDealerDialogue = { 3: { label: "Leave", tooltip: "(-) No Rewards", - selected: `You float about in the boat, steering without direction until you finally spot a landmark you remember. - $You and your Pokémon are fatigued from the whole ordeal.`, + selected: "Heh, wouldn't have figured you for a coward.", }, selected: `The man hands you two bottles and quickly disappears. \${{selectedPokemon}} gained {{boost1}} and {{boost2}} boosts!` diff --git a/src/overrides.ts b/src/overrides.ts index e19a5bf20dd..32a2988641f 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -117,9 +117,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0; */ // 1 to 256, set to null to ignore -export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null; +export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256; export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; -export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null; +export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.OFFER_YOU_CANT_REFUSE; /** * MODIFIER / ITEM OVERRIDES