From 32741835fd1c71829f09bb231d35f66d41b15426 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer Date: Sun, 22 Sep 2024 13:53:18 -0400 Subject: [PATCH] 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