diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 2b01b777a64..249ddebdd60 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1430,7 +1430,7 @@ export default class BattleScene extends SceneBase { const wave = waveIndex || this.currentBattle?.waveIndex || 0; this.waveSeed = Utils.shiftCharCodes(this.seed, wave); Phaser.Math.RND.sow([ this.waveSeed ]); - // console.log("Wave Seed:", this.waveSeed, wave); + console.log("Wave Seed:", this.waveSeed, wave); this.rngCounter = 0; } diff --git a/src/data/mystery-encounters/encounters/clowing-around-encounter.ts b/src/data/mystery-encounters/encounters/clowing-around-encounter.ts new file mode 100644 index 00000000000..a3f1d78da89 --- /dev/null +++ b/src/data/mystery-encounters/encounters/clowing-around-encounter.ts @@ -0,0 +1,181 @@ +import { + EnemyPartyConfig, + initBattleWithEnemyConfig, + setEncounterRewards, +} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { + trainerConfigs, + TrainerPartyCompoundTemplate, + TrainerPartyTemplate, +} from "#app/data/trainer-config"; +import { ModifierTier } from "#app/modifier/modifier-tier"; +import { modifierTypes } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { PartyMemberStrength } from "#enums/party-member-strength"; +import BattleScene from "#app/battle-scene"; +import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { Species } from "#enums/species"; +import { TrainerType } from "#enums/trainer-type"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; + +/** the i18n namespace for the encounter */ +const namespace = "mysteryEncounter:clowningAround"; + +/** + * Clowning Around encounter. + * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69} + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} + */ +export const ClowningAroundEncounter: IMysteryEncounter = + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) + .withEncounterTier(MysteryEncounterTier.ULTRA) + .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 + .withIntroSpriteConfigs([ + { + spriteKey: Species.MR_MIME.toString(), + fileRoot: "pokemon", + hasShadow: true, + repeat: true, + x: -25, + tint: 0.3, + y: -3, + yShadow: -3 + }, + { + spriteKey: Species.BLACEPHALON.toString(), + fileRoot: "pokemon/exp", + hasShadow: true, + repeat: true, + x: 25, + tint: 0.3, + y: -3, + yShadow: -3 + }, + { + spriteKey: "harlequin", + fileRoot: "trainer", + hasShadow: true, + x: 0 + }, + ]) + .withIntroDialogue([ + { + text: `${namespace}.intro`, + }, + { + text: `${namespace}.intro_dialogue`, + speaker: `${namespace}.speaker` + }, + ]) + .withOnInit((scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + + // Clown trainer is pulled from pool of boss trainers (gym leaders) for the biome + // They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons + const clownTrainerType = TrainerType.HARLEQUIN; + const clownPartyTemplate = new TrainerPartyCompoundTemplate( + new TrainerPartyTemplate(1, PartyMemberStrength.STRONG), + new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER), + new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)); + const clownConfig = trainerConfigs[clownTrainerType].copy(); + clownConfig.setPartyTemplates(clownPartyTemplate); + clownConfig.partyTemplateFunc = null; // Overrides party template func + + encounter.enemyPartyConfigs.push({ + trainerConfig: clownConfig, + pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon + { + species: getPokemonSpecies(Species.MR_MIME), + isBoss: false + }, + { + species: getPokemonSpecies(Species.BLACEPHALON), + isBoss: true + }, + ] + }); + + return true; + }) + .withTitle(`${namespace}.title`) + .withDescription(`${namespace}.description`) + .withQuery(`${namespace}.query`) + .withSimpleOption( + { + buttonLabel: `${namespace}.option.1.label`, + buttonTooltip: `${namespace}.option.1.tooltip`, + selected: [ + { + text: `${namespace}.option.selected`, + }, + ], + }, + async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + // Spawn battle + const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; + + setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true }); + await initBattleWithEnemyConfig(scene, config); + } + ) + .withSimpleOption( + { + buttonLabel: `${namespace}.option.2.label`, + buttonTooltip: `${namespace}.option.2.tooltip`, + selected: [ + { + text: `${namespace}.option.selected`, + }, + ], + }, + async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + // Spawn hard fight with ULTRA/GREAT reward (can improve with luck) + const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1]; + + setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true }); + + // Seed offsets to remove possibility of different trainers having exact same teams + let ret; + scene.executeWithSeedOffset(() => { + ret = initBattleWithEnemyConfig(scene, config); + }, scene.currentBattle.waveIndex * 100); + return ret; + } + ) + .withSimpleOption( + { + buttonLabel: `${namespace}.option.3.label`, + buttonTooltip: `${namespace}.option.3.tooltip`, + selected: [ + { + text: `${namespace}.option.selected`, + }, + ], + }, + async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + // Spawn brutal fight with ROGUE/ULTRA/GREAT reward (can improve with luck) + const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2]; + + // To avoid player level snowballing from picking this option + encounter.expMultiplier = 0.9; + + setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true }); + + // Seed offsets to remove possibility of different trainers having exact same teams + let ret; + scene.executeWithSeedOffset(() => { + ret = initBattleWithEnemyConfig(scene, config); + }, scene.currentBattle.waveIndex * 1000); + return ret; + } + ) + .withOutroDialogue([ + { + text: `${namespace}.outro`, + }, + ]) + .build(); diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 6d49e6d014f..53cf834dcad 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -21,6 +21,7 @@ import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounter import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter"; import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter"; import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter"; +import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowing-around-encounter"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; @@ -141,7 +142,7 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.MYSTERIOUS_CHALLENGERS, MysteryEncounterType.SHADY_VITAMIN_DEALER, MysteryEncounterType.THE_POKEMON_SALESMAN, - MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE + MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, ]; const civilizationBiomeEncounters: MysteryEncounterType[] = [ @@ -159,7 +160,8 @@ const anyBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.DELIBIRDY, MysteryEncounterType.A_TRAINERS_TEST, MysteryEncounterType.TRASH_TO_TREASURE, - MysteryEncounterType.BERRIES_ABOUND + MysteryEncounterType.BERRIES_ABOUND, + MysteryEncounterType.CLOWNING_AROUND ]; /** @@ -249,6 +251,7 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.A_TRAINERS_TEST] = ATrainersTestEncounter; allMysteryEncounters[MysteryEncounterType.TRASH_TO_TREASURE] = TrashToTreasureEncounter; allMysteryEncounters[MysteryEncounterType.BERRIES_ABOUND] = BerriesAboundEncounter; + allMysteryEncounters[MysteryEncounterType.CLOWNING_AROUND] = ClowningAroundEncounter; // Add extreme encounters to biome map extremeBiomeEncounters.forEach(encounter => { diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index ed050f6eb77..c166f287030 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -12,6 +12,7 @@ import PokemonData from "#app/system/pokemon-data"; import { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; +import * as Utils from "#app/utils"; import { isNullOrUndefined } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Biome } from "#enums/biome"; @@ -19,7 +20,6 @@ import { TrainerType } from "#enums/trainer-type"; import i18next from "i18next"; import BattleScene from "#app/battle-scene"; import Trainer, { TrainerVariant } from "#app/field/trainer"; -import * as Utils from "#app/utils"; import { Gender } from "#app/data/gender"; import { Nature } from "#app/data/nature"; import { Moves } from "#enums/moves"; @@ -152,7 +152,17 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: let isBoss = false; if (!loaded) { if (trainerType || trainerConfig) { - battle.enemyParty[e] = battle.trainer.genPartyMember(e); + // Allows overriding a trainer's pokemon to use specific species/data + if (e < partyConfig?.pokemonConfigs?.length) { + const config = partyConfig?.pokemonConfigs?.[e]; + level = config.level ? config.level : level; + dataSource = config.dataSource; + enemySpecies = config.species; + isBoss = config.isBoss; + battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, dataSource); + } else { + battle.enemyParty[e] = battle.trainer.genPartyMember(e); + } } else { if (e < partyConfig?.pokemonConfigs?.length) { const config = partyConfig?.pokemonConfigs?.[e]; diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index 6cf2c1b4fb2..d82a0075d46 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -18,5 +18,6 @@ export enum MysteryEncounterType { ABSOLUTE_AVARICE, A_TRAINERS_TEST, TRASH_TO_TREASURE, - BERRIES_ABOUND + BERRIES_ABOUND, + CLOWNING_AROUND } diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index 099802a5f53..11e6887b3e0 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -18,6 +18,7 @@ import { absoluteAvariceDialogue } from "#app/locales/en/mystery-encounters/abso import { aTrainersTestDialogue } from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue"; import { trashToTreasureDialogue } from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue"; import { berriesAboundDialogue } from "#app/locales/en/mystery-encounters/berries-abound-dialogue"; +import { clowningAroundDialogue } from "#app/locales/en/mystery-encounters/clowning-around-dialogue"; /** * Patterns that can be used: @@ -60,5 +61,6 @@ export const mysteryEncounter = { absoluteAvarice: absoluteAvariceDialogue, aTrainersTest: aTrainersTestDialogue, trashToTreasure: trashToTreasureDialogue, - berriesAbound: berriesAboundDialogue + berriesAbound: berriesAboundDialogue, + clowningAround: clowningAroundDialogue } as const; diff --git a/src/locales/en/mystery-encounters/clowning-around-dialogue.ts b/src/locales/en/mystery-encounters/clowning-around-dialogue.ts new file mode 100644 index 00000000000..4b70c39eefb --- /dev/null +++ b/src/locales/en/mystery-encounters/clowning-around-dialogue.ts @@ -0,0 +1,33 @@ +export const clowningAroundDialogue = { + intro: "It's...@d{64} a clown?", + speaker: "Clown", + intro_dialogue: `Bumbling buffoon,\nbrace for a brilliant battle! + $You’ll be beaten by this brawling busker!\nBring it!`, + title: "Clowning Around", + description: "The clown seems eager to goad you into a battle, but to what end?\n\nSomething is off about this encounter.", + query: "What will you do?", + option: { + 1: { + label: "Battle the Clown", + tooltip: "(-) Strange Battle\n(?) Affects Pokémon Abilities", + selected: "Your pitiful Pokémon are poised for a pathetic performance!" + }, + 2: { + label: "Remain Unprovoked", + tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Items", + selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!", + selected_2: `The clown's Blacephalon uses Trick! + All of your {{switchPokemon}}'s items were randomly swapped!`, + selected_3: "Flustered fool, fall for my flawless deception!", + }, + 3: { + label: "Return the Insults", + tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Types", + selected: "I'm appalled at your absurd antics!\nTaste my temper!", + selected_2: `The clown's Blacephalon uses\na move you've never seen before! + All of your team's types were randomly swapped!`, + selected_3: "Flustered fool, fall for my flawless deception!", + }, + }, + outro: "The clown and his cohorts\ndisappear in a puff of smoke." +}; diff --git a/src/overrides.ts b/src/overrides.ts index 1196797fac0..8e83692925d 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -127,9 +127,9 @@ class DefaultOverrides { // ------------------------- // 1 to 256, set to null to ignore - readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null; + readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256; readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; - readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null; + readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.CLOWNING_AROUND; // ------------------------- // MODIFIER / ITEM OVERRIDES