From 32741835fd1c71829f09bb231d35f66d41b15426 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Sun, 22 Sep 2024 13:53:18 -0400 Subject: [PATCH 01/13] more Mystery Encounter bug fixes --- src/battle-scene.ts | 75 +++++---- src/battle.ts | 2 + .../encounters/berries-abound-encounter.ts | 18 +-- .../global-trade-system-encounter.ts | 3 +- .../encounters/weird-dream-encounter.ts | 146 ++++++++++++------ .../utils/encounter-pokemon-utils.ts | 7 +- src/phases/encounter-phase.ts | 15 +- 7 files changed, 173 insertions(+), 93 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f55f1658648..82421fb1e17 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2,7 +2,7 @@ import Phaser from "phaser"; import UI from "./ui/ui"; import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon"; import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "./data/pokemon-species"; -import { Constructor, isNullOrUndefined } from "#app/utils"; +import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "./utils"; import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; import { PokeballType } from "./data/pokeball"; @@ -1201,32 +1201,12 @@ export default class BattleScene extends SceneBase { // Check for mystery encounter // Can only occur in place of a standard (non-boss) wild battle, waves 10-180 - const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves(); - if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < highestMysteryEncounterWave && newWaveIndex > lowestMysteryEncounterWave) { - const roll = Utils.randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT); - - // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor - const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; - const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; - - // If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well) - // Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET - const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (newWaveIndex - lowestMysteryEncounterWave); - const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length; - const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER; - - const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!; - - // If the most recent ME was 3 or fewer waves ago, can never spawn a ME - const canSpawn = encounteredEvents.length === 0 || (newWaveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE); - - if (canSpawn && roll < successRate) { - newBattleType = BattleType.MYSTERY_ENCOUNTER; - // Reset base spawn weight - this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; - } else { - this.mysteryEncounterSaveData.encounterSpawnChance = sessionEncounterRate + WEIGHT_INCREMENT_ON_SPAWN_MISS; - } + if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType)) { + newBattleType = BattleType.MYSTERY_ENCOUNTER; + // Reset base spawn weight + this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; + } else if (newBattleType === BattleType.WILD) { + this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; } } @@ -1267,9 +1247,8 @@ export default class BattleScene extends SceneBase { if (newBattleType === BattleType.MYSTERY_ENCOUNTER) { // Disable double battle on mystery encounters (it may be re-enabled as part of encounter) this.currentBattle.double = false; - this.executeWithSeedOffset(() => { - this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounterType); - }, this.currentBattle.waveIndex << 4); + // Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome + this.currentBattle.mysteryEncounterType = mysteryEncounterType; } //this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6)); @@ -3097,6 +3076,42 @@ export default class BattleScene extends SceneBase { } } + isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { + const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves(); + if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) { + // If ME type is already defined in session data, no need to roll RNG check + if (!isNullOrUndefined(sessionDataEncounterType)) { + return true; + } + + // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor + const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; + const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; + + // If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well) + // Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET + const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (waveIndex - lowestMysteryEncounterWave); + const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length; + const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER; + + const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!; + + // If the most recent ME was 3 or fewer waves ago, can never spawn a ME + const canSpawn = encounteredEvents.length === 0 || (waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE); + + if (canSpawn) { + let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT; + // Always rolls the check on the same offset to ensure no RNG changes from reloading session + this.executeWithSeedOffset(() => { + roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT); + }, waveIndex * 3 * 1000); + return roll < successRate; + } + } + + return false; + } + /** * Loads or generates a mystery encounter * @param encounterType used to load session encounter when restarting game, etc. diff --git a/src/battle.ts b/src/battle.ts index d99e1a91c15..f9c16d0189d 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -18,6 +18,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { CustomModifierSettings } from "#app/modifier/modifier-type"; import { ModifierTier } from "#app/modifier/modifier-tier"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; export enum ClassicFixedBossWaves { // TODO: other fixed wave battles should be added here @@ -88,6 +89,7 @@ export default class Battle { public playerFaintsHistory: FaintLogEntry[] = []; public enemyFaintsHistory: FaintLogEntry[] = []; + public mysteryEncounterType?: MysteryEncounterType; /** If the current battle is a Mystery Encounter, this will always be defined */ public mysteryEncounter?: MysteryEncounter; diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 9ff223947f5..2468ab0af8c 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -127,7 +127,7 @@ export const BerriesAboundEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const numBerries = encounter.misc.numBerries; - const doBerryRewards = async () => { + const doBerryRewards = () => { const berryText = numBerries + " " + i18next.t(`${namespace}.berries`); scene.playSound("item_fanfare"); @@ -135,7 +135,7 @@ export const BerriesAboundEncounter: MysteryEncounter = // Generate a random berry and give it to the first Pokemon with room for it for (let i = 0; i < numBerries; i++) { - await tryGiveBerry(scene); + tryGiveBerry(scene); } }; @@ -178,7 +178,7 @@ export const BerriesAboundEncounter: MysteryEncounter = if (speedDiff < 1) { // Caught and attacked by boss, gets +1 to all stats at start of fight - const doBerryRewards = async () => { + const doBerryRewards = () => { const berryText = numBerries + " " + i18next.t(`${namespace}.berries`); scene.playSound("item_fanfare"); @@ -186,7 +186,7 @@ export const BerriesAboundEncounter: MysteryEncounter = // Generate a random berry and give it to the first Pokemon with room for it for (let i = 0; i < numBerries; i++) { - await tryGiveBerry(scene); + tryGiveBerry(scene); } }; @@ -204,7 +204,7 @@ export const BerriesAboundEncounter: MysteryEncounter = // Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2 const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1)/0.08), numBerries), 2); encounter.setDialogueToken("numBerries", String(numBerriesGrabbed)); - const doFasterBerryRewards = async () => { + const doFasterBerryRewards = () => { const berryText = numBerriesGrabbed + " " + i18next.t(`${namespace}.berries`); scene.playSound("item_fanfare"); @@ -212,7 +212,7 @@ export const BerriesAboundEncounter: MysteryEncounter = // Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first) for (let i = 0; i < numBerriesGrabbed; i++) { - await tryGiveBerry(scene, fastestPokemon); + tryGiveBerry(scene, fastestPokemon); } }; @@ -242,7 +242,7 @@ export const BerriesAboundEncounter: MysteryEncounter = ) .build(); -async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) { +function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) { const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType; const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType; @@ -254,7 +254,7 @@ async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokem && m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) { - await applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry); + applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry); return; } } @@ -265,7 +265,7 @@ async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokem && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) { - await applyModifierTypeToPlayerPokemon(scene, pokemon, berry); + applyModifierTypeToPlayerPokemon(scene, pokemon, berry); return; } } diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 0f9f06c9a68..18b913f80ce 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -153,7 +153,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = return true; }, onHover: () => { - const formName = tradePokemon.species.forms?.[pokemon.formIndex]?.formName; + const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[pokemon.formIndex].formName : null; + // const formName = tradePokemon.species.forms?.[pokemon.formIndex]?.formName; const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : ""); const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : ""); showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false); diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 0b3b4434278..7ddafc7732e 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -22,6 +22,7 @@ import { getLevelTotalExp } from "#app/data/exp"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { Challenges } from "#enums/challenges"; +import { Moves } from "#enums/moves"; /** i18n namespace for encounter */ const namespace = "mysteryEncounter:weirdDream"; @@ -105,7 +106,7 @@ const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [40, 50]; export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) .withEncounterTier(MysteryEncounterTier.ROGUE) - .withDisallowedChallenges(Challenges.SINGLE_TYPE) + .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withIntroSpriteConfigs([ { @@ -216,7 +217,7 @@ export const WeirdDreamEncounter: MysteryEncounter = pokemon.levelExp = 0; pokemon.calculateStats(); - pokemon.updateInfo(); + await pokemon.updateInfo(); } leaveEncounterWithoutBattle(scene, true); @@ -346,6 +347,9 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon // If the previous pokemon had pokerus, transfer to new pokemon newPokemon.pokerus = previousPokemon.pokerus; + // Transfer previous Pokemon's luck value + newPokemon.luck = previousPokemon.getLuck(); + // If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock) newPokemon.ivs = newPokemon.ivs.map((iv, index) => { return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv; @@ -358,44 +362,15 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon // Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species newPokemon.generateAndPopulateMoveset(); - - // Try to find a favored STAB move - let favoredMove; - for (const move of newPokemon.moveset) { - // Needs to match first type, second type will be replaced - if (move?.getMove().type === newPokemon.getTypes()[0]) { - favoredMove = move; - break; - } - } - // If was unable to find a move, uses first move in moveset (typically a high power STAB move) - favoredMove = favoredMove ?? newPokemon.moveset[0]; + // Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move + const newPokemonGeneratedMoveset = newPokemon.moveset; newPokemon.moveset = previousPokemon.moveset; - let eggMoveIndex: null | number = null; - if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { - const eggMoves = speciesEggMoves[speciesRootForm]; - const randomEggMoveIndex = randSeedInt(4); - const randomEggMove = eggMoves[randomEggMoveIndex]; - if (newPokemon.moveset.length < 4) { - newPokemon.moveset.push(new PokemonMove(randomEggMove)); - } else { - eggMoveIndex = randSeedInt(4); - newPokemon.moveset[eggMoveIndex] = new PokemonMove(randomEggMove); - } - // For pokemon that the player owns (including ones just caught), unlock the egg move - if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) { - await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); - } - } - if (favoredMove) { - let favoredMoveIndex = randSeedInt(4); - while (favoredMoveIndex === eggMoveIndex) { - favoredMoveIndex = randSeedInt(4); - } - newPokemon.moveset[favoredMoveIndex] = favoredMove; - } + const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm); + + // Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset) + addFavoredMoveToNewPokemonMoveset(scene, newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex); // Randomize the second type of the pokemon // If the pokemon does not normally have a second type, it will gain 1 @@ -412,7 +387,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon for (const item of transformation.heldItems) { item.pokemonId = newPokemon.id; - scene.addModifier(item, false, false, false, true); + await scene.addModifier(item, false, false, false, true); } // Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef @@ -423,11 +398,12 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK); // Def or SpDef stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF); - // const mod = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().newModifier(newPokemon, 20, stats); - const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(scene.getParty(), [20, stats]); + const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU() + .generateType(scene.getParty(), [20, stats]) + ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); const modifier = modType?.newModifier(newPokemon); if (modifier) { - scene.addModifier(modifier); + await scene.addModifier(modifier, false, false, false, true); } } @@ -435,13 +411,15 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon newPokemon.passive = previousPokemon.passive; newPokemon.calculateStats(); - newPokemon.initBattleInfo(); + await newPokemon.updateInfo(); } // One random pokemon will get its passive unlocked const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive); if (passiveDisabledPokemon?.length > 0) { - passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)].passive = true; + const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)]; + enablePassiveMon.passive = true; + await enablePassiveMon.updateInfo(true); } // If at least one new starter was unlocked, play 1 fanfare @@ -451,7 +429,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon } function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies { - let newSpecies: PokemonSpecies | undefined; + let newSpecies: PokemonSpecies | undefined = undefined; while (isNullOrUndefined(newSpecies)) { const bstCap = originalBst + bstSearchRange[1]; const bstMin = Math.max(originalBst + bstSearchRange[0], 0); @@ -566,3 +544,83 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo } }); } + +/** + * Returns index of the new egg move within the Pokemon's moveset (not the index of the move in `speciesEggMoves`) + * @param scene + * @param newPokemon + * @param speciesRootForm + */ +async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species): Promise { + let eggMoveIndex: null | number = null; + if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { + const eggMoves: Moves[] = speciesEggMoves[speciesRootForm].slice(0); + const eggMoveIndices = [0, 1, 2, 3]; + randSeedShuffle(eggMoveIndices); + let randomEggMoveIndex = eggMoveIndices.pop(); + let randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex!] : null; + let retries = 0; + while (retries < 3 && (!randomEggMove || newPokemon.moveset.some(m => m?.moveId === randomEggMove))) { + // If Pokemon already knows this move, roll for another egg move + randomEggMoveIndex = eggMoveIndices.pop(); + randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex!] : null; + retries++; + } + + if (randomEggMove) { + if (!newPokemon.moveset.some(m => m?.moveId === randomEggMove)) { + if (newPokemon.moveset.length < 4) { + newPokemon.moveset.push(new PokemonMove(randomEggMove)); + } else { + eggMoveIndex = randSeedInt(4); + newPokemon.moveset[eggMoveIndex] = new PokemonMove(randomEggMove); + } + } + + // For pokemon that the player owns (including ones just caught), unlock the egg move + if (!isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { + await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex!, true); + } + } + } + + return eggMoveIndex; +} + +/** + * Returns index of the new egg move within the Pokemon's moveset (not the index of the move in `speciesEggMoves`) + * @param scene + * @param newPokemon + * @param newPokemonGeneratedMoveset + * @param newEggMoveIndex + */ +function addFavoredMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, newPokemonGeneratedMoveset: (PokemonMove | null)[], newEggMoveIndex: number | null) { + let favoredMove: PokemonMove | null = null; + for (const move of newPokemonGeneratedMoveset) { + // Needs to match first type, second type will be replaced + if (move?.getMove().type === newPokemon.getTypes()[0] && !newPokemon.moveset.some(m => m?.moveId === move?.moveId)) { + favoredMove = move; + break; + } + } + // If was unable to find a favored move, uses first move in moveset that isn't already known (typically a high power STAB move) + // Otherwise, it gains no favored move + if (!favoredMove) { + for (const move of newPokemonGeneratedMoveset) { + // Needs to match first type, second type will be replaced + if (!newPokemon.moveset.some(m => m?.moveId === move?.moveId)) { + favoredMove = move; + break; + } + } + } + // Finally, assign favored move to random index that isn't the new egg move index + if (favoredMove) { + let favoredMoveIndex = randSeedInt(4); + while (newEggMoveIndex !== null && favoredMoveIndex === newEggMoveIndex) { + favoredMoveIndex = randSeedInt(4); + } + + newPokemon.moveset[favoredMoveIndex] = favoredMove; + } +} diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 5db84186471..7277cca0600 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -311,7 +311,9 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h * @param value */ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) { - const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE().generateType(pokemon.scene.getParty(), [value]); + const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE() + .generateType(pokemon.scene.getParty(), [value]) + ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE); const modifier = modType?.newModifier(pokemon); if (modifier) { await pokemon.scene.addModifier(modifier, false, false, false, true); @@ -780,8 +782,7 @@ export function getGoldenBugNetSpecies(): PokemonSpecies { */ export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) { const currentBattle = scene.currentBattle; - // Default to use the first generated level from enemyLevels, or generate a new one if it DNE - const baseLevel = currentBattle.enemyLevels && currentBattle.enemyLevels?.length > 0 ? currentBattle.enemyLevels[0] : currentBattle.getLevelForWave(); + const baseLevel = currentBattle.getLevelForWave(); // Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0); diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index cead9de0fc6..16e6a591d9e 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -62,7 +62,13 @@ export class EncounterPhase extends BattlePhase { const battle = this.scene.currentBattle; - // Init Mystery Encounter if there is one + // Generate and Init Mystery Encounter + if (battle.battleType === BattleType.MYSTERY_ENCOUNTER && !battle.mysteryEncounter) { + this.scene.executeWithSeedOffset(() => { + const currentSessionEncounterType = battle.mysteryEncounterType; + battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType); + }, battle.waveIndex << 4); + } const mysteryEncounter = battle.mysteryEncounter; if (mysteryEncounter) { // If ME has an onInit() function, call it @@ -152,13 +158,10 @@ export class EncounterPhase extends BattlePhase { if (battle.battleType === BattleType.TRAINER) { loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct? } else if (battle.battleType === BattleType.MYSTERY_ENCOUNTER) { - if (!battle.mysteryEncounter) { - battle.mysteryEncounter = this.scene.getMysteryEncounter(mysteryEncounter?.encounterType); - } - if (battle.mysteryEncounter.introVisuals) { + if (battle.mysteryEncounter?.introVisuals) { loadEnemyAssets.push(battle.mysteryEncounter.introVisuals.loadAssets().then(() => battle.mysteryEncounter!.introVisuals!.initSprite())); } - if (battle.mysteryEncounter.loadAssets.length > 0) { + if (battle.mysteryEncounter?.loadAssets && battle.mysteryEncounter.loadAssets.length > 0) { loadEnemyAssets.push(...battle.mysteryEncounter.loadAssets); } // Load Mystery Encounter Exclamation bubble and sfx From eb0e8ccca7e2433d2390f92891075dec48354b66 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Sun, 22 Sep 2024 14:19:47 -0400 Subject: [PATCH 02/13] possible Pokemon Salesman shiny fix --- .../encounters/the-pokemon-salesman-encounter.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index ba6a628f51e..edd687b4270 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -59,11 +59,12 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false)); - const tries = 0; + let tries = 0; // Reroll any species that don't have HAs while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) { species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false)); + tries++; } let pokemon: PlayerPokemon; @@ -71,7 +72,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = // If no HA mon found or you roll 1%, give shiny Magikarp species = getPokemonSpecies(Species.MAGIKARP); const hiddenIndex = species.ability2 ? 2 : 1; - pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true); + pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true, 0); } else { const hiddenIndex = species.ability2 ? 2 : 1; pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex); From cf80abe33f7882f04812c63622c96d45f8e9ba00 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Sun, 22 Sep 2024 19:04:56 -0400 Subject: [PATCH 03/13] small locales fixes --- .../encounters/mysterious-chest-encounter.ts | 24 ++++++++++++------- .../bug-type-superfan-dialogue.json | 2 +- .../clowning-around-dialogue.json | 2 +- .../dancing-lessons-dialogue.json | 2 +- .../mysterious-chest-dialogue.json | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index b5da4340dea..4117de14fc4 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -19,10 +19,11 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; const namespace = "mysteryEncounter:mysteriousChest"; const RAND_LENGTH = 100; -const COMMON_REWARDS_WEIGHT = 20; // 20% -const ULTRA_REWARDS_WEIGHT = 50; // 30% -const ROGUE_REWARDS_WEIGHT = 60; // 10% -const MASTER_REWARDS_WEIGHT = 65; // 5% +const TRAP_PERCENT = 35; +const COMMON_REWARDS_PERCENT = 20; +const ULTRA_REWARDS_PERCENT = 30; +const ROGUE_REWARDS_PERCENT = 10; +const MASTER_REWARDS_PERCENT = 5; /** * Mysterious Chest encounter. @@ -83,6 +84,11 @@ export const MysteriousChestEncounter: MysteryEncounter = encounter.enemyPartyConfigs = [config]; encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName()); + encounter.setDialogueToken("trapPercent", TRAP_PERCENT.toString()); + encounter.setDialogueToken("commonPercent", COMMON_REWARDS_PERCENT.toString()); + encounter.setDialogueToken("ultraPercent", ULTRA_REWARDS_PERCENT.toString()); + encounter.setDialogueToken("roguePercent", ROGUE_REWARDS_PERCENT.toString()); + encounter.setDialogueToken("masterPercent", MASTER_REWARDS_PERCENT.toString()); return true; }) @@ -109,7 +115,7 @@ export const MysteriousChestEncounter: MysteryEncounter = roll }; - if (roll >= MASTER_REWARDS_WEIGHT) { + if (roll < TRAP_PERCENT) { // Chest is springing trap, change to red chest sprite const blueChestSprites = introVisuals.getSpriteAtIndex(0); const redChestSprites = introVisuals.getSpriteAtIndex(1); @@ -124,7 +130,7 @@ export const MysteriousChestEncounter: MysteryEncounter = // Open the chest const encounter = scene.currentBattle.mysteryEncounter!; const roll = encounter.misc.roll; - if (roll < COMMON_REWARDS_WEIGHT) { + if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) { // Choose between 2 COMMON / 2 GREAT tier items (20%) setEncounterRewards(scene, { guaranteedModifierTiers: [ @@ -137,7 +143,7 @@ export const MysteriousChestEncounter: MysteryEncounter = // Display result message then proceed to rewards queueEncounterMessage(scene, `${namespace}.option.1.normal`); leaveEncounterWithoutBattle(scene); - } else if (roll < ULTRA_REWARDS_WEIGHT) { + } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) { // Choose between 3 ULTRA tier items (30%) setEncounterRewards(scene, { guaranteedModifierTiers: [ @@ -149,13 +155,13 @@ export const MysteriousChestEncounter: MysteryEncounter = // Display result message then proceed to rewards queueEncounterMessage(scene, `${namespace}.option.1.good`); leaveEncounterWithoutBattle(scene); - } else if (roll < ROGUE_REWARDS_WEIGHT) { + } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) { // Choose between 2 ROGUE tier items (10%) setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE] }); // Display result message then proceed to rewards queueEncounterMessage(scene, `${namespace}.option.1.great`); leaveEncounterWithoutBattle(scene); - } else if (roll < MASTER_REWARDS_WEIGHT) { + } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) { // Choose 1 MASTER tier item (5%) setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER] }); // Display result message then proceed to rewards diff --git a/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json b/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json index 09488addb98..188c94c7994 100644 --- a/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json +++ b/src/locales/en/mystery-encounters/bug-type-superfan-dialogue.json @@ -8,7 +8,7 @@ "option": { "1": { "label": "Offer to Battle", - "tooltip": "(-) Challenging Battle\n(+) Teach a Pokémon a Bug Type Move", + "tooltip": "(-) Challenging Battle\n(+) Teach any Pokémon a Bug Type Move", "selected": "A challenge, eh?\nMy bugs are more than ready for you!" }, "2": { diff --git a/src/locales/en/mystery-encounters/clowning-around-dialogue.json b/src/locales/en/mystery-encounters/clowning-around-dialogue.json index 17781240838..743358b4f29 100644 --- a/src/locales/en/mystery-encounters/clowning-around-dialogue.json +++ b/src/locales/en/mystery-encounters/clowning-around-dialogue.json @@ -10,7 +10,7 @@ "label": "Battle the Clown", "tooltip": "(-) Strange Battle\n(?) Affects Pokémon Abilities", "selected": "Your pitiful Pokémon are poised for a pathetic performance!", - "apply_ability_dialogue": "A sensational showcase!\nYour savvy suits a sensational skill as spoils!", + "apply_ability_dialogue": "A sensational showcase!\nYour savvy suits a special skill as spoils!", "apply_ability_message": "The clown is offering to permanently Skill Swap one of your Pokémon's ability to {{ability}}!", "ability_prompt": "Would you like to permanently teach a Pokémon the {{ability}} ability?", "ability_gained": "@s{level_up_fanfare}{{chosenPokemon}} gained the {{ability}} ability!" diff --git a/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json b/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json index 8e2883ecb16..065c4e0c06c 100644 --- a/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json +++ b/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json @@ -12,7 +12,7 @@ }, "2": { "label": "Learn Its Dance", - "tooltip": "(+) Teach a Pokémon Revelation Dance", + "tooltip": "(+) Teach any Pokémon Revelation Dance", "selected": "You watch the {{oricorioName}} closely as it performs its dance...$@s{level_up_fanfare}Your {{selectedPokemon}} learned from the {{oricorioName}}!" }, "3": { diff --git a/src/locales/en/mystery-encounters/mysterious-chest-dialogue.json b/src/locales/en/mystery-encounters/mysterious-chest-dialogue.json index 1de7a5992ed..e789771b7b1 100644 --- a/src/locales/en/mystery-encounters/mysterious-chest-dialogue.json +++ b/src/locales/en/mystery-encounters/mysterious-chest-dialogue.json @@ -6,7 +6,7 @@ "option": { "1": { "label": "Open It", - "tooltip": "@[SUMMARY_BLUE]{(35%) Something terrible}\n@[SUMMARY_GREEN]{(40%) Okay Rewards}\n@[SUMMARY_GREEN]{(20%) Good Rewards}\n@[SUMMARY_GREEN]{(4%) Great Rewards}\n@[SUMMARY_GREEN]{(1%) Amazing Rewards}", + "tooltip": "@[SUMMARY_BLUE]{({{trapPercent}}%) Something terrible}\n@[SUMMARY_GREEN]{({{commonPercent}}%) Okay Rewards}\n@[SUMMARY_GREEN]{({{ultraPercent}}%) Good Rewards}\n@[SUMMARY_GREEN]{({{roguePercent}}%) Great Rewards}\n@[SUMMARY_GREEN]{({{masterPercent}}%) Amazing Rewards}", "selected": "You open the chest to find...", "normal": "Just some normal tools and items.", "good": "Some pretty nice tools and items.", From 05b9e74729f1f2d1423836d2f2bb4d6201a979bf Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Mon, 23 Sep 2024 19:23:46 -0400 Subject: [PATCH 04/13] balance adjustments to certain ME battles --- src/battle-scene.ts | 2 +- .../encounters/absolute-avarice-encounter.ts | 25 +++++++++-- .../encounters/berries-abound-encounter.ts | 7 +++- .../teleporting-hijinks-encounter.ts | 2 +- .../encounters/uncommon-breed-encounter.ts | 7 +++- .../delibirdy-dialogue.json | 2 +- .../berries-abound-encounter.test.ts | 42 +++++++++++++++---- .../teleporting-hijinks-encounter.test.ts | 5 ++- .../uncommon-breed-encounter.test.ts | 42 ++++++++++++++++--- 9 files changed, 109 insertions(+), 25 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 82421fb1e17..75dacadff78 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1201,7 +1201,7 @@ export default class BattleScene extends SceneBase { // Check for mystery encounter // Can only occur in place of a standard (non-boss) wild battle, waves 10-180 - if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType)) { + if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER || !isNullOrUndefined(mysteryEncounterType)) { newBattleType = BattleType.MYSTERY_ENCOUNTER; // Reset base spawn weight this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index c9605aadc49..93d47fb7e5b 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -202,10 +202,15 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType; bossModifierConfigs.push({ modifier: modifierType }); } - - scene.removeModifier(berryMod); }); + // Do NOT remove the real berries yet or else it will be persisted in the session data + + // SpDef buff below wave 50, +1 to all stats otherwise + const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? + [Stat.SPDEF] : + [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]; + // Calculate boss mon const config: EnemyPartyConfig = { levelAdditiveModifier: 1, @@ -214,12 +219,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = species: getPokemonSpecies(Species.GREEDENT), isBoss: true, bossSegments: 3, - moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF], + moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH], modifierConfigs: bossModifierConfigs, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); } } ], @@ -230,6 +235,18 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = return true; }) + .withOnVisualsStart((scene: BattleScene) => { + // Remove the berries from the party + // Session has been safely saved at this point, so data won't be lost + const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; + berryItems.forEach(berryMod => { + scene.removeModifier(berryMod); + }); + + scene.updateModifiers(true); + + return true; + }) .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 2468ab0af8c..4cc5edc5208 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -190,11 +190,16 @@ export const BerriesAboundEncounter: MysteryEncounter = } }; + // Defense/Spd buffs below wave 50, +1 to all stats otherwise + const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? + [Stat.DEF, Stat.SPDEF, Stat.SPD] : + [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]; + const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); }; setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); await showEncounterText(scene, `${namespace}.option.2.selected_bad`); diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index c35817255e0..f5d1891a391 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -172,7 +172,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) { const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); - // Defense/Spd buffs below wave 50, Atk/Def/Spd buffs otherwise + // Defense/Spd buffs below wave 50, +1 to all stats otherwise const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? [Stat.DEF, Stat.SPDEF, Stat.SPD] : [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 4f8a43ce364..4eade78baa6 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -72,6 +72,11 @@ export const UncommonBreedEncounter: MysteryEncounter = encounter.misc.pokemon = pokemon; + // Defense/Spd buffs below wave 50, +1 to all stats otherwise + const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? + [Stat.DEF, Stat.SPDEF, Stat.SPD] : + [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]; + const config: EnemyPartyConfig = { pokemonConfigs: [{ level: level, @@ -81,7 +86,7 @@ export const UncommonBreedEncounter: MysteryEncounter = tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1)); } }], }; diff --git a/src/locales/en/mystery-encounters/delibirdy-dialogue.json b/src/locales/en/mystery-encounters/delibirdy-dialogue.json index ca1fefa3a39..eb9e02f1312 100644 --- a/src/locales/en/mystery-encounters/delibirdy-dialogue.json +++ b/src/locales/en/mystery-encounters/delibirdy-dialogue.json @@ -25,5 +25,5 @@ "selected": "You toss the {{chosenItem}} to the {{delibirdName}}s,\nwho chatter amongst themselves excitedly.$They turn back to you and happily give you a present!" } }, - "outro": "The {{delibirdName}} pack happily waddles off into the distance.$What a curious little exchange!" + "outro": "The {{delibirdName}} flock happily waddles off into the distance.$What a curious little exchange!" } \ No newline at end of file diff --git a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 78f4a477216..d71b982bfc7 100644 --- a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -17,6 +17,7 @@ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encount import * as EncounterDialogueUtils from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { Abilities } from "#enums/abilities"; const namespace = "mysteryEncounter:berriesAbound"; const defaultParty = [Species.PYUKUMUKU, Species.MAGIKARP, Species.PIKACHU]; @@ -35,13 +36,15 @@ describe("Berries Abound - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); - game.override.startingModifier([]); - game.override.startingHeldItems([]); + game.override.mysteryEncounterChance(100) + .mysteryEncounterTier(MysteryEncounterTier.COMMON) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves() + .startingModifier([]) + .startingHeldItems([]) + .enemyAbility(Abilities.BALL_FETCH) + .enemyPassiveAbility(Abilities.BALL_FETCH); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -168,7 +171,30 @@ describe("Berries Abound - Mystery Encounter", () => { }); }); - it("should start battle if fastest pokemon is slower than boss", async () => { + it("should start battle if fastest pokemon is slower than boss below wave 50", async () => { + game.override.startingWave(41); + const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); + await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); + + const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; + const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId; + // Setting enemy's level arbitrarily high to outspeed + config.pokemonConfigs![0].dataSource!.level = 1000; + + await runMysteryEncounterToEnd(game, 2, undefined, true); + + const enemyField = scene.getEnemyField(); + expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(enemyField.length).toBe(1); + expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); + + // Should be enraged + expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 1, 0, 0]); + expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.selected_bad`); + }); + + it("should start battle if fastest pokemon is slower than boss above wave 50", async () => { + game.override.startingWave(57); const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); diff --git a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 44a5197a39e..f3723983b14 100644 --- a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -42,6 +42,7 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { .startingWave(defaultWave) .startingBiome(defaultBiome) .disableTrainerWaves() + .enemyAbility(Abilities.BALL_FETCH) .enemyPassiveAbility(Abilities.BALL_FETCH); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( @@ -257,8 +258,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { it("should start a battle against an extra enraged boss above wave 50", { retry: 5 }, async () => { game.override.startingWave(56); - await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty); - await runMysteryEncounterToEnd(game, 1, undefined, true); + await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [Species.PIKACHU]); + await runMysteryEncounterToEnd(game, 2, undefined, true); const enemyField = scene.getEnemyField(); expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 1, 0, 0]); expect(enemyField[0].isBoss()).toBe(true); diff --git a/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts index 2f8c4e5111a..3943063d7c0 100644 --- a/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/uncommon-breed-encounter.test.ts @@ -24,6 +24,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { Stat } from "#enums/stat"; import { BerryModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; +import { Abilities } from "#enums/abilities"; const namespace = "mysteryEncounter:uncommonBreed"; const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; @@ -42,11 +43,13 @@ describe("Uncommon Breed - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override.mysteryEncounterChance(100) + .mysteryEncounterTier(MysteryEncounterTier.COMMON) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves() + .enemyAbility(Abilities.BALL_FETCH) + .enemyPassiveAbility(Abilities.BALL_FETCH); vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -107,7 +110,34 @@ describe("Uncommon Breed - Mystery Encounter", () => { }); }); - it.skip("should start a fight against the boss", async () => { + it.skip("should start a fight against the boss below wave 50", async () => { + const phaseSpy = vi.spyOn(scene, "pushPhase"); + const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase"); + await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); + + const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; + const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId; + + await runMysteryEncounterToEnd(game, 1, undefined, true); + + const enemyField = scene.getEnemyField(); + expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(enemyField.length).toBe(1); + expect(enemyField[0].species.speciesId).toBe(speciesToSpawn); + + const statStagePhases = unshiftPhaseSpy.mock.calls.filter(p => p[0] instanceof StatStageChangePhase)[0][0] as any; + expect(statStagePhases.stats).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]); + + // Should have used its egg move pre-battle + const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); + expect(movePhases.length).toBe(1); + const eggMoves: Moves[] = speciesEggMoves[getPokemonSpecies(speciesToSpawn).getRootSpeciesId()]; + const usedMove = (movePhases[0] as MovePhase).move.moveId; + expect(eggMoves.includes(usedMove)).toBe(true); + }); + + it.skip("should start a fight against the boss above wave 50", async () => { + game.override.startingWave(57); const phaseSpy = vi.spyOn(scene, "pushPhase"); const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase"); await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); From ff256f78dd1ad0bdd3a771fa3490aedb0f41ecb0 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Mon, 23 Sep 2024 19:29:32 -0400 Subject: [PATCH 05/13] can no longer go above max rev seed stacks --- .../encounters/absolute-avarice-encounter.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 93d47fb7e5b..7a3de0a5bbb 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -10,7 +10,7 @@ import { PersistentModifierRequirement } from "#app/data/mystery-encounters/myst import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { BerryModifier } from "#app/modifier/modifier"; +import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { BattlerTagType } from "#enums/battler-tag-type"; @@ -268,7 +268,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = const givePartyPokemonReviverSeeds = () => { const party = scene.getParty(); party.forEach(p => { - if (revSeed) { + const heldItems = p.getHeldItems(); + if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) { const seedModifier = revSeed.newModifier(p); if (seedModifier) { encounter.setDialogueToken("foodReward", seedModifier.type.name); From b2b88c37cf79beb03f75f46f0be338132a482e0a Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Mon, 23 Sep 2024 19:36:09 -0400 Subject: [PATCH 06/13] fix Absolute Avarice test --- .../encounters/absolute-avarice-encounter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index 58c8e1fbc30..b73d2a42cc2 100644 --- a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -128,7 +128,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { expect(enemyField[0].species.speciesId).toBe(Species.GREEDENT); const moveset = enemyField[0].moveset.map(m => m?.moveId); expect(moveset?.length).toBe(4); - expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF]); + expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH]); const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); expect(movePhases.length).toBe(1); From e77595ebb8ad44e5a9379de16a1948d196254621 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com> Date: Tue, 24 Sep 2024 09:29:40 -0400 Subject: [PATCH 07/13] Update src/data/mystery-encounters/encounters/global-trade-system-encounter.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- .../encounters/global-trade-system-encounter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 18b913f80ce..e1ddabc9cc1 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -154,7 +154,6 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = }, onHover: () => { const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[pokemon.formIndex].formName : null; - // const formName = tradePokemon.species.forms?.[pokemon.formIndex]?.formName; const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : ""); const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : ""); showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false); From 1513f2a57ddec2409b5be208e38bae5e4be1d462 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Tue, 24 Sep 2024 11:32:22 -0400 Subject: [PATCH 08/13] more ME balance changes and bug fixes --- .../encounters/a-trainers-test-encounter.ts | 3 +- .../encounters/absolute-avarice-encounter.ts | 9 ++-- .../an-offer-you-cant-refuse-encounter.ts | 11 ++++- .../encounters/delibirdy-encounter.ts | 10 ++-- .../encounters/field-trip-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 46 +++++++++++++++---- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../encounters/trash-to-treasure-encounter.ts | 16 +++++-- .../encounters/weird-dream-encounter.ts | 2 +- .../mystery-encounters/mystery-encounters.ts | 8 +++- .../a-trainers-test-dialogue.json | 2 +- .../clowning-around-dialogue.json | 6 +-- .../delibirdy-dialogue.json | 2 +- .../global-trade-system-dialogue.json | 4 +- .../the-pokemon-salesman-dialogue.json | 4 +- .../training-session-dialogue.json | 8 ++-- .../trash-to-treasure-dialogue.json | 2 +- src/modifier/modifier-type.ts | 11 +++-- src/modifier/modifier.ts | 12 +++-- .../encounters/delibirdy-encounter.test.ts | 22 ++++----- .../the-strong-stuff-encounter.test.ts | 2 +- 21 files changed, 122 insertions(+), 62 deletions(-) diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index f7666fa1b37..88fdadf8588 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -15,6 +15,7 @@ import { EggTier } from "#enums/egg-type"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes } from "#app/modifier/modifier-type"; +import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:aTrainersTest"; @@ -27,7 +28,7 @@ const namespace = "mysteryEncounter:aTrainersTest"; export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST) .withEncounterTier(MysteryEncounterTier.ROGUE) - .withSceneWaveRangeRequirement(100, 180) + .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withIntroSpriteConfigs([]) // These are set in onInit() .withIntroDialogue([ { diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 7a3de0a5bbb..efcd41054ea 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -159,12 +159,6 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = ]) .withHideWildIntroMessage(true) .withAutoHideIntroVisuals(false) - .withOnVisualsStart((scene: BattleScene) => { - doGreedentSpriteSteal(scene); - doBerrySpritePile(scene); - - return true; - }) .withIntroDialogue([ { text: `${namespace}.intro`, @@ -236,6 +230,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = return true; }) .withOnVisualsStart((scene: BattleScene) => { + doGreedentSpriteSteal(scene); + doBerrySpritePile(scene); + // Remove the berries from the party // Session has been safely saved at this point, so data won't be lost const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[]; diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 62025957abe..013feb6e5a4 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -8,7 +8,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; @@ -61,7 +61,14 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = .withOnInit((scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true); - const price = scene.getWaveMoneyAmount(10); + + // Base value of Relic Gold, increased linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased + // Starter value 1-3 -> 10x + // Starter value 10 -> 30x + const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(true); + const starterValue: number = speciesStarters[baseSpecies] ?? 1; + const multiplier = Math.max(3 * starterValue, 10); + const price = scene.getWaveMoneyAmount(multiplier); encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("price", price.toString()); diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 90ed486efd7..2b0f7b0722e 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -10,7 +10,7 @@ import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier"; +import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import i18next from "#app/plugins/i18n"; @@ -33,7 +33,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [ "PokemonBaseStatTotalModifier" ]; -const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 1.5; +const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2; /** * Delibird-y encounter. @@ -122,9 +122,9 @@ export const DelibirdyEncounter: MysteryEncounter = return true; }) .withOptionPhase(async (scene: BattleScene) => { - // Give the player an Ability Charm + // Give the player an Amulet Coin // Check if the player has max stacks of that item already - const existing = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier; + const existing = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier; if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -133,7 +133,7 @@ export const DelibirdyEncounter: MysteryEncounter = scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM)); + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.AMULET_COIN)); } leaveEncounterWithoutBattle(scene, true); diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 82f27c5e59b..49936b67efe 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:fieldTrip"; export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP) .withEncounterTier(MysteryEncounterTier.COMMON) - .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + .withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100) .withIntroSpriteConfigs([ { spriteKey: "preschooler_m", diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 18b913f80ce..71e9a71a554 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -11,9 +11,10 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon import { getTypeRgb } from "#app/data/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; +import * as Utils from "#app/utils"; import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils"; -import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; -import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import PokemonData from "#app/system/pokemon-data"; import i18next from "i18next"; @@ -24,6 +25,8 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter import { trainerNamePools } from "#app/data/trainer-names"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { Moves } from "#enums/moves"; +import { speciesEggMoves } from "#app/data/egg-moves"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:globalTradeSystem"; @@ -222,21 +225,48 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false); // Extra shiny roll at 1/128 odds (boosted by events and charms) if (!tradePokemon.shiny) { - // 512/65536 -> 1/128 - tradePokemon.trySetShinySeed(512, true); + const baseShinyChance = 512; + const shinyThreshold = new Utils.IntegerHolder(baseShinyChance); + if (scene.eventManager.isEventActive()) { + shinyThreshold.value *= scene.eventManager.getShinyMultiplier(); + } + scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); + + // Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms + // Maximum shiny chance of 4090/65536 -> 1/16, cannot improve further after that + const shinyChance = Math.min(shinyThreshold.value, 4090); + + tradePokemon.trySetShinySeed(shinyChance, false); } // Extra HA roll at base 1/64 odds (boosted by events and charms) - if (pokemon.species.abilityHidden) { - const hiddenIndex = pokemon.species.ability2 ? 2 : 1; - if (pokemon.abilityIndex < hiddenIndex) { + const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1; + if (tradePokemon.species.abilityHidden) { + if (tradePokemon.abilityIndex < hiddenIndex) { const hiddenAbilityChance = new IntegerHolder(64); scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); if (hasHiddenAbility) { - pokemon.abilityIndex = hiddenIndex; + tradePokemon.abilityIndex = hiddenIndex; + } + } + } + + // If Pokemon is still not shiny or with HA, give the Pokemon a random Common egg move in its moveset + if (!tradePokemon.shiny && (!tradePokemon.species.abilityHidden || tradePokemon.abilityIndex < hiddenIndex)) { + const eggMoves: Moves[] = speciesEggMoves[tradePokemon.getSpeciesForm().getRootSpeciesId()]; + if (eggMoves) { + // Cannot gen the rare egg move, only 1 of the first 3 common moves + const eggMove = eggMoves[randSeedInt(3)]; + if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) { + if (tradePokemon.moveset.length < 4) { + tradePokemon.moveset.push(new PokemonMove(eggMove)); + } else { + const eggMoveIndex = randSeedInt(4); + tradePokemon.moveset[eggMoveIndex] = new PokemonMove(eggMove); + } } } } diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 55cb10644e8..56328695128 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -33,7 +33,7 @@ const BST_INCREASE_VALUE = 10; */ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF) - .withEncounterTier(MysteryEncounterTier.GREAT) + .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party .withMaxAllowedEncounters(1) diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index d295c8ab548..985984e73e8 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -16,7 +16,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; import { PokemonMove } from "#app/field/pokemon"; -import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; /** the i18n namespace for this encounter */ @@ -24,6 +23,9 @@ const namespace = "mysteryEncounter:trashToTreasure"; const SOUND_EFFECT_WAIT_TIME = 700; +// Items will cost 2.5x as much for remainder of the run +const SHOP_ITEM_COST_MULTIPLIER = 2.5; + /** * Trash to Treasure encounter. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809} @@ -79,6 +81,8 @@ export const TrashToTreasureEncounter: MysteryEncounter = scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav"); scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav"); + encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString()); + return true; }) .withOption( @@ -102,8 +106,14 @@ export const TrashToTreasureEncounter: MysteryEncounter = transitionMysteryEncounterIntroVisuals(scene); await tryApplyDigRewardItems(scene); - // Give the player the Black Sludge curse - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE)); + const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [SHOP_ITEM_COST_MULTIPLIER]); + const modifier = blackSludge?.newModifier(); + if (modifier) { + await scene.addModifier(modifier, false, false, false, true); + scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 }); + await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true); + } + leaveEncounterWithoutBattle(scene, true); }) .build() diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 7ddafc7732e..43a3bfd8d3b 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -429,7 +429,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon } function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies { - let newSpecies: PokemonSpecies | undefined = undefined; + let newSpecies: PokemonSpecies | undefined; while (isNullOrUndefined(newSpecies)) { const bstCap = originalBst + bstSearchRange[1]; const bstMin = Math.max(originalBst + bstSearchRange[0], 0); diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index cc2eaf234c4..0ce4a5c2506 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -32,6 +32,7 @@ import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fu import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter"; import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter"; import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; +import { getBiomeName } from "#app/data/biomes"; /** * Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT @@ -362,11 +363,16 @@ export function initMysteryEncounters() { }); // Add ANY biome encounters to biome map - mysteryEncountersByBiome.forEach(biomeEncounters => { + let encounterBiomeTableLog = ""; + mysteryEncountersByBiome.forEach((biomeEncounters, biome) => { anyBiomeEncounters.forEach(encounter => { if (!biomeEncounters.includes(encounter)) { biomeEncounters.push(encounter); } }); + + encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`; }); + + console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog); } diff --git a/src/locales/en/mystery-encounters/a-trainers-test-dialogue.json b/src/locales/en/mystery-encounters/a-trainers-test-dialogue.json index c96c0d5f327..553f4822f07 100644 --- a/src/locales/en/mystery-encounters/a-trainers-test-dialogue.json +++ b/src/locales/en/mystery-encounters/a-trainers-test-dialogue.json @@ -31,7 +31,7 @@ "option": { "1": { "label": "Accept the Challenge", - "tooltip": "(-) Tough Battle\n(+) Gain a @[TOOLTIP_TITLE]{Very Rare Egg}" + "tooltip": "(-) Extremely Tough Battle\n(+) Gain a @[TOOLTIP_TITLE]{Very Rare Egg}" }, "2": { "label": "Refuse the Challenge", diff --git a/src/locales/en/mystery-encounters/clowning-around-dialogue.json b/src/locales/en/mystery-encounters/clowning-around-dialogue.json index 743358b4f29..c60a5eb0c01 100644 --- a/src/locales/en/mystery-encounters/clowning-around-dialogue.json +++ b/src/locales/en/mystery-encounters/clowning-around-dialogue.json @@ -8,7 +8,7 @@ "option": { "1": { "label": "Battle the Clown", - "tooltip": "(-) Strange Battle\n(?) Affects Pokémon Abilities", + "tooltip": "(-) Strange Battle\n(?) Affects One Pokémon's Ability", "selected": "Your pitiful Pokémon are poised for a pathetic performance!", "apply_ability_dialogue": "A sensational showcase!\nYour savvy suits a special skill as spoils!", "apply_ability_message": "The clown is offering to permanently Skill Swap one of your Pokémon's ability to {{ability}}!", @@ -17,14 +17,14 @@ }, "2": { "label": "Remain Unprovoked", - "tooltip": "(-) Upsets the Clown\n(?) Affects Pokémon Items", + "tooltip": "(?) Affects One Pokémon's Items", "selected": "Dismal dodger, you deny a delightful duel?\nFeel my fury!", "selected_2": "The clown's {{blacephalonName}} uses Trick!\nAll 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", + "tooltip": "(?) Affects Your Pokémons' Types", "selected": "Dismal dodger, you deny a delightful duel?\nFeel my fury!", "selected_2": "The clown's {{blacephalonName}} uses a strange move!\nAll of your team's types were randomly swapped!", "selected_3": "Flustered fool, fall for my flawless deception!" diff --git a/src/locales/en/mystery-encounters/delibirdy-dialogue.json b/src/locales/en/mystery-encounters/delibirdy-dialogue.json index eb9e02f1312..0a16c424f5a 100644 --- a/src/locales/en/mystery-encounters/delibirdy-dialogue.json +++ b/src/locales/en/mystery-encounters/delibirdy-dialogue.json @@ -1,7 +1,7 @@ { - "intro": "A pack of {{delibirdName}} have appeared!", + "intro": "A flock of {{delibirdName}} have appeared!", "title": "Delibir-dy", "description": "The {{delibirdName}}s are looking at you expectantly, as if they want something. Perhaps giving them an item or some money would satisfy them?", "query": "What will you give them?", diff --git a/src/locales/en/mystery-encounters/global-trade-system-dialogue.json b/src/locales/en/mystery-encounters/global-trade-system-dialogue.json index 1cc420355b7..84abebd4cbb 100644 --- a/src/locales/en/mystery-encounters/global-trade-system-dialogue.json +++ b/src/locales/en/mystery-encounters/global-trade-system-dialogue.json @@ -11,13 +11,13 @@ }, "2": { "label": "Wonder Trade", - "tooltip": "(+) Send one of your Pokémon to the GTS and get a random Pokémon in return" + "tooltip": "(+) Send one of your Pokémon to the GTS and get a random special Pokémon in return" }, "3": { "label": "Trade an Item", "trade_options_prompt": "Select an item to send.", "invalid_selection": "This Pokémon doesn't have legal items to trade.", - "tooltip": "(+) Send one of your Items to the GTS and get a random new Item" + "tooltip": "(+) Send one of your Items to the GTS and get a random improved Item" }, "4": { "label": "Leave", diff --git a/src/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json b/src/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json index 7e8091bbfff..0dc22574686 100644 --- a/src/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json +++ b/src/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json @@ -3,8 +3,8 @@ "speaker": "Gentleman", "intro_dialogue": "Hello there! Have I got a deal just for YOU!", "title": "The Pokémon Salesman", - "description": "\"This {{purchasePokemon}} is extremely unique and carries an ability not normally found in its species! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"", - "description_shiny": "\"This {{purchasePokemon}} is extremely unique and has a pigment not normally found in its species! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"", + "description": "\"This {{purchasePokemon}} is extremely unique and @[TOOLTIP_TITLE]{carries an ability not normally found in its species}! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"", + "description_shiny": "\"This {{purchasePokemon}} is extremely unique and @[TOOLTIP_TITLE]{has a pigment not normally found in its species}! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"", "query": "What will you do?", "option": { "1": { diff --git a/src/locales/en/mystery-encounters/training-session-dialogue.json b/src/locales/en/mystery-encounters/training-session-dialogue.json index f018018fe4e..61597dc7c89 100644 --- a/src/locales/en/mystery-encounters/training-session-dialogue.json +++ b/src/locales/en/mystery-encounters/training-session-dialogue.json @@ -1,24 +1,24 @@ { "intro": "You've come across some\ntraining tools and supplies.", "title": "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.", + "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 @[TOOLTIP_TITLE]{battling and defeating it with the rest of your team}.", "query": "How should you train?", "invalid_selection": "Pokémon must be healthy enough.", "option": { "1": { "label": "Light Training", - "tooltip": "(-) Light Battle\n(+) Improve 2 Random IVs of Pokémon", + "tooltip": "(-) Light Battle with Chosen Pokémon\n(+) Permanently Improve 2 Random IVs of Chosen Pokémon", "finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its {{stat1}} and {{stat2}} IVs were improved!" }, "2": { "label": "Moderate Training", - "tooltip": "(-) Moderate Battle\n(+) Change Pokémon's Nature", + "tooltip": "(-) Moderate Battle with Chosen Pokémon\n(+) Permanently Change Chosen Pokémon's Nature", "select_prompt": "Select a new nature\nto train your Pokémon in.", "finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its nature was changed to {{nature}}!" }, "3": { "label": "Heavy Training", - "tooltip": "(-) Harsh Battle\n(+) Change Pokémon's Ability", + "tooltip": "(-) Harsh Battle with Chosen Pokémon\n(+) Permanently Change Chosen Pokémon's Ability", "select_prompt": "Select a new ability\nto train your Pokémon in.", "finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its ability was changed to {{ability}}!" }, diff --git a/src/locales/en/mystery-encounters/trash-to-treasure-dialogue.json b/src/locales/en/mystery-encounters/trash-to-treasure-dialogue.json index fe2cb54f5b1..da35082248c 100644 --- a/src/locales/en/mystery-encounters/trash-to-treasure-dialogue.json +++ b/src/locales/en/mystery-encounters/trash-to-treasure-dialogue.json @@ -6,7 +6,7 @@ "option": { "1": { "label": "Dig for Valuables", - "tooltip": "(-) Items in Shops Cost 3x\n(+) Gain Amazing Items", + "tooltip": "(-) Items in Shops Will Cost {{costMultiplier}}x\n(+) Gain Amazing Items", "selected": "You wade through the garbage pile, becoming mired in filth.$There's no way any respectable shopkeeper would\nsell you items at the normal rate in your grimy state!$You'll have to pay extra for items now.$However, you found some incredible items in the garbage!" }, "2": { diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 2b4f910034b..6503aaafbc5 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1577,17 +1577,22 @@ export const modifierTypes = { MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { - return new PokemonBaseStatTotalModifierType(pregenArgs[0] as integer); + return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number); } return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20)); }), MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs) { - return new PokemonBaseStatFlatModifierType(pregenArgs[0] as integer, pregenArgs[1] as Stat[]); + return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]); } return new PokemonBaseStatFlatModifierType(Utils.randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]); }), - MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type)), + MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { + if (pregenArgs) { + return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type, pregenArgs[0] as number)); + } + return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type, 2.5)); + }), MYSTERY_ENCOUNTER_MACHO_BRACE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", "macho_brace", (type, args) => new Modifiers.PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id)), MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET", "golden_net", (type, _args) => new Modifiers.BoostBugSpawnModifier(type)), }; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index d091648b1b0..6c998105c1d 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2576,8 +2576,12 @@ export class LockModifierTiersModifier extends PersistentModifier { * Black Sludge item */ export class HealShopCostModifier extends PersistentModifier { - constructor(type: ModifierType, stackCount?: integer) { + public readonly shopMultiplier: number; + + constructor(type: ModifierType, shopMultiplier: number, stackCount?: integer) { super(type, stackCount); + + this.shopMultiplier = shopMultiplier; } match(modifier: Modifier): boolean { @@ -2585,11 +2589,11 @@ export class HealShopCostModifier extends PersistentModifier { } clone(): HealShopCostModifier { - return new HealShopCostModifier(this.type, this.stackCount); + return new HealShopCostModifier(this.type, this.shopMultiplier, this.stackCount); } apply(args: any[]): boolean { - (args[0] as Utils.IntegerHolder).value *= Math.pow(3, this.getStackCount()); + (args[0] as Utils.IntegerHolder).value *= this.shopMultiplier; return true; } @@ -2608,7 +2612,7 @@ export class BoostBugSpawnModifier extends PersistentModifier { return modifier instanceof BoostBugSpawnModifier; } - clone(): HealShopCostModifier { + clone(): BoostBugSpawnModifier { return new BoostBugSpawnModifier(this.type, this.stackCount); } diff --git a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 7e452fd90c7..dfa793fe7b5 100644 --- a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -11,7 +11,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; -import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier"; +import { BerryModifier, HealingBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; @@ -104,35 +104,35 @@ describe("Delibird-y - Mystery Encounter", () => { expect(scene.money).toBe(initialMoney - price); }); - it("Should give the player a Hidden Ability Charm", async () => { + it("Should give the player an Amulet Coin", async () => { scene.money = 200000; await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); await runMysteryEncounterToEnd(game, 1); - const itemModifier = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier; + const itemModifier = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier; expect(itemModifier).toBeDefined(); expect(itemModifier?.stackCount).toBe(1); }); - it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => { + it("Should give the player a Shell Bell if they have max stacks of Amulet Coins", async () => { scene.money = 200000; await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); - // 5 Healing Charms + // Max Amulet Coins scene.modifiers = []; - const abilityCharm = generateModifierType(scene, modifierTypes.ABILITY_CHARM)!.newModifier() as HiddenAbilityRateBoosterModifier; - abilityCharm.stackCount = 4; - await scene.addModifier(abilityCharm, true, false, false, true); + const amuletCoin = generateModifierType(scene, modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier; + amuletCoin.stackCount = 5; + await scene.addModifier(amuletCoin, true, false, false, true); await scene.updateModifiers(true); await runMysteryEncounterToEnd(game, 1); - const abilityCharmAfter = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier); + const amuletCoinAfter = scene.findModifier(m => m instanceof MoneyMultiplierModifier); const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); - expect(abilityCharmAfter).toBeDefined(); - expect(abilityCharmAfter?.stackCount).toBe(4); + expect(amuletCoinAfter).toBeDefined(); + expect(amuletCoinAfter?.stackCount).toBe(5); expect(shellBellAfter).toBeDefined(); expect(shellBellAfter?.stackCount).toBe(1); }); diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index 4bcb063fff0..6930195b6cb 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -70,7 +70,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty); expect(TheStrongStuffEncounter.encounterType).toBe(MysteryEncounterType.THE_STRONG_STUFF); - expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT); + expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON); expect(TheStrongStuffEncounter.dialogue).toBeDefined(); expect(TheStrongStuffEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]); expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`); From 6dc1aedb8e5bdcb5b1b7ce8f3c049a9b9fde8abd Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Tue, 24 Sep 2024 11:34:42 -0400 Subject: [PATCH 09/13] more ME balance changes and bug fixes --- .../encounters/trash-to-treasure-encounter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 985984e73e8..83ec3e1e4e9 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -190,7 +190,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) { } scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), null, undefined, true); + await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2x " + leftovers.name }), null, undefined, true); // First Shell bell for (const pokemon of party) { @@ -217,7 +217,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) { } scene.playSound("item_fanfare"); - await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), null, undefined, true); + await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2x " + shellBell.name }), null, undefined, true); } async function doGarbageDig(scene: BattleScene) { From 24bbb0931cf63898617b0ca6898ae1d5487848b8 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Tue, 24 Sep 2024 13:15:18 -0400 Subject: [PATCH 10/13] change return type of isNullOrUndefined --- src/battle-scene.ts | 22 ++++-- src/data/battle-anims.ts | 2 +- .../encounters/bug-type-superfan-encounter.ts | 12 ++-- .../department-store-sale-encounter.ts | 2 +- .../encounters/fiery-fallout-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 36 +++++----- .../teleporting-hijinks-encounter.ts | 4 +- .../the-pokemon-salesman-encounter.ts | 4 +- .../encounters/training-session-encounter.ts | 15 ++--- .../encounters/uncommon-breed-encounter.ts | 67 +++++++------------ .../encounters/weird-dream-encounter.ts | 12 ++-- .../mystery-encounter-option.ts | 2 +- .../mystery-encounter-requirements.ts | 28 ++++---- .../utils/encounter-dialogue-utils.ts | 2 +- .../utils/encounter-phase-utils.ts | 22 +++--- .../utils/encounter-pokemon-utils.ts | 2 +- src/field/mystery-encounter-intro.ts | 2 +- src/field/pokemon.ts | 4 +- src/phases/select-modifier-phase.ts | 4 +- .../mystery-encounter/encounter-test-utils.ts | 2 +- src/test/utils/gameManager.ts | 2 +- src/ui/mystery-encounter-ui-handler.ts | 4 +- src/utils.ts | 2 +- 23 files changed, 122 insertions(+), 132 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 75dacadff78..bbae01bf2f6 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2636,8 +2636,7 @@ export default class BattleScene extends SceneBase { modifier = mt.modifier as PokemonHeldItemModifier; modifier.pokemonId = enemyPokemon.id; } - const stackCount = mt.stackCount ?? 1; - modifier.stackCount = stackCount; + modifier.stackCount = mt.stackCount ?? 1; modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable; this.addEnemyModifier(modifier, true); }); @@ -3076,7 +3075,16 @@ export default class BattleScene extends SceneBase { } } - isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { + /** + * Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}. + * Currently, the only modes that MEs are allowed in are Classic and Challenge. + * Additionally, MEs cannot spawn outside of waves 10-180 in those modes + * + * @param newBattleType + * @param waveIndex + * @param sessionDataEncounterType + */ + private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves(); if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) { // If ME type is already defined in session data, no need to roll RNG check @@ -3120,10 +3128,10 @@ export default class BattleScene extends SceneBase { getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter { // Loading override or session encounter let encounter: MysteryEncounter | null; - if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) { - encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!]; + if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { + encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; } else { - encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType!] : null; + encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null; } // Check for queued encounters first @@ -3166,7 +3174,7 @@ export default class BattleScene extends SceneBase { let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > greatThreshold ? MysteryEncounterTier.GREAT : tierValue > ultraThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE; if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) { - tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE!; + tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE; } let availableEncounters: MysteryEncounter[] = []; diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 7c149ec54d5..62ef8112e6c 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -430,7 +430,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent { scene.field.add(moveAnim.bgSprite); const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon(); if (!isNullOrUndefined(priority)) { - scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!); + scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority); } else if (fieldPokemon?.isOnField()) { scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon); } diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 68840943c49..35858b2e6db 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -492,7 +492,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { if (!isNullOrUndefined(pool3Mon.formIndex)) { - p.formIndex = pool3Mon.formIndex!; + p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); } @@ -515,14 +515,14 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { if (!isNullOrUndefined(pool3Mon.formIndex)) { - p.formIndex = pool3Mon.formIndex!; + p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); } })) .setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => { if (!isNullOrUndefined(pool3Mon2.formIndex)) { - p.formIndex = pool3Mon2.formIndex!; + p.formIndex = pool3Mon2.formIndex; p.generateAndPopulateMoveset(); p.generateName(); } @@ -543,7 +543,7 @@ function getTrainerConfigForWave(waveIndex: number) { .setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { if (!isNullOrUndefined(pool3Mon.formIndex)) { - p.formIndex = pool3Mon.formIndex!; + p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); } @@ -566,14 +566,14 @@ function getTrainerConfigForWave(waveIndex: number) { })) .setPartyMemberFunc(2, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { if (!isNullOrUndefined(pool3Mon.formIndex)) { - p.formIndex = pool3Mon.formIndex!; + p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); } })) .setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { if (!isNullOrUndefined(pool3Mon.formIndex)) { - p.formIndex = pool3Mon.formIndex!; + p.formIndex = pool3Mon.formIndex; p.generateAndPopulateMoveset(); p.generateName(); } diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index db25d338a29..88101752eda 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -63,7 +63,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = // Choose TMs const modifiers: ModifierTypeFunc[] = []; let i = 0; - while (i < 4) { + while (i < 6) { // 2/2/1 weight on TM rarity const roll = randSeedInt(5); if (roll < 2) { diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 4f5430b63d9..13f9d926345 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -189,7 +189,7 @@ export const FieryFalloutEncounter: MysteryEncounter = } // Burn random member - const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.NONE); + const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE); if (burnable?.length > 0) { const roll = randSeedInt(burnable.length); const chosenPokemon = burnable[roll]; diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 4c3472a1c9e..9da54e60612 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -26,11 +26,15 @@ import { trainerNamePools } from "#app/data/trainer-names"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Moves } from "#enums/moves"; -import { speciesEggMoves } from "#app/data/egg-moves"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:globalTradeSystem"; +/** Base shiny chance of 512/65536 -> 1/128 odds, affected by events and Shiny Charms. Cannot exceed 1/16 odds. */ +const WONDER_TRADE_SHINY_CHANCE = 512; +/** Max shiny chance of 4096/65536 -> 1/16 odds. */ +const MAX_WONDER_TRADE_SHINY_CHANCE = 4096; + const LEGENDARY_TRADE_POOLS = { 1: [Species.RATTATA, Species.PIDGEY, Species.WEEDLE], 2: [Species.SENTRET, Species.HOOTHOOT, Species.LEDYBA], @@ -224,16 +228,15 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false); // Extra shiny roll at 1/128 odds (boosted by events and charms) if (!tradePokemon.shiny) { - const baseShinyChance = 512; - const shinyThreshold = new Utils.IntegerHolder(baseShinyChance); + const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE); if (scene.eventManager.isEventActive()) { shinyThreshold.value *= scene.eventManager.getShinyMultiplier(); } scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold); // Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms - // Maximum shiny chance of 4090/65536 -> 1/16, cannot improve further after that - const shinyChance = Math.min(shinyThreshold.value, 4090); + // Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that + const shinyChance = Math.min(shinyThreshold.value, MAX_WONDER_TRADE_SHINY_CHANCE); tradePokemon.trySetShinySeed(shinyChance, false); } @@ -255,17 +258,16 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = // If Pokemon is still not shiny or with HA, give the Pokemon a random Common egg move in its moveset if (!tradePokemon.shiny && (!tradePokemon.species.abilityHidden || tradePokemon.abilityIndex < hiddenIndex)) { - const eggMoves: Moves[] = speciesEggMoves[tradePokemon.getSpeciesForm().getRootSpeciesId()]; - if (eggMoves) { - // Cannot gen the rare egg move, only 1 of the first 3 common moves - const eggMove = eggMoves[randSeedInt(3)]; - if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) { - if (tradePokemon.moveset.length < 4) { - tradePokemon.moveset.push(new PokemonMove(eggMove)); - } else { - const eggMoveIndex = randSeedInt(4); - tradePokemon.moveset[eggMoveIndex] = new PokemonMove(eggMove); - } + const eggMoves: Moves[] = tradePokemon.getEggMoves(); + + // Cannot gen the rare egg move, only 1 of the first 3 common moves + const eggMove = eggMoves[randSeedInt(3)]; + if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) { + if (tradePokemon.moveset.length < 4) { + tradePokemon.moveset.push(new PokemonMove(eggMove)); + } else { + const eggMoveIndex = randSeedInt(4); + tradePokemon.moveset[eggMoveIndex] = new PokemonMove(eggMove); } } } @@ -480,7 +482,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?: if (validSpecies?.length > 20) { validSpecies = randSeedShuffle(validSpecies); newSpecies = validSpecies.pop(); - while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) { + while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) { newSpecies = validSpecies.pop(); } } else { diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index f5d1891a391..abea725f113 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -39,7 +39,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave - .withSceneRequirement(new MoneyRequirement(undefined, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost + .withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost .withAutoHideIntroVisuals(false) .withCatchAllowed(true) .withIntroSpriteConfigs([ @@ -73,7 +73,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withSceneMoneyRequirement(undefined, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost + .withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost .withDialogue({ buttonLabel: `${namespace}.option.1.label`, buttonTooltip: `${namespace}.option.1.tooltip`, diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index edd687b4270..53e27022195 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -34,7 +34,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN) .withEncounterTier(MysteryEncounterTier.ULTRA) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) - .withSceneRequirement(new MoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay + .withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay .withAutoHideIntroVisuals(false) .withIntroSpriteConfigs([ { @@ -114,7 +114,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .withHasDexProgress(true) - .withSceneMoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2 + .withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2 .withDialogue({ buttonLabel: `${namespace}.option.1.label`, buttonTooltip: `${namespace}.option.1.tooltip`, diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index cdf1cef540c..33864c00143 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -324,21 +324,18 @@ export const TrainingSessionEncounter: MysteryEncounter = const abilityIndex = encounter.misc.abilityIndex; if (!!playerPokemon.getFusionSpeciesForm()) { playerPokemon.fusionAbilityIndex = abilityIndex; - if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies!.speciesId)) { - scene.gameData.starterData[playerPokemon.fusionSpecies!.speciesId] + if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies.speciesId)) { + scene.gameData.starterData[playerPokemon.fusionSpecies.speciesId] .abilityAttr |= - abilityIndex !== 1 || playerPokemon.fusionSpecies!.ability2 + abilityIndex !== 1 || playerPokemon.fusionSpecies.ability2 ? Math.pow(2, playerPokemon.fusionAbilityIndex) : AbilityAttr.ABILITY_HIDDEN; } } else { playerPokemon.abilityIndex = abilityIndex; - if ( - speciesStarters.hasOwnProperty(playerPokemon.species.speciesId) - ) { - scene.gameData.starterData[ - playerPokemon.species.speciesId - ].abilityAttr |= + if (speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)) { + scene.gameData.starterData[playerPokemon.species.speciesId] + .abilityAttr |= abilityIndex !== 1 || playerPokemon.species.ability2 ? Math.pow(2, playerPokemon.abilityIndex) : AbilityAttr.ABILITY_HIDDEN; diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 4eade78baa6..c283ff0aa69 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -12,7 +12,6 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { TrainerSlot } from "#app/data/trainer-config"; import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import PokemonData from "#app/system/pokemon-data"; -import { speciesEggMoves } from "#app/data/egg-moves"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; @@ -53,21 +52,18 @@ export const UncommonBreedEncounter: MysteryEncounter = const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2; const species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true); const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true); - const speciesRootForm = pokemon.species.getRootSpeciesId(); // Pokemon will always have one of its egg moves in its moveset - if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { - const eggMoves: Moves[] = speciesEggMoves[speciesRootForm]; - const eggMoveIndex = randSeedInt(4); - const randomEggMove: Moves = eggMoves[eggMoveIndex]; - encounter.misc = { - eggMove: randomEggMove - }; - if (pokemon.moveset.length < 4) { - pokemon.moveset.push(new PokemonMove(randomEggMove)); - } else { - pokemon.moveset[0] = new PokemonMove(randomEggMove); - } + const eggMoves: Moves[] = pokemon.getEggMoves(); + const eggMoveIndex = randSeedInt(4); + const randomEggMove: Moves = eggMoves[eggMoveIndex]; + encounter.misc = { + eggMove: randomEggMove + }; + if (pokemon.moveset.length < 4) { + pokemon.moveset.push(new PokemonMove(randomEggMove)); + } else { + pokemon.moveset[0] = new PokemonMove(randomEggMove); } encounter.misc.pokemon = pokemon; @@ -198,20 +194,7 @@ export const UncommonBreedEncounter: MysteryEncounter = const pokemon = encounter.misc.pokemon; // Give 1 additional egg move - const previousEggMove = encounter.misc.eggMove; - const speciesRootForm = pokemon.species.getRootSpeciesId(); - if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { - const eggMoves: Moves[] = speciesEggMoves[speciesRootForm]; - let randomEggMove: Moves = eggMoves[randSeedInt(4)]; - while (randomEggMove === previousEggMove) { - randomEggMove = eggMoves[randSeedInt(4)]; - } - if (pokemon.moveset.length < 4) { - pokemon.moveset.push(new PokemonMove(randomEggMove)); - } else { - pokemon.moveset[1] = new PokemonMove(randomEggMove); - } - } + givePokemonExtraEggMove(pokemon, encounter.misc.eggMove); await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false); setEncounterRewards(scene, { fillRemaining: true }); @@ -240,20 +223,7 @@ export const UncommonBreedEncounter: MysteryEncounter = const pokemon = encounter.misc.pokemon; // Give 1 additional egg move - const previousEggMove = encounter.misc.eggMove; - const speciesRootForm = pokemon.species.getRootSpeciesId(); - if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { - const eggMoves: Moves[] = speciesEggMoves[speciesRootForm]; - let randomEggMove: Moves = eggMoves[randSeedInt(4)]; - while (randomEggMove === previousEggMove) { - randomEggMove = eggMoves[randSeedInt(4)]; - } - if (pokemon.moveset.length < 4) { - pokemon.moveset.push(new PokemonMove(randomEggMove)); - } else { - pokemon.moveset[1] = new PokemonMove(randomEggMove); - } - } + givePokemonExtraEggMove(pokemon, encounter.misc.eggMove); // Roll IVs a second time pokemon.ivs = pokemon.ivs.map(iv => { @@ -271,3 +241,16 @@ export const UncommonBreedEncounter: MysteryEncounter = .build() ) .build(); + +function givePokemonExtraEggMove(pokemon: EnemyPokemon, previousEggMove: Moves) { + const eggMoves: Moves[] = pokemon.getEggMoves(); + let randomEggMove: Moves = eggMoves[randSeedInt(4)]; + while (randomEggMove === previousEggMove) { + randomEggMove = eggMoves[randSeedInt(4)]; + } + if (pokemon.moveset.length < 4) { + pokemon.moveset.push(new PokemonMove(randomEggMove)); + } else { + pokemon.moveset[1] = new PokemonMove(randomEggMove); + } +} diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 43a3bfd8d3b..33573fbe2d8 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -449,7 +449,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num if (validSpecies?.length > 20) { validSpecies = randSeedShuffle(validSpecies); newSpecies = validSpecies.pop(); - while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) { + while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) { newSpecies = validSpecies.pop(); } } else { @@ -459,7 +459,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num } } - return newSpecies!; + return newSpecies; } function doShowDreamBackground(scene: BattleScene) { @@ -554,16 +554,16 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species): Promise { let eggMoveIndex: null | number = null; if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { - const eggMoves: Moves[] = speciesEggMoves[speciesRootForm].slice(0); + const eggMoves: Moves[] = newPokemon.getEggMoves().slice(0); const eggMoveIndices = [0, 1, 2, 3]; randSeedShuffle(eggMoveIndices); let randomEggMoveIndex = eggMoveIndices.pop(); - let randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex!] : null; + let randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex] : null; let retries = 0; while (retries < 3 && (!randomEggMove || newPokemon.moveset.some(m => m?.moveId === randomEggMove))) { // If Pokemon already knows this move, roll for another egg move randomEggMoveIndex = eggMoveIndices.pop(); - randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex!] : null; + randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex] : null; retries++; } @@ -579,7 +579,7 @@ async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: Pla // For pokemon that the player owns (including ones just caught), unlock the egg move if (!isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { - await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex!, true); + await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); } } } diff --git a/src/data/mystery-encounters/mystery-encounter-option.ts b/src/data/mystery-encounters/mystery-encounter-option.ts index 865877445c1..ffae71b9555 100644 --- a/src/data/mystery-encounters/mystery-encounter-option.ts +++ b/src/data/mystery-encounters/mystery-encounter-option.ts @@ -208,7 +208,7 @@ export class MysteryEncounterOptionBuilder implements Partial= 0 && (this.waveRange?.[0] >= 0 && this.waveRange?.[0] > waveIndex) || (this.waveRange?.[1] >= 0 && this.waveRange?.[1] < waveIndex)) { + if (waveIndex >= 0 && (this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) || (this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)) { return false; } } @@ -251,7 +251,7 @@ export class WeatherRequirement extends EncounterSceneRequirement { const currentWeather = scene.arena.weather?.weatherType; let token = ""; if (!isNullOrUndefined(currentWeather)) { - token = WeatherType[currentWeather!].replace("_", " ").toLocaleLowerCase(); + token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase(); } return ["weather", token]; } @@ -274,9 +274,9 @@ export class PartySizeRequirement extends EncounterSceneRequirement { } override meetsRequirement(scene: BattleScene): boolean { - if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) { + if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) { const partySize = this.excludeDisallowedPokemon ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length; - if (partySize >= 0 && (this.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) { + if (partySize >= 0 && (this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) || (this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)) { return false; } } @@ -326,7 +326,7 @@ export class MoneyRequirement extends EncounterSceneRequirement { requiredMoney: number; // Static value scalingMultiplier: number; // Calculates required money based off wave index - constructor(requiredMoney?: number, scalingMultiplier?: number) { + constructor(requiredMoney: number, scalingMultiplier?: number) { super(); this.requiredMoney = requiredMoney ?? 0; this.scalingMultiplier = scalingMultiplier ?? 0; @@ -418,8 +418,8 @@ export class NatureRequirement extends EncounterPokemonRequirement { } override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon!.nature)) { - return ["nature", Nature[pokemon!.nature]]; + if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) { + return ["nature", Nature[pokemon.nature]]; } return ["nature", ""]; } @@ -620,7 +620,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { return this.requiredStatusEffect.some((statusEffect) => { if (statusEffect === StatusEffect.NONE) { // StatusEffect.NONE also checks for null or undefined status - return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect; + return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect; } else { return pokemon.status?.effect === statusEffect; } @@ -628,12 +628,11 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { }); } else { // for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects - // return partyPokemon.filter((pokemon) => this.requiredStatusEffect.filter((statusEffect) => pokemon.status?.effect === statusEffect).length === 0); return partyPokemon.filter((pokemon) => { return !this.requiredStatusEffect.some((statusEffect) => { if (statusEffect === StatusEffect.NONE) { // StatusEffect.NONE also checks for null or undefined status - return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect; + return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect; } else { return pokemon.status?.effect === statusEffect; } @@ -645,7 +644,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { const reqStatus = this.requiredStatusEffect.filter((a) => { if (a === StatusEffect.NONE) { - return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon!.status!.effect) || pokemon!.status!.effect === a; + return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a; } return pokemon!.status?.effect === a; }); @@ -988,8 +987,9 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement { } override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - if (!isNullOrUndefined(pokemon?.getHpRatio())) { - return ["healthRatio", Math.floor(pokemon!.getHpRatio() * 100).toString() + "%"]; + const hpRatio = pokemon?.getHpRatio(); + if (!isNullOrUndefined(hpRatio)) { + return ["healthRatio", Math.floor(hpRatio * 100).toString() + "%"]; } return ["healthRatio", ""]; } diff --git a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts index 494a45f69f5..c4d5e47cb05 100644 --- a/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-dialogue-utils.ts @@ -17,7 +17,7 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima return null; } - let textString: string | null = getTextWithDialogueTokens(scene, keyOrString!); + let textString: string | null = getTextWithDialogueTokens(scene, keyOrString); // Can only color the text if a Primary Style is defined // primaryStyle is applied to all text that does not have its own specified style diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index ef4f396b823..ea04241e663 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -135,7 +135,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: scene.currentBattle.trainer.destroy(); } - trainerConfig = partyConfig?.trainerConfig ? partyConfig?.trainerConfig : trainerConfigs[trainerType!]; + trainerConfig = partyTrainerConfig ? partyTrainerConfig : trainerConfigs[trainerType!]; const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle); doubleBattle = doubleTrainer; @@ -166,7 +166,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // This can be amplified or counteracted by setting levelAdditiveModifier in config // levelAdditiveModifier value of 0.5 will halve the modifier scaling, 2 will double it, etc. // Leaving null/undefined will disable level scaling - const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier! : 0; + const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier : 0; const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0); battle.enemyLevels = battle.enemyLevels.map(level => level + additive); @@ -226,7 +226,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // Set form if (!isNullOrUndefined(config.nickname)) { - enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname!))); + enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname))); } // Generate new id, reset status and HP in case using data source @@ -236,29 +236,29 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // Set form if (!isNullOrUndefined(config.formIndex)) { - enemyPokemon.formIndex = config.formIndex!; + enemyPokemon.formIndex = config.formIndex; } // Set shiny if (!isNullOrUndefined(config.shiny)) { - enemyPokemon.shiny = config.shiny!; + enemyPokemon.shiny = config.shiny; } // Set Variant if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) { - enemyPokemon.variant = config.variant!; + enemyPokemon.variant = config.variant; } // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) { - enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData!; + enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData; } // Set Boss if (config.isBoss) { let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true); if (!isNullOrUndefined(config.bossSegmentModifier)) { - segments += config.bossSegmentModifier!; + segments += config.bossSegmentModifier; } enemyPokemon.setBoss(true, segments); } @@ -294,18 +294,18 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // Set ability if (!isNullOrUndefined(config.abilityIndex)) { - enemyPokemon.abilityIndex = config.abilityIndex!; + enemyPokemon.abilityIndex = config.abilityIndex; } // Set gender if (!isNullOrUndefined(config.gender)) { enemyPokemon.gender = config.gender!; - enemyPokemon.summonData.gender = config.gender!; + enemyPokemon.summonData.gender = config.gender; } // Set AI type if (!isNullOrUndefined(config.aiType)) { - enemyPokemon.aiType = config.aiType!; + enemyPokemon.aiType = config.aiType; } // Set moves diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 7277cca0600..d7e596af879 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -218,7 +218,7 @@ export function getRandomSpeciesByStarterTier(starterTiers: number | [number, nu .map(s => [getPokemonSpecies(s[0]), s[1]]); if (types && types.length > 0) { - filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2!))); + filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2))); } // If no filtered mons exist at specified starter tiers, will expand starter search range until there are diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 7c58a494699..70588da5d44 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -86,7 +86,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con }; if (!isNullOrUndefined(result.species)) { - const keys = getSpriteKeysFromSpecies(result.species!); + const keys = getSpriteKeysFromSpecies(result.species); result.spriteKey = keys.spriteKey; result.fileRoot = keys.fileRoot; result.isPokemon = true; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 7734f8adec2..bf2c123fa71 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1258,8 +1258,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } if (this.isFusion()) { - if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData!.ability !== -1) { - return allAbilities[this.fusionMysteryEncounterPokemonData!.ability]; + if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) { + return allAbilities[this.fusionMysteryEncounterPokemonData.ability]; } else { return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 58fb13ac466..ba55340bd8d 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -252,13 +252,13 @@ export class SelectModifierPhase extends BattlePhase { let multiplier = 1; if (!isNullOrUndefined(this.customModifierSettings?.rerollMultiplier)) { - if (this.customModifierSettings!.rerollMultiplier! < 0) { + if (this.customModifierSettings.rerollMultiplier < 0) { // Completely overrides reroll cost to -1 and early exits return -1; } // Otherwise, continue with custom multiplier - multiplier = this.customModifierSettings!.rerollMultiplier!; + multiplier = this.customModifierSettings.rerollMultiplier; } return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); } diff --git a/src/test/mystery-encounter/encounter-test-utils.ts b/src/test/mystery-encounter/encounter-test-utils.ts index a31ee150bfd..9fb2504c02b 100644 --- a/src/test/mystery-encounter/encounter-test-utils.ts +++ b/src/test/mystery-encounter/encounter-test-utils.ts @@ -114,7 +114,7 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN } if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) { - await handleSecondaryOptionSelect(game, secondaryOptionSelect!.pokemonNo, secondaryOptionSelect!.optionNo); + await handleSecondaryOptionSelect(game, secondaryOptionSelect.pokemonNo, secondaryOptionSelect.optionNo); } else { uiHandler.processInput(Button.ACTION); } diff --git a/src/test/utils/gameManager.ts b/src/test/utils/gameManager.ts index 36423c5e18f..f48fe3ef228 100644 --- a/src/test/utils/gameManager.ts +++ b/src/test/utils/gameManager.ts @@ -195,7 +195,7 @@ export default class GameManager { async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) { if (!isNullOrUndefined(encounterType)) { this.override.disableTrainerWaves(); - this.override.mysteryEncounter(encounterType!); + this.override.mysteryEncounter(encounterType); } await this.runToTitle(); diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 08de740e3ec..edff5c25cda 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -95,8 +95,8 @@ export default class MysteryEncounterUiHandler extends UiHandler { super.show(args); this.overrideSettings = args[0] as OptionSelectSettings ?? {}; - const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) ? true : !this.overrideSettings?.hideDescription; - const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) ? true : this.overrideSettings?.slideInDescription; + const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) ? true : !this.overrideSettings.hideDescription; + const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) ? true : this.overrideSettings.slideInDescription; const startingCursorIndex = this.overrideSettings?.startingCursorIndex ?? 0; this.cursorContainer.setVisible(true); diff --git a/src/utils.ts b/src/utils.ts index e526d086316..b029067c8d6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -584,7 +584,7 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole * Returns if an object is null or undefined * @param object */ -export function isNullOrUndefined(object: any): boolean { +export function isNullOrUndefined(object: any): object is undefined | null { return null === object || undefined === object; } From a1d1de2c0dae60ba25292685642833a815fe9e4c Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Tue, 24 Sep 2024 13:41:00 -0400 Subject: [PATCH 11/13] more ME dialogue cleanup --- .../an-offer-you-cant-refuse-encounter.ts | 13 +++++++++---- .../encounters/department-store-sale-encounter.ts | 2 +- .../encounters/safari-zone-encounter.ts | 8 +++++++- .../dancing-lessons-dialogue.json | 2 +- .../en/mystery-encounters/safari-zone-dialogue.json | 2 +- .../the-expert-pokemon-breeder-dialogue.json | 2 +- .../the-strong-stuff-dialogue.json | 2 +- .../the-winstrate-challenge-dialogue.json | 4 ++-- .../department-store-sale-encounter.test.ts | 2 +- 9 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index 013feb6e5a4..919cd1df5ca 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -17,6 +17,14 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounter:offerYouCantRefuse"; +/** + * Money offered starts at base value of Relic Gold, increasing linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased + * Starter value 1-3 -> Relic Gold + * Starter value 10 -> 3 * Relic Gold + */ +const MONEY_MINIMUM_MULTIPLIER = 10; +const MONEY_MAXIMUM_MULTIPLIER = 30; + /** * An Offer You Can't Refuse encounter. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808} @@ -62,12 +70,9 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true); - // Base value of Relic Gold, increased linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased - // Starter value 1-3 -> 10x - // Starter value 10 -> 30x const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(true); const starterValue: number = speciesStarters[baseSpecies] ?? 1; - const multiplier = Math.max(3 * starterValue, 10); + const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER); const price = scene.getWaveMoneyAmount(multiplier); encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender()); diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 88101752eda..52890f0ffed 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -63,7 +63,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = // Choose TMs const modifiers: ModifierTypeFunc[] = []; let i = 0; - while (i < 6) { + while (i < 5) { // 2/2/1 weight on TM rarity const roll = randSeedInt(5); if (roll < 2) { diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 97aedc4f826..8b8fc5f73be 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -27,6 +27,8 @@ const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768]; const SAFARI_MONEY_MULTIPLIER = 2; +const NUM_SAFARI_ENCOUNTERS = 3; + /** * Safari Zone encounter. * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800} @@ -55,6 +57,10 @@ export const SafariZoneEncounter: MysteryEncounter = .withTitle(`${namespace}.title`) .withDescription(`${namespace}.description`) .withQuery(`${namespace}.query`) + .withOnInit((scene: BattleScene) => { + scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString()); + return true; + }) .withOption(MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive @@ -72,7 +78,7 @@ export const SafariZoneEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; encounter.continuousEncounter = true; encounter.misc = { - safariPokemonRemaining: 3 + safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS }; updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Load bait/mud assets diff --git a/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json b/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json index 065c4e0c06c..20b01708be0 100644 --- a/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json +++ b/src/locales/en/mystery-encounters/dancing-lessons-dialogue.json @@ -1,7 +1,7 @@ { "intro": "An {{oricorioName}} dances sadly alone, without a partner.", "title": "Dancing Lessons", - "description": "The {{oricorioName}} doesn't seem aggressive, if anything it seems sad.\n\nMaybe it just wants someone to dance with...", + "description": "The {{oricorioName}} doesn't seem aggressive, if anything it seems despondent.\n\nPerhaps it just wants someone to dance with...", "query": "What will you do?", "option": { "1": { diff --git a/src/locales/en/mystery-encounters/safari-zone-dialogue.json b/src/locales/en/mystery-encounters/safari-zone-dialogue.json index 8869f2055e5..b96e3b5beb8 100644 --- a/src/locales/en/mystery-encounters/safari-zone-dialogue.json +++ b/src/locales/en/mystery-encounters/safari-zone-dialogue.json @@ -1,7 +1,7 @@ { "intro": "It's a safari zone!", "title": "The Safari Zone", - "description": "There are all kinds of rare and special Pokémon that can be found here!\nIf you choose to enter, you'll have a time limit of 3 wild encounters where you can try to catch these special Pokémon.\n\nBeware, though. These Pokémon may flee before you're able to catch them!", + "description": "There are all kinds of rare and special Pokémon that can be found here!\nIf you choose to enter, you'll have a time limit of @[TOOLTIP_TITLE]{{{numEncounters}} wild encounters} where you can try to catch these special Pokémon.\n\nBeware, though. These Pokémon may flee before you're able to catch them!", "query": "Would you like to enter?", "option": { "1": { diff --git a/src/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json b/src/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json index 3c74c7b2726..5f1eece528b 100644 --- a/src/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json +++ b/src/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json @@ -25,7 +25,7 @@ "outro": "Look how happy your {{chosenPokemon}} is now!$Here, you can have these as well.", "outro_failed": "How disappointing...$It looks like you still have a long way\nto go to earn your Pokémon's trust!", "gained_eggs": "@s{item_fanfare}You received {{numEggs}}!", - "eggs_tooltip": "\n(+) Earn {{eggs}}", + "eggs_tooltip": "\n(+) Earn @[TOOLTIP_TITLE]{{{eggs}}}", "numEggs_one": "{{count}} {{rarity}} Egg", "numEggs_other": "{{count}} {{rarity}} Eggs" } \ No newline at end of file diff --git a/src/locales/en/mystery-encounters/the-strong-stuff-dialogue.json b/src/locales/en/mystery-encounters/the-strong-stuff-dialogue.json index b5403616c9b..ec557eb8cea 100644 --- a/src/locales/en/mystery-encounters/the-strong-stuff-dialogue.json +++ b/src/locales/en/mystery-encounters/the-strong-stuff-dialogue.json @@ -1,7 +1,7 @@ { "intro": "It's a massive {{shuckleName}} and what appears\nto be a large stash of... juice?", "title": "The Strong Stuff", - "description": "The {{shuckleName}} that blocks your path looks incredibly strong. Meanwhile, the juice next to it is emanating power of some kind.\n\nThe {{shuckleName}} extends its feelers in your direction. It seems like it wants to do something...", + "description": "The {{shuckleName}} that blocks your path looks formidable. Meanwhile, the juice next to it emanates power of some kind.\n\nThe {{shuckleName}} extends its feelers in your direction. It seems like it wants to do something...", "query": "What will you do?", "option": { "1": { diff --git a/src/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json b/src/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json index 37807a91667..e2963b98d61 100644 --- a/src/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json +++ b/src/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json @@ -3,12 +3,12 @@ "speaker": "The Winstrates", "intro_dialogue": "We're the Winstrates!$What do you say to taking on our family in a series of Pokémon battles?", "title": "The Winstrate Challenge", - "description": "The Winstrates are a family of 5 trainers, and they want to battle! If you beat all of them back-to-back, they'll give you a grand prize. But can you handle the heat?", + "description": "The Winstrates are a family of @[TOOLTIP_TITLE]{5 trainers}, and they want to battle! If you beat all of them back-to-back, they'll give you a grand prize. But can you handle the heat?", "query": "What will you do?", "option": { "1": { "label": "Accept the Challenge", - "tooltip": "(-) Brutal Battle\n(+) Special Item Reward", + "tooltip": "(-) Brutal Battle Against 5 Trainers\n(+) Special Item Reward", "selected": "Let the challenge begin!" }, "2": { diff --git a/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index 0b2d66db20b..f91e1a5a13c 100644 --- a/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -98,7 +98,7 @@ describe("Department Store Sale - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(4); + expect(modifierSelectHandler.options.length).toEqual(5); for (const option of modifierSelectHandler.options) { expect(option.modifierTypeOption.type.id).toContain("TM_"); } From 219215f54c9e2e52a36e67685d1aa2b5f2a40587 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Tue, 24 Sep 2024 13:51:02 -0400 Subject: [PATCH 12/13] fix getEggMoves() helper function --- .../encounters/global-trade-system-encounter.ts | 3 +++ src/field/pokemon.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 9da54e60612..99be66b820f 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -260,6 +260,9 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = if (!tradePokemon.shiny && (!tradePokemon.species.abilityHidden || tradePokemon.abilityIndex < hiddenIndex)) { const eggMoves: Moves[] = tradePokemon.getEggMoves(); + if (eggMoves) { + + } // Cannot gen the rare egg move, only 1 of the first 3 common moves const eggMove = eggMoves[randSeedInt(3)]; if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index bf2c123fa71..f1183fa579e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1800,7 +1800,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns list of egg moves */ getEggMoves() : Moves[] { - return speciesEggMoves[this.species.speciesId]; + return speciesEggMoves[this.getSpeciesForm().getRootSpeciesId(true)]; } setMove(moveIndex: integer, moveId: Moves): void { From d52f691d43501f3ea8ca5a20807e2db23d02251a Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Tue, 24 Sep 2024 14:06:59 -0400 Subject: [PATCH 13/13] account for undefined egg moves --- .../global-trade-system-encounter.ts | 23 +++++----- .../encounters/uncommon-breed-encounter.ts | 42 ++++++++++--------- .../encounters/weird-dream-encounter.ts | 6 +-- src/field/pokemon.ts | 2 +- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index 99be66b820f..f3765c9acba 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -25,7 +25,6 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter import { trainerNamePools } from "#app/data/trainer-names"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; -import { Moves } from "#enums/moves"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:globalTradeSystem"; @@ -258,19 +257,17 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = // If Pokemon is still not shiny or with HA, give the Pokemon a random Common egg move in its moveset if (!tradePokemon.shiny && (!tradePokemon.species.abilityHidden || tradePokemon.abilityIndex < hiddenIndex)) { - const eggMoves: Moves[] = tradePokemon.getEggMoves(); - + const eggMoves = tradePokemon.getEggMoves(); if (eggMoves) { - - } - // Cannot gen the rare egg move, only 1 of the first 3 common moves - const eggMove = eggMoves[randSeedInt(3)]; - if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) { - if (tradePokemon.moveset.length < 4) { - tradePokemon.moveset.push(new PokemonMove(eggMove)); - } else { - const eggMoveIndex = randSeedInt(4); - tradePokemon.moveset[eggMoveIndex] = new PokemonMove(eggMove); + // Cannot gen the rare egg move, only 1 of the first 3 common moves + const eggMove = eggMoves[randSeedInt(3)]; + if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) { + if (tradePokemon.moveset.length < 4) { + tradePokemon.moveset.push(new PokemonMove(eggMove)); + } else { + const eggMoveIndex = randSeedInt(4); + tradePokemon.moveset[eggMoveIndex] = new PokemonMove(eggMove); + } } } } diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index c283ff0aa69..24298a633df 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -54,16 +54,18 @@ export const UncommonBreedEncounter: MysteryEncounter = const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true); // Pokemon will always have one of its egg moves in its moveset - const eggMoves: Moves[] = pokemon.getEggMoves(); - const eggMoveIndex = randSeedInt(4); - const randomEggMove: Moves = eggMoves[eggMoveIndex]; - encounter.misc = { - eggMove: randomEggMove - }; - if (pokemon.moveset.length < 4) { - pokemon.moveset.push(new PokemonMove(randomEggMove)); - } else { - pokemon.moveset[0] = new PokemonMove(randomEggMove); + const eggMoves = pokemon.getEggMoves(); + if (eggMoves) { + const eggMoveIndex = randSeedInt(4); + const randomEggMove: Moves = eggMoves[eggMoveIndex]; + encounter.misc = { + eggMove: randomEggMove + }; + if (pokemon.moveset.length < 4) { + pokemon.moveset.push(new PokemonMove(randomEggMove)); + } else { + pokemon.moveset[0] = new PokemonMove(randomEggMove); + } } encounter.misc.pokemon = pokemon; @@ -243,14 +245,16 @@ export const UncommonBreedEncounter: MysteryEncounter = .build(); function givePokemonExtraEggMove(pokemon: EnemyPokemon, previousEggMove: Moves) { - const eggMoves: Moves[] = pokemon.getEggMoves(); - let randomEggMove: Moves = eggMoves[randSeedInt(4)]; - while (randomEggMove === previousEggMove) { - randomEggMove = eggMoves[randSeedInt(4)]; - } - if (pokemon.moveset.length < 4) { - pokemon.moveset.push(new PokemonMove(randomEggMove)); - } else { - pokemon.moveset[1] = new PokemonMove(randomEggMove); + const eggMoves = pokemon.getEggMoves(); + if (eggMoves) { + let randomEggMove: Moves = eggMoves[randSeedInt(4)]; + while (randomEggMove === previousEggMove) { + randomEggMove = eggMoves[randSeedInt(4)]; + } + if (pokemon.moveset.length < 4) { + pokemon.moveset.push(new PokemonMove(randomEggMove)); + } else { + pokemon.moveset[1] = new PokemonMove(randomEggMove); + } } } diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 33573fbe2d8..8fa60774a72 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -12,7 +12,6 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from " import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; -import { speciesEggMoves } from "#app/data/egg-moves"; import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; @@ -22,7 +21,6 @@ import { getLevelTotalExp } from "#app/data/exp"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { Challenges } from "#enums/challenges"; -import { Moves } from "#enums/moves"; /** i18n namespace for encounter */ const namespace = "mysteryEncounter:weirdDream"; @@ -553,8 +551,8 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo */ async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species): Promise { let eggMoveIndex: null | number = null; - if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { - const eggMoves: Moves[] = newPokemon.getEggMoves().slice(0); + const eggMoves = newPokemon.getEggMoves()?.slice(0); + if (eggMoves) { const eggMoveIndices = [0, 1, 2, 3]; randSeedShuffle(eggMoveIndices); let randomEggMoveIndex = eggMoveIndices.pop(); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f1183fa579e..14f93809414 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1799,7 +1799,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @returns list of egg moves */ - getEggMoves() : Moves[] { + getEggMoves() : Moves[] | undefined { return speciesEggMoves[this.getSpeciesForm().getRootSpeciesId(true)]; }