diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 3e9176430a5..c0a3f0b090a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1071,13 +1071,14 @@ export default class BattleScene extends SceneBase { this.field.add(newTrainer); } - // Check for mystery encounter - // Can only occur in place of a standard wild battle, waves 10-180 + // TODO: remove this once spawn rates are finalized // let testStartingWeight = 0; - // while (testStartingWeight < 20) { + // while (testStartingWeight < 3) { // calculateMEAggregateStats(this, testStartingWeight); // testStartingWeight += 1; // } + // Check for mystery encounter + // Can only occur in place of a standard wild battle, waves 10-180 if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < 180 && newWaveIndex > 10) { const roll = Utils.randSeedInt(256); @@ -2651,7 +2652,7 @@ export default class BattleScene extends SceneBase { } // Common / Uncommon / Rare / Super Rare - const tierWeights = [61, 40, 21, 6]; + const tierWeights = [64, 40, 21, 3]; // Adjust tier weights by previously encountered events to lower odds of only common/uncommons in run this.mysteryEncounterData.encounteredEvents.forEach(val => { @@ -2681,10 +2682,23 @@ export default class BattleScene extends SceneBase { // If no valid encounters exist at tier, checks next tier down, continuing until there are some encounters available while (availableEncounters.length === 0 && tier >= 0) { availableEncounters = biomeMysteryEncounters - .filter((encounterType) => - allMysteryEncounters[encounterType]?.meetsRequirements(this) && - allMysteryEncounters[encounterType].encounterTier === tier && - (isNullOrUndefined(previousEncounter) || encounterType !== previousEncounter)) + .filter((encounterType) => { + if (allMysteryEncounters[encounterType].encounterTier !== tier) { // Encounter is in tier + return false; + } + if (!allMysteryEncounters[encounterType]?.meetsRequirements(this)) { // Meets encounter requirements + return false; + } + if (!isNullOrUndefined(previousEncounter) && encounterType === previousEncounter) { // Previous encounter was not this one + return false; + } + if (this.mysteryEncounterData.encounteredEvents?.length > 0 && // Encounter has not exceeded max allowed encounters + allMysteryEncounters[encounterType].maxAllowedEncounters > 0 + && this.mysteryEncounterData.encounteredEvents.filter(e => e[0] === encounterType).length >= allMysteryEncounters[encounterType].maxAllowedEncounters) { + return false; + } + return true; + }) .map((m) => (allMysteryEncounters[m])); tier--; } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 45ff955bc33..c0c8b1e5aaf 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -50,6 +50,7 @@ export default interface IMysteryEncounter { hideBattleIntroMessage?: boolean; hideIntroVisuals?: boolean; catchAllowed?: boolean; + maxAllowedEncounters?: number; doEncounterExp?: (scene: BattleScene) => boolean; doEncounterRewards?: (scene: BattleScene) => boolean; onInit?: (scene: BattleScene) => boolean; @@ -144,6 +145,8 @@ export default class IMysteryEncounter implements IMysteryEncounter { } this.encounterTier = this.encounterTier ? this.encounterTier : MysteryEncounterTier.COMMON; this.dialogue = this.dialogue ?? {}; + // Default max is 1 for ROGUE encounters, 3 for others + this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? 1 : 3; this.encounterVariant = MysteryEncounterVariant.DEFAULT; this.requirements = this.requirements ? this.requirements : []; this.hideBattleIntroMessage = !isNullOrUndefined(this.hideBattleIntroMessage) ? this.hideBattleIntroMessage : false; @@ -435,9 +438,9 @@ export class MysteryEncounterBuilder implements Partial { * If not specified, defaults to COMMON * Tiers are: * COMMON 32/64 odds - * UNCOMMON 16/64 odds - * RARE 10/64 odds - * SUPER_RARE 6/64 odds + * GREAT 16/64 odds + * ULTRA 10/64 odds + * ROGUE 6/64 odds * ULTRA_RARE Not currently used * @param encounterTier * @returns @@ -446,6 +449,15 @@ export class MysteryEncounterBuilder implements Partial { return Object.assign(this, { encounterTier: encounterTier }); } + /** + * Sets the maximum number of times that an encounter can spawn in a given Classic run + * @param maxAllowedEncounters + * @returns + */ + withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required> { + return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters }); + } + /** * Specifies a requirement for an encounter * For example, passing requirement as "new WaveCountRequirement([2, 180])" would create a requirement that the encounter can only be spawned between waves 2 and 180 diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 100c25aa45d..c9c3e980456 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -632,7 +632,7 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n // Calculate encounter rarity // Common / Uncommon / Rare / Super Rare (base is out of 128) - const tierWeights = [61, 40, 21, 6]; + const tierWeights = [64, 40, 21, 3]; // Adjust tier weights by currently encountered events (pity system that lowers odds of multiple common/uncommons) tierWeights[0] = tierWeights[0] - 6 * numEncounters[0];