From 16df3fd806881f83f3ef8e69e810fe5870416129 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Fri, 21 Mar 2025 23:06:31 -0400 Subject: [PATCH 01/18] Metronome challenge --- src/data/challenge.ts | 99 ++++++++++++++++++++++++++++++++++- src/enums/challenges.ts | 1 + src/field/pokemon.ts | 4 +- src/modifier/modifier-type.ts | 70 ++++++++++++++++++++++--- src/phases/evolution-phase.ts | 26 +++++---- src/phases/level-up-phase.ts | 13 +++-- src/phases/move-phase.ts | 8 ++- 7 files changed, 194 insertions(+), 27 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index b9d817836c3..5e8c430eeb5 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -16,7 +16,7 @@ import { Challenges } from "#enums/challenges"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; -import type { Moves } from "#enums/moves"; +import { Moves } from "#enums/moves"; import { TypeColor, TypeShadow } from "#enums/color"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; @@ -93,6 +93,18 @@ export enum ChallengeType { * Modifies what the pokemon stats for Flip Stat Mode. */ FLIP_STAT, + /** + * Modifies movesets of generated enemy mons + */ + MOVESET_MODIFY, + /** + * Prevents the learning of moves + */ + NO_MOVE_LEARNING, + /** + * Negates PP Usage + */ + NO_PP_USE, } /** @@ -433,6 +445,18 @@ export abstract class Challenge { applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) { return false; } + + applyMovesetModify(_pokemon: Pokemon) { + return false; + } + + applyNoMoveLearning(_valid: Utils.BooleanHolder) { + return false; + } + + applyNoPPUsage(_valid: Utils.BooleanHolder) { + return false; + } } type ChallengeCondition = (data: GameData) => boolean; @@ -917,6 +941,14 @@ export class InverseBattleChallenge extends Challenge { return 0; } + override applyMovesetModify(pokemon: Pokemon): boolean { + if (pokemon.species.speciesId === Species.ETERNATUS) { + pokemon.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); + return true; + } + return false; + } + applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean { if (effectiveness.value < 1) { effectiveness.value = 2; @@ -958,6 +990,41 @@ export class FlipStatChallenge extends Challenge { } } +export class MetronomeChallenge extends Challenge { + constructor() { + super(Challenges.METRONOME, 1); + } + + override applyStarterModify(_pokemon: Pokemon): boolean { + return this.applyMovesetModify(_pokemon); + } + + override applyMovesetModify(pokemon: Pokemon): boolean { + pokemon.setMove(0, Moves.METRONOME); + pokemon.setMove(1, Moves.NONE); + pokemon.setMove(2, Moves.NONE); + pokemon.setMove(3, Moves.NONE); + return true; + } + + override applyNoMoveLearning(valid: Utils.BooleanHolder): boolean { + valid.value = true; + return true; + } + + override applyNoPPUsage(valid: Utils.BooleanHolder): boolean { + valid.value = true; + return true; + } + + static loadChallenge(source: MetronomeChallenge | any): MetronomeChallenge { + const newChallenge = new MetronomeChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + /** * Lowers the amount of starter points available. */ @@ -1216,6 +1283,24 @@ export function applyChallenges( baseStats: number[], ): boolean; +export function applyChallenges( + gameMode: GameMode, + challengeType: ChallengeType.MOVESET_MODIFY, + pokemon: Pokemon, +): boolean; + +export function applyChallenges( + gameMode: GameMode, + challengeType: ChallengeType.NO_MOVE_LEARNING, + valid: Utils.BooleanHolder, +): boolean; + +export function applyChallenges( + gameMode: GameMode, + challengeType: ChallengeType.NO_PP_USE, + valid: Utils.BooleanHolder, +): boolean; + export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { let ret = false; gameMode.challenges.forEach(c => { @@ -1263,6 +1348,15 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType case ChallengeType.FLIP_STAT: ret ||= c.applyFlipStat(args[0], args[1]); break; + case ChallengeType.MOVESET_MODIFY: + ret ||= c.applyMovesetModify(args[0]); + break; + case ChallengeType.NO_MOVE_LEARNING: + ret ||= c.applyNoMoveLearning(args[0]); + break; + case ChallengeType.NO_PP_USE: + ret ||= c.applyNoPPUsage(args[0]); + break; } } }); @@ -1290,6 +1384,8 @@ export function copyChallenge(source: Challenge | any): Challenge { return InverseBattleChallenge.loadChallenge(source); case Challenges.FLIP_STAT: return FlipStatChallenge.loadChallenge(source); + case Challenges.METRONOME: + return MetronomeChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } @@ -1303,5 +1399,6 @@ export function initChallenges() { new FreshStartChallenge(), new InverseBattleChallenge(), new FlipStatChallenge(), + new MetronomeChallenge(), ); } diff --git a/src/enums/challenges.ts b/src/enums/challenges.ts index 7b506a61a2f..2ab52a53e2a 100644 --- a/src/enums/challenges.ts +++ b/src/enums/challenges.ts @@ -6,4 +6,5 @@ export enum Challenges { FRESH_START, INVERSE_BATTLE, FLIP_STAT, + METRONOME, } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 890c6bab0d6..ff7840de05c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -7024,14 +7024,12 @@ export class EnemyPokemon extends Pokemon { new PokemonMove(Moves.FLAMETHROWER), new PokemonMove(Moves.COSMIC_POWER), ]; - if (globalScene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { - this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); - } break; default: super.generateAndPopulateMoveset(); break; } + applyChallenges(globalScene.gameMode, ChallengeType.MOVESET_MODIFY, this); } /** diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 3d2e67b0dc3..6e914565a3f 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -106,6 +106,7 @@ import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party import PartyUiHandler from "#app/ui/party-ui-handler"; import { getModifierTierTextTint } from "#app/ui/text"; import { + BooleanHolder, formatMoney, getEnumKeys, getEnumValues, @@ -126,6 +127,7 @@ import type { PermanentStat, TempBattleStat } from "#enums/stat"; import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -2028,6 +2030,8 @@ export const modifierTypes = { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { return new BerryModifierType(pregenArgs[0] as BerryType); } + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb const berryTypes = getEnumValues(BerryType); let randBerryType: BerryType; const rand = randSeedInt(12); @@ -2035,7 +2039,7 @@ export const modifierTypes = { randBerryType = BerryType.SITRUS; } else if (rand < 4) { randBerryType = BerryType.LUM; - } else if (rand < 6) { + } else if (rand < 6 && !noMoveLearning.value) { randBerryType = BerryType.LEPPA; } else { randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; @@ -2464,14 +2468,30 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), new WeightedModifierType(modifierTypes.BERRY, 2), - new WeightedModifierType(modifierTypes.TM_COMMON, 2), + new WeightedModifierType( + modifierTypes.TM_COMMON, + () => { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); + return noMoveLearning.value ? 0 : 2; + }, + 2, + ), ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), [ModifierTier.GREAT]: [ new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), - new WeightedModifierType(modifierTypes.PP_UP, 2), + new WeightedModifierType( + modifierTypes.PP_UP, + () => { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); + return noMoveLearning.value ? 0 : 2; + }, + 2, + ), new WeightedModifierType( modifierTypes.FULL_HEAL, (party: Pokemon[]) => { @@ -2567,6 +2587,11 @@ const modifierPool: ModifierPool = { new WeightedModifierType( modifierTypes.ELIXIR, (party: Pokemon[]) => { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); + if (noMoveLearning.value) { + return 0; + } const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -2586,6 +2611,11 @@ const modifierPool: ModifierPool = { new WeightedModifierType( modifierTypes.MAX_ELIXIR, (party: Pokemon[]) => { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); + if (noMoveLearning.value) { + return 0; + } const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -2618,11 +2648,21 @@ const modifierPool: ModifierPool = { 2, ), new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), - new WeightedModifierType(modifierTypes.TM_GREAT, 3), + new WeightedModifierType( + modifierTypes.TM_GREAT, + () => { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); + return noMoveLearning.value ? 0 : 3; + }, + 3, + ), new WeightedModifierType( modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => { - if (!party.find(p => p.getLearnableLevelMoves().length)) { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb + if (noMoveLearning.value || !party.find(p => p.getLearnableLevelMoves().length)) { return 0; } const highestPartyLevel = party @@ -2668,7 +2708,15 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), - new WeightedModifierType(modifierTypes.PP_MAX, 3), + new WeightedModifierType( + modifierTypes.PP_MAX, + () => { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); // Yeah this is kind of dumb + return noMoveLearning.value ? 0 : 3; + }, + 3, + ), new WeightedModifierType(modifierTypes.MINT, 4), new WeightedModifierType( modifierTypes.RARE_EVOLUTION_ITEM, @@ -2813,7 +2861,15 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), - new WeightedModifierType(modifierTypes.TM_ULTRA, 11), + new WeightedModifierType( + modifierTypes.TM_ULTRA, + () => { + const noMoveLearning = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb + return noMoveLearning.value ? 0 : 11; + }, + 11, + ), new WeightedModifierType(modifierTypes.RARER_CANDY, 4), new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index bb283fa8139..ffaec96c453 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -17,6 +17,8 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; +import { BooleanHolder } from "#app/utils"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; @@ -343,16 +345,20 @@ export class EvolutionPhase extends Phase { this.evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { - const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved - ? LearnMoveSituation.EVOLUTION_FUSED - : this.pokemon.fusionSpecies - ? LearnMoveSituation.EVOLUTION_FUSED_BASE - : LearnMoveSituation.EVOLUTION; - const levelMoves = this.pokemon - .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) - .filter(lm => lm[0] === EVOLVE_MOVE); - for (const lm of levelMoves) { - globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); + const skipMoveLearn = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, skipMoveLearn); + if (!skipMoveLearn.value) { + const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved + ? LearnMoveSituation.EVOLUTION_FUSED + : this.pokemon.fusionSpecies + ? LearnMoveSituation.EVOLUTION_FUSED_BASE + : LearnMoveSituation.EVOLUTION; + const levelMoves = this.pokemon + .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) + .filter(lm => lm[0] === EVOLVE_MOVE); + for (const lm of levelMoves) { + globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); + } } globalScene.unshiftPhase(new EndEvolutionPhase()); diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index 31c7fabf451..5346afdcc79 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -6,8 +6,9 @@ import { EvolutionPhase } from "#app/phases/evolution-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { LevelAchv } from "#app/system/achv"; -import { NumberHolder } from "#app/utils"; +import { BooleanHolder, NumberHolder } from "#app/utils"; import i18next from "i18next"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { protected lastLevel: number; @@ -63,9 +64,13 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { public override end() { if (this.lastLevel < 100) { // this feels like an unnecessary optimization - const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); - for (const lm of levelMoves) { - globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); + const skipMoveLearn = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, skipMoveLearn); + if (!skipMoveLearn.value) { + const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); + for (const lm of levelMoves) { + globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); + } } } if (!this.pokemon.pauseEvolutions) { diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index f8edaa56981..17e3514820b 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -43,13 +43,14 @@ import { MoveChargePhase } from "#app/phases/move-charge-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import { NumberHolder } from "#app/utils"; +import { BooleanHolder, NumberHolder } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; export class MovePhase extends BattlePhase { protected _pokemon: Pokemon; @@ -104,11 +105,14 @@ export class MovePhase extends BattlePhase { ) { super(); + const ignorePPChallenge = new BooleanHolder(ignorePp); + applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, ignorePPChallenge); + this.pokemon = pokemon; this.targets = targets; this.move = move; this.followUp = followUp; - this.ignorePp = ignorePp; + this.ignorePp = ignorePPChallenge.value; this.reflected = reflected; this.forcedLast = forcedLast; } From 632b44df2fd4df4041b4bd3ee2e806ad28310270 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Fri, 21 Mar 2025 23:19:19 -0400 Subject: [PATCH 02/18] Fix starting moveset --- src/data/challenge.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 5e8c430eeb5..985d9a2891e 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1000,10 +1000,7 @@ export class MetronomeChallenge extends Challenge { } override applyMovesetModify(pokemon: Pokemon): boolean { - pokemon.setMove(0, Moves.METRONOME); - pokemon.setMove(1, Moves.NONE); - pokemon.setMove(2, Moves.NONE); - pokemon.setMove(3, Moves.NONE); + pokemon.moveset = [new PokemonMove(Moves.METRONOME)]; return true; } From 8ba733d0c94efc0ad3ef7c5208fd7db08219083f Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Sun, 23 Mar 2025 12:22:01 -0400 Subject: [PATCH 03/18] Disable other challenge achievements with Metronome challenge --- src/system/achv.ts | 69 +++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/system/achv.ts b/src/system/achv.ts index bd8595b2f94..10b0888aa7e 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -541,7 +541,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 1 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_TWO_VICTORY: new ChallengeAchv( @@ -554,7 +554,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 2 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_THREE_VICTORY: new ChallengeAchv( @@ -567,7 +567,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 3 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_FOUR_VICTORY: new ChallengeAchv( @@ -580,7 +580,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 4 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_FIVE_VICTORY: new ChallengeAchv( @@ -593,7 +593,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 5 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_SIX_VICTORY: new ChallengeAchv( @@ -606,7 +606,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 6 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_SEVEN_VICTORY: new ChallengeAchv( @@ -619,7 +619,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 7 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_EIGHT_VICTORY: new ChallengeAchv( @@ -632,7 +632,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 8 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GEN_NINE_VICTORY: new ChallengeAchv( @@ -645,7 +645,7 @@ export const achvs = { c instanceof SingleGenerationChallenge && c.value === 9 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_NORMAL: new ChallengeAchv( @@ -658,7 +658,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 1 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_FIGHTING: new ChallengeAchv( @@ -671,7 +671,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 2 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_FLYING: new ChallengeAchv( @@ -684,7 +684,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 3 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_POISON: new ChallengeAchv( @@ -697,7 +697,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 4 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GROUND: new ChallengeAchv( @@ -710,7 +710,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 5 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_ROCK: new ChallengeAchv( @@ -723,7 +723,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 6 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_BUG: new ChallengeAchv( @@ -736,7 +736,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 7 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GHOST: new ChallengeAchv( @@ -749,7 +749,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 8 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_STEEL: new ChallengeAchv( @@ -762,7 +762,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 9 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_FIRE: new ChallengeAchv( @@ -775,7 +775,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 10 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_WATER: new ChallengeAchv( @@ -788,7 +788,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 11 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_GRASS: new ChallengeAchv( @@ -801,7 +801,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 12 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_ELECTRIC: new ChallengeAchv( @@ -814,7 +814,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 13 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_PSYCHIC: new ChallengeAchv( @@ -827,7 +827,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 14 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_ICE: new ChallengeAchv( @@ -840,7 +840,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 15 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_DRAGON: new ChallengeAchv( @@ -853,7 +853,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 16 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_DARK: new ChallengeAchv( @@ -866,7 +866,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 17 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), MONO_FAIRY: new ChallengeAchv( @@ -879,7 +879,7 @@ export const achvs = { c instanceof SingleTypeChallenge && c.value === 18 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), FRESH_START: new ChallengeAchv( @@ -892,7 +892,7 @@ export const achvs = { c instanceof FreshStartChallenge && c.value > 0 && !globalScene.gameMode.challenges.some( - c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT].includes(c.id) && c.value > 0, + c => [Challenges.INVERSE_BATTLE, Challenges.FLIP_STAT, Challenges.METRONOME].includes(c.id) && c.value > 0, ), ), INVERSE_BATTLE: new ChallengeAchv( @@ -901,7 +901,10 @@ export const achvs = { "INVERSE_BATTLE.description", "inverse", 100, - c => c instanceof InverseBattleChallenge && c.value > 0, + c => + c instanceof InverseBattleChallenge && + c.value > 0 && + !globalScene.gameMode.challenges.some(c => c.id === Challenges.METRONOME && c.value > 0), ), FLIP_STATS: new ChallengeAchv( "FLIP_STATS", @@ -909,7 +912,10 @@ export const achvs = { "FLIP_STATS.description", "dubious_disc", 100, - c => c instanceof FlipStatChallenge && c.value > 0, + c => + c instanceof FlipStatChallenge && + c.value > 0 && + !globalScene.gameMode.challenges.some(c => c.id === Challenges.METRONOME && c.value > 0), ), FLIP_INVERSE: new ChallengeAchv( "FLIP_INVERSE", @@ -920,7 +926,8 @@ export const achvs = { c => c instanceof FlipStatChallenge && c.value > 0 && - globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0), + globalScene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0) && + !globalScene.gameMode.challenges.some(c => c.id === Challenges.METRONOME && c.value > 0), ).setSecret(), BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 50).setSecret(), }; From 8d29c1fb31693c8dd830e6c60c262b5ef47c8446 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Mon, 24 Mar 2025 14:21:59 -0400 Subject: [PATCH 04/18] Challenge types for shop removals and reward table changes --- src/data/challenge.ts | 64 ++++++++++++++++++ src/modifier/modifier-type.ts | 122 ++++++++++++++++------------------ 2 files changed, 121 insertions(+), 65 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 985d9a2891e..7e307ba96c9 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -21,6 +21,7 @@ import { TypeColor, TypeShadow } from "#enums/color"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { ModifierTier } from "#app/modifier/modifier-tier"; +import type { ModifierTypeKeys, RewardTableModification } from "#app/modifier/modifier-type"; /** A constant for the default max cost of the starting party before a run */ const DEFAULT_PARTY_MAX_COST = 10; @@ -105,6 +106,14 @@ export enum ChallengeType { * Negates PP Usage */ NO_PP_USE, + /** + * Modifies reward table + */ + REWARD_TABLE_MODIFY, + /** + * Removes items from the shop + */ + SHOP_REMOVAL, } /** @@ -457,6 +466,14 @@ export abstract class Challenge { applyNoPPUsage(_valid: Utils.BooleanHolder) { return false; } + + applyRewardTableModify(_modifications: RewardTableModification[]) { + return false; + } + + applyShopRemovals(_removals: ModifierTypeKeys[]) { + return false; + } } type ChallengeCondition = (data: GameData) => boolean; @@ -910,6 +927,14 @@ export class FreshStartChallenge extends Challenge { return true; } + override applyRewardTableModify(modifications: RewardTableModification[]): boolean { + modifications.push( + { type: "EVIOLITE", tier: ModifierTier.ULTRA, maxWeight: 0 }, // No Eviolite + { type: "MINI_BLACK_HOLE", tier: ModifierTier.MASTER, maxWeight: 0 }, // No MBH + ); + return true; + } + override getDifficulty(): number { return 0; } @@ -1014,6 +1039,27 @@ export class MetronomeChallenge extends Challenge { return true; } + override applyRewardTableModify(modifications: RewardTableModification[]): boolean { + modifications.push( + { type: "TM_COMMON", tier: ModifierTier.COMMON, maxWeight: 0 }, // Remove TMs + { type: "ETHER", tier: ModifierTier.COMMON, maxWeight: 0 }, // Remove PP Restores + { type: "MAX_ETHER", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores + { type: "ELIXIR", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores + { type: "MAX_ELIXIR", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores + { type: "PP_UP", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Upgrades + { type: "MEMORY_MUSHROOM", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove Mushrooms + { type: "TM_GREAT", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove TMs + { type: "TM_ULTRA", tier: ModifierTier.ULTRA, maxWeight: 0 }, // Remove TMs + { type: "PP_MAX", tier: ModifierTier.ULTRA, maxWeight: 0 }, // Remove PP Upgrades + ); + return true; + } + + override applyShopRemovals(removals: ModifierTypeKeys[]): boolean { + removals.push("ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "MEMORY_MUSHROOM"); + return true; + } + static loadChallenge(source: MetronomeChallenge | any): MetronomeChallenge { const newChallenge = new MetronomeChallenge(); newChallenge.value = source.value; @@ -1298,6 +1344,18 @@ export function applyChallenges( valid: Utils.BooleanHolder, ): boolean; +export function applyChallenges( + gameMode: GameMode, + challengeType: ChallengeType.REWARD_TABLE_MODIFY, + modifications: RewardTableModification[], +): boolean; + +export function applyChallenges( + gameMode: GameMode, + challengeType: ChallengeType.SHOP_REMOVAL, + removals: ModifierTypeKeys[], +): boolean; + export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { let ret = false; gameMode.challenges.forEach(c => { @@ -1354,6 +1412,12 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType case ChallengeType.NO_PP_USE: ret ||= c.applyNoPPUsage(args[0]); break; + case ChallengeType.REWARD_TABLE_MODIFY: + ret ||= c.applyRewardTableModify(args[0]); + break; + case ChallengeType.SHOP_REMOVAL: + ret ||= c.applyShopRemovals(args[0]); + break; } } }); diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 6e914565a3f..d172f27b7a9 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2468,30 +2468,14 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), new WeightedModifierType(modifierTypes.BERRY, 2), - new WeightedModifierType( - modifierTypes.TM_COMMON, - () => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); - return noMoveLearning.value ? 0 : 2; - }, - 2, - ), + new WeightedModifierType(modifierTypes.TM_COMMON, 2), ].map(m => { m.setTier(ModifierTier.COMMON); return m; }), [ModifierTier.GREAT]: [ new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), - new WeightedModifierType( - modifierTypes.PP_UP, - () => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); - return noMoveLearning.value ? 0 : 2; - }, - 2, - ), + new WeightedModifierType(modifierTypes.PP_UP, 2), new WeightedModifierType( modifierTypes.FULL_HEAL, (party: Pokemon[]) => { @@ -2587,11 +2571,6 @@ const modifierPool: ModifierPool = { new WeightedModifierType( modifierTypes.ELIXIR, (party: Pokemon[]) => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); - if (noMoveLearning.value) { - return 0; - } const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -2611,11 +2590,6 @@ const modifierPool: ModifierPool = { new WeightedModifierType( modifierTypes.MAX_ELIXIR, (party: Pokemon[]) => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); - if (noMoveLearning.value) { - return 0; - } const thresholdPartyMemberCount = Math.min( party.filter( p => @@ -2648,21 +2622,11 @@ const modifierPool: ModifierPool = { 2, ), new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2), - new WeightedModifierType( - modifierTypes.TM_GREAT, - () => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); - return noMoveLearning.value ? 0 : 3; - }, - 3, - ), + new WeightedModifierType(modifierTypes.TM_GREAT, 3), new WeightedModifierType( modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb - if (noMoveLearning.value || !party.find(p => p.getLearnableLevelMoves().length)) { + if (!party.find(p => p.getLearnableLevelMoves().length)) { return 0; } const highestPartyLevel = party @@ -2708,15 +2672,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15), new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), - new WeightedModifierType( - modifierTypes.PP_MAX, - () => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning); // Yeah this is kind of dumb - return noMoveLearning.value ? 0 : 3; - }, - 3, - ), + new WeightedModifierType(modifierTypes.PP_MAX, 3), new WeightedModifierType(modifierTypes.MINT, 4), new WeightedModifierType( modifierTypes.RARE_EVOLUTION_ITEM, @@ -2731,7 +2687,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.AMULET_COIN, skipInLastClassicWaveOrDefault(3)), new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => { const { gameMode, gameData } = globalScene; - if (gameMode.isDaily || (!gameMode.isFreshStartChallenge() && gameData.isUnlocked(Unlockables.EVIOLITE))) { + if (gameMode.isDaily || gameData.isUnlocked(Unlockables.EVIOLITE)) { return party.some(p => { // Check if Pokemon's species (or fusion species, if applicable) can evolve or if they're G-Max'd if ( @@ -2861,15 +2817,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), - new WeightedModifierType( - modifierTypes.TM_ULTRA, - () => { - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb - return noMoveLearning.value ? 0 : 11; - }, - 11, - ), + new WeightedModifierType(modifierTypes.TM_ULTRA, 11), new WeightedModifierType(modifierTypes.RARER_CANDY, 4), new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), @@ -2955,11 +2903,7 @@ const modifierPool: ModifierPool = { ), new WeightedModifierType( modifierTypes.MINI_BLACK_HOLE, - () => - globalScene.gameMode.isDaily || - (!globalScene.gameMode.isFreshStartChallenge() && globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE)) - ? 1 - : 0, + () => (globalScene.gameMode.isDaily || globalScene.gameData.isUnlocked(Unlockables.MINI_BLACK_HOLE) ? 1 : 0), 1, ), ].map(m => { @@ -3170,8 +3114,51 @@ const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024]; */ export const itemPoolChecks: Map = new Map(); +export interface RewardTableModification { + type: ModifierTypeKeys; + tier: ModifierTier; + maxWeight: number; +} + export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount = 0) { + const modifications: RewardTableModification[] = []; + applyChallenges(globalScene.gameMode, ChallengeType.REWARD_TABLE_MODIFY, modifications); const pool = getModifierPoolForType(poolType); + modifications.map(mod => { + let t = mod.tier; + let dindex = pool[mod.tier].findIndex(wm => wm.modifierType.id === mod.type); + if (mod.maxWeight === 0) { + // Remove the modifier from the specified tier + pool[t].splice(dindex, 1); + } else if (dindex < 0) { + // Add the modifier to specified tier + for (t = ModifierTier.COMMON; t <= ModifierTier.MASTER && dindex < 0; t++) { + if (t === mod.tier) { + // We know it's not in that tier + continue; + } + dindex = pool[t].findIndex(wm => wm.modifierType.id === mod.type); + } + if (dindex >= 0) { + // Move the existing WMT to the specified tier with same func and specified max weight + const wmt = pool[t].splice(dindex, 1)[0]; + wmt.maxWeight = mod.maxWeight; + wmt.setTier(mod.tier); + pool[mod.tier].push(wmt); + } else { + // Item isn't anywhere on the table, make a new WMT and push it + const newWMT = new WeightedModifierType(getModifierTypeFuncById(mod.type), mod.maxWeight); + newWMT.setTier(mod.tier); + pool[mod.tier].push(newWMT); + } + } else { + pool[t].map(wmt => { + if (wmt.modifierType.id === mod.type) { + wmt.maxWeight = mod.maxWeight; + } + }); + } + }); itemPoolChecks.forEach((_v, k) => { itemPoolChecks.set(k, false); }); @@ -3459,7 +3446,12 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC [new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25)], [new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10)], ]; - return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); + const removeShop: ModifierTypeKeys[] = []; + applyChallenges(globalScene.gameMode, ChallengeType.SHOP_REMOVAL, removeShop); + return options + .slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)) + .flat() + .filter(s => !removeShop.includes(s.type.id as ModifierTypeKeys)); } export function getEnemyBuffModifierForWave( From 126904aeab4b92cb720a335c32708f1bb6186323 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Tue, 25 Mar 2025 14:58:12 -0400 Subject: [PATCH 05/18] Fix shop --- src/modifier/modifier-type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index d172f27b7a9..6281e263b89 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -3451,7 +3451,7 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC return options .slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)) .flat() - .filter(s => !removeShop.includes(s.type.id as ModifierTypeKeys)); + .filter(s => !removeShop.includes(s.type.localeKey.split(".")[1] as ModifierTypeKeys)); // I don't wanna hear it } export function getEnemyBuffModifierForWave( From 88ec4c9aa37028b5b5cdfebc3d01df3752453cfc Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Wed, 26 Mar 2025 18:40:57 -0400 Subject: [PATCH 06/18] Fix PP Usage --- src/phases/move-phase.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 17e3514820b..cc19aef1155 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -105,14 +105,11 @@ export class MovePhase extends BattlePhase { ) { super(); - const ignorePPChallenge = new BooleanHolder(ignorePp); - applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, ignorePPChallenge); - this.pokemon = pokemon; this.targets = targets; this.move = move; this.followUp = followUp; - this.ignorePp = ignorePPChallenge.value; + this.ignorePp = ignorePp; this.reflected = reflected; this.forcedLast = forcedLast; } @@ -353,7 +350,13 @@ export class MovePhase extends BattlePhase { // "commit" to using the move, deducting PP. if (!this.ignorePp) { - const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); + let ppUsed = 1 + this.getPpIncreaseFromPressure(targets); + + const ignorePPChallenge = new BooleanHolder(false); + applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, ignorePPChallenge); + if (ignorePPChallenge.value) { + ppUsed = 0; + } this.move.usePp(ppUsed); globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); From 558b6120d3e5a7f6cc289299feaa9f24fe059494 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Wed, 26 Mar 2025 21:25:25 -0400 Subject: [PATCH 07/18] Fix many thing make better very good --- src/battle-scene.ts | 3 + src/data/challenge.ts | 322 ++++++++++++++++------------ src/data/moves/move.ts | 6 +- src/field/pokemon.ts | 8 +- src/game-mode.ts | 4 +- src/modifier/modifier-type.ts | 66 +----- src/phases/evolution-phase.ts | 24 +-- src/phases/learn-move-phase.ts | 5 + src/phases/level-up-phase.ts | 10 +- src/phases/move-phase.ts | 2 +- src/phases/select-starter-phase.ts | 2 +- src/system/game-data.ts | 2 +- src/ui/party-ui-handler.ts | 2 +- src/ui/starter-select-ui-handler.ts | 2 +- 14 files changed, 231 insertions(+), 227 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a6c986d3d0f..8ae4f0d136a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -170,6 +170,7 @@ import { StatusEffect } from "#enums/status-effect"; import { initGlobalScene } from "#app/global-scene"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { HideAbilityPhase } from "#app/phases/hide-ability-phase"; +import { applyChallenges, ChallengeType } from "./data/challenge"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -1082,6 +1083,8 @@ export default class BattleScene extends SceneBase { postProcess(pokemon); } + applyChallenges(ChallengeType.ENEMY_POKEMON_MODIFY, pokemon); + for (let i = 0; i < pokemon.ivs.length; i++) { if (OPP_IVS_OVERRIDE_VALIDATED[i] > -1) { pokemon.ivs[i] = OPP_IVS_OVERRIDE_VALIDATED[i]; diff --git a/src/data/challenge.ts b/src/data/challenge.ts index f817c311f57..198a4d95c19 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -6,11 +6,10 @@ import type PokemonSpecies from "#app/data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { speciesStarterCosts } from "#app/data/balance/starters"; import type Pokemon from "#app/field/pokemon"; -import { PokemonMove } from "#app/field/pokemon"; +import { type EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type { FixedBattleConfig } from "#app/battle"; import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle"; import Trainer, { TrainerVariant } from "#app/field/trainer"; -import type { GameMode } from "#app/game-mode"; import { PokemonType } from "#enums/pokemon-type"; import { Challenges } from "#enums/challenges"; import { Species } from "#enums/species"; @@ -22,7 +21,14 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { globalScene } from "#app/global-scene"; import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonEvolutions } from "./balance/pokemon-evolutions"; -import type { ModifierTypeKeys, RewardTableModification } from "#app/modifier/modifier-type"; +import { + getModifierType, + getModifierTypeFuncById, + ModifierPoolType, + type ModifierPool, + type ModifierTypeKeys, + type ModifierTypeOption, +} from "#app/modifier/modifier-type"; /** A constant for the default max cost of the starting party before a run */ const DEFAULT_PARTY_MAX_COST = 10; @@ -96,25 +102,25 @@ export enum ChallengeType { */ FLIP_STAT, /** - * Modifies movesets of generated enemy mons + * Modifies enemy mons AFTER post process function */ - MOVESET_MODIFY, + ENEMY_POKEMON_MODIFY, /** * Prevents the learning of moves */ - NO_MOVE_LEARNING, + BAN_MOVE_LEARNING, /** * Negates PP Usage */ - NO_PP_USE, + MODIFY_PP_USE, /** - * Modifies reward table + * Modifies modifier pools of specified type (PLEASE MODIFIER REWORK PLEASE I'M BEGGING YOU PLEASE PLEASE PLEASE PLEASE) */ - REWARD_TABLE_MODIFY, + MODIFIER_POOL_MODIFY, /** - * Removes items from the shop + * Modifies the shop options */ - SHOP_REMOVAL, + SHOP_MODIFY, } /** @@ -404,10 +410,9 @@ export abstract class Challenge { /** * An apply function for GAME_MODE_MODIFY challenges. Derived classes should alter this. - * @param gameMode {@link GameMode} The current game mode. * @returns {@link boolean} Whether this function did anything. */ - applyGameModeModify(_gameMode: GameMode): boolean { + applyGameModeModify(): boolean { return false; } @@ -450,23 +455,52 @@ export abstract class Challenge { return false; } - applyMovesetModify(_pokemon: Pokemon) { + /** + * An apply function for ENEMY_POKEMON_MODIFY. Derived classes should alter this. + * @param _pokemon {@link EnemyPokemon} the mon to be modified + * @returns {@link boolean} Whether this function did anything. + */ + applyEnemyPokemonModify(_pokemon: EnemyPokemon) { return false; } - applyNoMoveLearning(_valid: Utils.BooleanHolder) { + /** + * An apply function for BAN_MOVE_LEARNING. Derived classes should alter this. + * @param _pokemon {@link Pokemon} Pokemon who wants to learn the move + * @param _move {@link Moves} Move being learned + * @returns {@link boolean} Whether the move should be restricted from learning + */ + applyBanMoveLearning(_pokemon: Pokemon, _move: Moves) { return false; } - applyNoPPUsage(_valid: Utils.BooleanHolder) { + /** + * An apply function for MODIFY_PP_USE. Derived classes should alter this. + * @param _pokemon {@link Pokemon} Pokemon using the move + * @param _move {@link Moves} Move being used + * @param _usedPP {@link Utils.NumberHolder} Holds the value associated with how much PP should be used + * @returns {@link boolean} Whether this function did anything. + */ + applyModifyPPUsage(_pokemon: Pokemon, _move: Moves, _usedPP: Utils.NumberHolder) { return false; } - applyRewardTableModify(_modifications: RewardTableModification[]) { + /** + * An apply function for MODIFIER_POOL_MODIFY. Derived classes should alter this. + * @param _poolType {@link ModifierPoolType} What kind of item pool + * @param _modifierPool {@link ModifierPool} Pool to modify + * @returns {@link boolean} Whether this function did anything. + */ + applyModifierPoolModify(_poolType: ModifierPoolType, _modifierPool: ModifierPool) { return false; } - applyShopRemovals(_removals: ModifierTypeKeys[]) { + /** + * An apply function for SHOP_MODIFY. Derived classes should alter this. + * @param _options {@link ModifierTypeOption} Array of shop options + * @returns {@link boolean} Whether this function did anything. + */ + applyShopModify(_options: ModifierTypeOption[]) { return false; } } @@ -876,11 +910,22 @@ export class FreshStartChallenge extends Challenge { return true; } - override applyRewardTableModify(modifications: RewardTableModification[]): boolean { - modifications.push( - { type: "EVIOLITE", tier: ModifierTier.ULTRA, maxWeight: 0 }, // No Eviolite - { type: "MINI_BLACK_HOLE", tier: ModifierTier.MASTER, maxWeight: 0 }, // No MBH + override applyModifierPoolModify(poolType: ModifierPoolType, modifierPool: ModifierPool): boolean { + if (poolType !== ModifierPoolType.PLAYER) { + return false; + } + let idx = modifierPool[ModifierTier.ULTRA].findIndex( + p => p.modifierType === getModifierType(getModifierTypeFuncById("EVIOLITE")), ); + if (idx >= 0) { + modifierPool[ModifierTier.ULTRA].splice(idx, 1); + } + idx = modifierPool[ModifierTier.MASTER].findIndex( + p => p.modifierType === getModifierType(getModifierTypeFuncById("MINI_BLACK_HOLE")), + ); + if (idx >= 0) { + modifierPool[ModifierTier.MASTER].splice(idx, 1); + } return true; } @@ -915,7 +960,7 @@ export class InverseBattleChallenge extends Challenge { return 0; } - override applyMovesetModify(pokemon: Pokemon): boolean { + override applyEnemyPokemonModify(pokemon: EnemyPokemon): boolean { if (pokemon.species.speciesId === Species.ETERNATUS) { pokemon.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); return true; @@ -969,46 +1014,78 @@ export class MetronomeChallenge extends Challenge { super(Challenges.METRONOME, 1); } - override applyStarterModify(_pokemon: Pokemon): boolean { - return this.applyMovesetModify(_pokemon); - } - - override applyMovesetModify(pokemon: Pokemon): boolean { + override applyStarterModify(pokemon: Pokemon): boolean { pokemon.moveset = [new PokemonMove(Moves.METRONOME)]; return true; } - override applyNoMoveLearning(valid: Utils.BooleanHolder): boolean { - valid.value = true; + override applyEnemyPokemonModify(pokemon: EnemyPokemon): boolean { + pokemon.moveset = [new PokemonMove(Moves.METRONOME)]; return true; } - override applyNoPPUsage(valid: Utils.BooleanHolder): boolean { - valid.value = true; + override applyBanMoveLearning(_pokemon: Pokemon, _move: Moves): boolean { return true; } - override applyRewardTableModify(modifications: RewardTableModification[]): boolean { - modifications.push( - { type: "TM_COMMON", tier: ModifierTier.COMMON, maxWeight: 0 }, // Remove TMs - { type: "ETHER", tier: ModifierTier.COMMON, maxWeight: 0 }, // Remove PP Restores - { type: "MAX_ETHER", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores - { type: "ELIXIR", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores - { type: "MAX_ELIXIR", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Restores - { type: "PP_UP", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove PP Upgrades - { type: "MEMORY_MUSHROOM", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove Mushrooms - { type: "TM_GREAT", tier: ModifierTier.GREAT, maxWeight: 0 }, // Remove TMs - { type: "TM_ULTRA", tier: ModifierTier.ULTRA, maxWeight: 0 }, // Remove TMs - { type: "PP_MAX", tier: ModifierTier.ULTRA, maxWeight: 0 }, // Remove PP Upgrades - ); + override applyModifyPPUsage(_pokemon: Pokemon, _move: Moves, usedPP: Utils.NumberHolder): boolean { + usedPP.value = 0; return true; } - override applyShopRemovals(removals: ModifierTypeKeys[]): boolean { - removals.push("ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "MEMORY_MUSHROOM"); + override applyModifierPoolModify(poolType: ModifierPoolType, modifierPool: ModifierPool): boolean { + if (poolType !== ModifierPoolType.PLAYER) { + return false; + } + const common_block = ["TM_COMMON", "ETHER"]; + const great_block = ["ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "PP_UP", "MEMORY_MUSHROOM", "TM_GREAT"]; + const ultra_block = ["TM_ULTRA", "PP_MAX"]; + + common_block.map(b => { + const idx = modifierPool[ModifierTier.COMMON].findIndex( + p => p.modifierType === getModifierType(getModifierTypeFuncById(b)), + ); + if (idx >= 0) { + modifierPool[ModifierTier.COMMON].splice(idx, 1); + } else { + console.log(`${b} not found in Common tier!`); + } + }); + great_block.map(b => { + const idx = modifierPool[ModifierTier.GREAT].findIndex( + p => p.modifierType === getModifierType(getModifierTypeFuncById(b)), + ); + if (idx >= 0) { + modifierPool[ModifierTier.GREAT].splice(idx, 1); + } else { + console.log(`${b} not found in Great tier!`); + } + }); + ultra_block.map(b => { + const idx = modifierPool[ModifierTier.ULTRA].findIndex( + p => p.modifierType === getModifierType(getModifierTypeFuncById(b)), + ); + if (idx >= 0) { + modifierPool[ModifierTier.ULTRA].splice(idx, 1); + } else { + console.log(`${b} not found in Ultra tier!`); + } + }); return true; } + override applyShopModify(options: ModifierTypeOption[]): boolean { + const removals = ["ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "MEMORY_MUSHROOM"]; + const opstart = options.length; + removals.map(r => { + const idx = options.findIndex(o => o.type === getModifierType(getModifierTypeFuncById(r))); + if (idx >= 0) { + options.splice(idx, 1); + } + }); + return opstart > options.length; + } + static loadChallenge(source: MetronomeChallenge | any): MetronomeChallenge { const newChallenge = new MetronomeChallenge(); newChallenge.value = source.value; @@ -1084,7 +1161,6 @@ export class LowerStarterPointsChallenge extends Challenge { /** * Apply all challenges that modify starter choice. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.STARTER_CHOICE * @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. @@ -1092,7 +1168,6 @@ export class LowerStarterPointsChallenge extends Challenge { * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, @@ -1100,85 +1175,66 @@ export function applyChallenges( ): boolean; /** * Apply all challenges that modify available total starter points. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.STARTER_POINTS * @param points {@link Utils.NumberHolder} The amount of points you have available. * @returns True if any challenge was successfully applied. */ -export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.STARTER_POINTS, - points: Utils.NumberHolder, -): boolean; +export function applyChallenges(challengeType: ChallengeType.STARTER_POINTS, points: Utils.NumberHolder): boolean; /** * Apply all challenges that modify the cost of a starter. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.STARTER_COST * @param species {@link Species} The pokemon to change the cost of. * @param points {@link Utils.NumberHolder} The cost of the pokemon. * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.STARTER_COST, species: Species, cost: Utils.NumberHolder, ): boolean; /** * Apply all challenges that modify a starter after selection. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.STARTER_MODIFY * @param pokemon {@link Pokemon} The starter pokemon to modify. * @returns True if any challenge was successfully applied. */ -export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.STARTER_MODIFY, - pokemon: Pokemon, -): boolean; +export function applyChallenges(challengeType: ChallengeType.STARTER_MODIFY, pokemon: Pokemon): boolean; /** * Apply all challenges that what pokemon you can have in battle. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.POKEMON_IN_BATTLE * @param pokemon {@link Pokemon} The pokemon to check the validity of. * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.POKEMON_IN_BATTLE, pokemon: Pokemon, valid: Utils.BooleanHolder, ): boolean; /** * Apply all challenges that modify what fixed battles there are. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.FIXED_BATTLES * @param waveIndex {@link Number} The current wave index. * @param battleConfig {@link FixedBattleConfig} The battle config to modify. * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: number, battleConfig: FixedBattleConfig, ): boolean; /** * Apply all challenges that modify type effectiveness. - * @param gameMode {@linkcode GameMode} The current gameMode * @param challengeType {@linkcode ChallengeType} ChallengeType.TYPE_EFFECTIVENESS * @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: Utils.NumberHolder, ): boolean; /** * Apply all challenges that modify what level AI are. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.AI_LEVEL * @param level {@link Utils.NumberHolder} The generated level of the pokemon. * @param levelCap {@link Number} The maximum level cap for the current wave. @@ -1187,7 +1243,6 @@ export function applyChallenges( * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.AI_LEVEL, level: Utils.NumberHolder, levelCap: number, @@ -1196,42 +1251,36 @@ export function applyChallenges( ): boolean; /** * Apply all challenges that modify how many move slots the AI has. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.AI_MOVE_SLOTS * @param pokemon {@link Pokemon} The pokemon being considered. * @param moveSlots {@link Utils.NumberHolder} The amount of move slots. * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.AI_MOVE_SLOTS, pokemon: Pokemon, moveSlots: Utils.NumberHolder, ): boolean; /** * Apply all challenges that modify whether a pokemon has its passive. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.PASSIVE_ACCESS * @param pokemon {@link Pokemon} The pokemon to modify. * @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive. * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.PASSIVE_ACCESS, pokemon: Pokemon, hasPassive: Utils.BooleanHolder, ): boolean; /** * Apply all challenges that modify the game modes settings. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.GAME_MODE_MODIFY * @returns True if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.GAME_MODE_MODIFY): boolean; +export function applyChallenges(challengeType: ChallengeType.GAME_MODE_MODIFY): boolean; /** * Apply all challenges that modify what level a pokemon can access a move. - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.MOVE_ACCESS * @param pokemon {@link Pokemon} What pokemon would learn the move. * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. @@ -1240,7 +1289,6 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.MOVE_ACCESS, pokemon: Pokemon, moveSource: MoveSourceType, @@ -1249,7 +1297,6 @@ export function applyChallenges( ): boolean; /** * Apply all challenges that modify what weight a pokemon gives to move generation - * @param gameMode {@link GameMode} The current gameMode * @param challengeType {@link ChallengeType} ChallengeType.MOVE_WEIGHT * @param pokemon {@link Pokemon} What pokemon would learn the move. * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. @@ -1258,7 +1305,6 @@ export function applyChallenges( * @returns True if any challenge was successfully applied. */ export function applyChallenges( - gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, @@ -1266,46 +1312,64 @@ export function applyChallenges( weight: Utils.NumberHolder, ): boolean; +export function applyChallenges(challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; + +/** + * Apply all challenges that modify Enemy Pokemon generation (after post process funcs) + * @param challengeType {@link ChallengeType} ChallengeType.ENEMY_POKEMON_MODIFY + * @param pokemon {@link EnemyPokemon} Pokemon to be modified + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.ENEMY_POKEMON_MODIFY, pokemon: EnemyPokemon): boolean; + +/** + * Apply all challenges that restrict Pokemon from learning certain moves + * @param challengeType {@link ChallengeType} ChallengeType.BAN_MOVE_LEARNING + * @param pokemon {@link Pokemon} The mon attempting to learn + * @param move {@link Moves} The move being learned + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.BAN_MOVE_LEARNING, pokemon: Pokemon, move: Moves): boolean; + +/** + * Apply all challenges that modify how much PP is used + * @param challengeType {@link ChallengeType} ChallengeType.MODIFY_PP_USE + * @param pokemon {@link Pokemon} Pokemon using the move + * @param move {@link Moves} Move being used + * @param usedPP {@link Utils.NumberHolder} Holds the value associated with how much PP should be used + * @returns True if any challenge was successfully applied. + */ export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.FLIP_STAT, + challengeType: ChallengeType.MODIFY_PP_USE, pokemon: Pokemon, - baseStats: number[], + move: Moves, + usedPP: Utils.NumberHolder, ): boolean; +/** + * Apply all challenges that modify the horrific abomination that is the modifier pools + * @param challengeType {@link ChallengeType} ChallengeType.MODIFIER_POOL_MODIFY + * @param poolType {@link ModifierPoolType} Which kind of pool is being changed (wild held items, player rewards etc) + * @param modifierPool {@link ModifierPool} The item pool the challenge may attempt to modify + * @returns True if any challenge was successfully applied. + */ export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.MOVESET_MODIFY, - pokemon: Pokemon, + challengeType: ChallengeType.MODIFIER_POOL_MODIFY, + poolType: ModifierPoolType, + modifierPool: ModifierPool, ): boolean; -export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.NO_MOVE_LEARNING, - valid: Utils.BooleanHolder, -): boolean; +/** + * Apply all challenges that modify the shop + * @param challengeType ChallengeType.SHOP_MODIFY + * @param options {@link ModifierTypeOption} List of shop options including the prices + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.SHOP_MODIFY, options: ModifierTypeOption[]): boolean; -export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.NO_PP_USE, - valid: Utils.BooleanHolder, -): boolean; - -export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.REWARD_TABLE_MODIFY, - modifications: RewardTableModification[], -): boolean; - -export function applyChallenges( - gameMode: GameMode, - challengeType: ChallengeType.SHOP_REMOVAL, - removals: ModifierTypeKeys[], -): boolean; - -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { +export function applyChallenges(challengeType: ChallengeType, ...args: any[]): boolean { let ret = false; - gameMode.challenges.forEach(c => { + globalScene.gameMode.challenges.forEach(c => { if (c.value !== 0) { switch (challengeType) { case ChallengeType.STARTER_CHOICE: @@ -1339,7 +1403,7 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType ret ||= c.applyPassiveAccess(args[0], args[1]); break; case ChallengeType.GAME_MODE_MODIFY: - ret ||= c.applyGameModeModify(gameMode); + ret ||= c.applyGameModeModify(); break; case ChallengeType.MOVE_ACCESS: ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); @@ -1350,20 +1414,20 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType case ChallengeType.FLIP_STAT: ret ||= c.applyFlipStat(args[0], args[1]); break; - case ChallengeType.MOVESET_MODIFY: - ret ||= c.applyMovesetModify(args[0]); + case ChallengeType.ENEMY_POKEMON_MODIFY: + ret ||= c.applyEnemyPokemonModify(args[0]); break; - case ChallengeType.NO_MOVE_LEARNING: - ret ||= c.applyNoMoveLearning(args[0]); + case ChallengeType.BAN_MOVE_LEARNING: + ret ||= c.applyBanMoveLearning(args[0], args[1]); break; - case ChallengeType.NO_PP_USE: - ret ||= c.applyNoPPUsage(args[0]); + case ChallengeType.MODIFY_PP_USE: + ret ||= c.applyModifyPPUsage(args[0], args[1], args[2]); break; - case ChallengeType.REWARD_TABLE_MODIFY: - ret ||= c.applyRewardTableModify(args[0]); + case ChallengeType.MODIFIER_POOL_MODIFY: + ret ||= c.applyModifierPoolModify(args[0], args[1]); break; - case ChallengeType.SHOP_REMOVAL: - ret ||= c.applyShopRemovals(args[0]); + case ChallengeType.SHOP_MODIFY: + ret ||= c.applyShopModify(args[0]); break; } } @@ -1422,7 +1486,7 @@ export function initChallenges() { export function checkStarterValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { if (!soft) { const isValidForChallenge = new Utils.BooleanHolder(true); - applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); + applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); return isValidForChallenge.value; } // We check the validity of every evolution and form change, and require that at least one is valid @@ -1460,7 +1524,7 @@ export function checkStarterValidForChallenge(species: PokemonSpecies, props: De */ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrProps, soft: boolean) { const isValidForChallenge = new Utils.BooleanHolder(true); - applyChallenges(globalScene.gameMode, ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); + applyChallenges(ChallengeType.STARTER_CHOICE, species, isValidForChallenge, props); if (!soft || !pokemonFormChanges.hasOwnProperty(species.speciesId)) { return isValidForChallenge.value; } @@ -1479,13 +1543,7 @@ function checkSpeciesValidForChallenge(species: PokemonSpecies, props: DexAttrPr const formProps = { ...props }; formProps.formIndex = formIndex; const isFormValidForChallenge = new Utils.BooleanHolder(true); - applyChallenges( - globalScene.gameMode, - ChallengeType.STARTER_CHOICE, - species, - isFormValidForChallenge, - formProps, - ); + applyChallenges(ChallengeType.STARTER_CHOICE, species, isFormValidForChallenge, formProps); if (isFormValidForChallenge.value) { return true; } diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 1555967789c..a91d32e0c4b 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -8082,7 +8082,7 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { return false; } const userTypes = user.getTypes(); - const validTypes = this.getTypeResistances(globalScene.gameMode, moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types + const validTypes = this.getTypeResistances(moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types if (!validTypes.length) { return false; } @@ -8098,13 +8098,13 @@ export class ResistLastMoveTypeAttr extends MoveEffectAttr { * Retrieve the types resisting a given type. Used by Conversion 2 * @returns An array populated with Types, or an empty array if no resistances exist (Unknown or Stellar type) */ - getTypeResistances(gameMode: GameMode, type: number): PokemonType[] { + getTypeResistances(type: number): PokemonType[] { const typeResistances: PokemonType[] = []; for (let i = 0; i < Object.keys(PokemonType).length; i++) { const multiplier = new NumberHolder(1); multiplier.value = getTypeDamageMultiplier(type, i); - applyChallenges(gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier); + applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier); if (multiplier.value < 1) { typeResistances.push(i); } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0c44ac53cd3..0dfd0908815 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -631,7 +631,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public isAllowedInChallenge(): boolean { const challengeAllowed = new Utils.BooleanHolder(true); applyChallenges( - globalScene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed, @@ -1588,7 +1587,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { calculateBaseStats(): number[] { const baseStats = this.getSpeciesForm(true).baseStats.slice(0); applyChallenges( - globalScene.gameMode, ChallengeType.FLIP_STAT, this, baseStats, @@ -1610,7 +1608,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isFusion()) { const fusionBaseStats = this.getFusionSpeciesForm(true).baseStats; applyChallenges( - globalScene.gameMode, ChallengeType.FLIP_STAT, this, fusionBaseStats, @@ -2586,7 +2583,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getTypeDamageMultiplier(moveType, defType), ); applyChallenges( - globalScene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, multiplier, ); @@ -2638,7 +2634,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getTypeDamageMultiplier(moveType, PokemonType.FLYING), ); applyChallenges( - globalScene.gameMode, ChallengeType.TYPE_EFFECTIVENESS, typeMultiplierAgainstFlying, ); @@ -6353,7 +6348,7 @@ export class PlayerPokemon extends Pokemon { if (reverseCompatibleTms.indexOf(moveId) > -1) { compatible = !compatible; } - if (compatible) { + if (compatible && !applyChallenges(ChallengeType.BAN_MOVE_LEARNING, this, moveId)) { this.compatibleTms.push(moveId); } } @@ -7034,7 +7029,6 @@ export class EnemyPokemon extends Pokemon { super.generateAndPopulateMoveset(); break; } - applyChallenges(globalScene.gameMode, ChallengeType.MOVESET_MODIFY, this); } /** diff --git a/src/game-mode.ts b/src/game-mode.ts index 9ab1674bcce..5e27c32f015 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -285,7 +285,7 @@ export class GameMode implements GameModeConfig { const dummyConfig = new FixedBattleConfig(); return ( this.battleConfig.hasOwnProperty(waveIndex) || - applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, dummyConfig) + applyChallenges(ChallengeType.FIXED_BATTLES, waveIndex, dummyConfig) ); } @@ -296,7 +296,7 @@ export class GameMode implements GameModeConfig { */ getFixedBattle(waveIndex: number): FixedBattleConfig { const challengeConfig = new FixedBattleConfig(); - if (applyChallenges(this, ChallengeType.FIXED_BATTLES, waveIndex, challengeConfig)) { + if (applyChallenges(ChallengeType.FIXED_BATTLES, waveIndex, challengeConfig)) { return challengeConfig; } return this.battleConfig[waveIndex]; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 6281e263b89..e2a95f96173 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2026,12 +2026,12 @@ export const modifierTypes = { }), BERRY: () => - new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { + new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { return new BerryModifierType(pregenArgs[0] as BerryType); } - const noMoveLearning = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, noMoveLearning); // Yeah this is kind of dumb + const ppCheck = new NumberHolder(1); + applyChallenges(ChallengeType.MODIFY_PP_USE, party[0], Moves.NONE, ppCheck); const berryTypes = getEnumValues(BerryType); let randBerryType: BerryType; const rand = randSeedInt(12); @@ -2039,7 +2039,7 @@ export const modifierTypes = { randBerryType = BerryType.SITRUS; } else if (rand < 4) { randBerryType = BerryType.LUM; - } else if (rand < 6 && !noMoveLearning.value) { + } else if (rand < 6 && ppCheck.value !== 0) { randBerryType = BerryType.LEPPA; } else { randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; @@ -2388,7 +2388,7 @@ export const modifierTypes = { ), }; -interface ModifierPool { +export interface ModifierPool { [tier: string]: WeightedModifierType[]; } @@ -3105,6 +3105,7 @@ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool pool = dailyStarterModifierPool; break; } + applyChallenges(ChallengeType.MODIFIER_POOL_MODIFY, poolType, pool); return pool; } @@ -3114,54 +3115,8 @@ const tierWeights = [768 / 1024, 195 / 1024, 48 / 1024, 12 / 1024, 1 / 1024]; */ export const itemPoolChecks: Map = new Map(); -export interface RewardTableModification { - type: ModifierTypeKeys; - tier: ModifierTier; - maxWeight: number; -} - export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount = 0) { - const modifications: RewardTableModification[] = []; - applyChallenges(globalScene.gameMode, ChallengeType.REWARD_TABLE_MODIFY, modifications); const pool = getModifierPoolForType(poolType); - modifications.map(mod => { - let t = mod.tier; - let dindex = pool[mod.tier].findIndex(wm => wm.modifierType.id === mod.type); - if (mod.maxWeight === 0) { - // Remove the modifier from the specified tier - pool[t].splice(dindex, 1); - } else if (dindex < 0) { - // Add the modifier to specified tier - for (t = ModifierTier.COMMON; t <= ModifierTier.MASTER && dindex < 0; t++) { - if (t === mod.tier) { - // We know it's not in that tier - continue; - } - dindex = pool[t].findIndex(wm => wm.modifierType.id === mod.type); - } - if (dindex >= 0) { - // Move the existing WMT to the specified tier with same func and specified max weight - const wmt = pool[t].splice(dindex, 1)[0]; - wmt.maxWeight = mod.maxWeight; - wmt.setTier(mod.tier); - pool[mod.tier].push(wmt); - } else { - // Item isn't anywhere on the table, make a new WMT and push it - const newWMT = new WeightedModifierType(getModifierTypeFuncById(mod.type), mod.maxWeight); - newWMT.setTier(mod.tier); - pool[mod.tier].push(newWMT); - } - } else { - pool[t].map(wmt => { - if (wmt.modifierType.id === mod.type) { - wmt.maxWeight = mod.maxWeight; - } - }); - } - }); - itemPoolChecks.forEach((_v, k) => { - itemPoolChecks.set(k, false); - }); const ignoredIndexes = {}; const modifierTableData = {}; @@ -3446,12 +3401,9 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseC [new ModifierTypeOption(modifierTypes.FULL_RESTORE(), 0, baseCost * 2.25)], [new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10)], ]; - const removeShop: ModifierTypeKeys[] = []; - applyChallenges(globalScene.gameMode, ChallengeType.SHOP_REMOVAL, removeShop); - return options - .slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)) - .flat() - .filter(s => !removeShop.includes(s.type.localeKey.split(".")[1] as ModifierTypeKeys)); // I don't wanna hear it + const opts = options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); + applyChallenges(ChallengeType.SHOP_MODIFY, opts); + return opts; } export function getEnemyBuffModifierForWave( diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index ffaec96c453..7a8e7f0bf6b 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -345,20 +345,16 @@ export class EvolutionPhase extends Phase { this.evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { - const skipMoveLearn = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, skipMoveLearn); - if (!skipMoveLearn.value) { - const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved - ? LearnMoveSituation.EVOLUTION_FUSED - : this.pokemon.fusionSpecies - ? LearnMoveSituation.EVOLUTION_FUSED_BASE - : LearnMoveSituation.EVOLUTION; - const levelMoves = this.pokemon - .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) - .filter(lm => lm[0] === EVOLVE_MOVE); - for (const lm of levelMoves) { - globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); - } + const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved + ? LearnMoveSituation.EVOLUTION_FUSED + : this.pokemon.fusionSpecies + ? LearnMoveSituation.EVOLUTION_FUSED_BASE + : LearnMoveSituation.EVOLUTION; + const levelMoves = this.pokemon + .getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) + .filter(lm => lm[0] === EVOLVE_MOVE); + for (const lm of levelMoves) { + globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); } globalScene.unshiftPhase(new EndEvolutionPhase()); diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 7bed71b3363..0c6d103fb33 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -13,6 +13,7 @@ import i18next from "i18next"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import type Pokemon from "#app/field/pokemon"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; export enum LearnMoveType { /** For learning a move via level-up, evolution, or other non-item-based event */ @@ -48,6 +49,10 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { const move = allMoves[this.moveId]; const currentMoveset = pokemon.getMoveset(); + if (applyChallenges(ChallengeType.BAN_MOVE_LEARNING, pokemon, this.moveId)) { + return this.end(); + } + // The game first checks if the Pokemon already has the move and ends the phase if it does. const hasMoveAlready = currentMoveset.some(m => m?.moveId === move.id) && this.moveId !== Moves.SKETCH; if (hasMoveAlready) { diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index 5346afdcc79..f4680f69074 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -64,13 +64,9 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { public override end() { if (this.lastLevel < 100) { // this feels like an unnecessary optimization - const skipMoveLearn = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, skipMoveLearn); - if (!skipMoveLearn.value) { - const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); - for (const lm of levelMoves) { - globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); - } + const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); + for (const lm of levelMoves) { + globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); } } if (!this.pokemon.pauseEvolutions) { diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index cc19aef1155..1670ec92154 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -353,7 +353,7 @@ export class MovePhase extends BattlePhase { let ppUsed = 1 + this.getPpIncreaseFromPressure(targets); const ignorePPChallenge = new BooleanHolder(false); - applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, ignorePPChallenge); + applyChallenges(ChallengeType.MODIFY_PP_USE, ignorePPChallenge); if (ignorePPChallenge.value) { ppUsed = 0; } diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 0f4360b0af6..b3ebe6731c9 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -101,7 +101,7 @@ export class SelectStarterPhase extends Phase { starterPokemon.generateFusionSpecies(true); } starterPokemon.setVisible(false); - applyChallenges(globalScene.gameMode, ChallengeType.STARTER_MODIFY, starterPokemon); + applyChallenges(ChallengeType.STARTER_MODIFY, starterPokemon); party.push(starterPokemon); loadPokemonAssets.push(starterPokemon.loadAssets()); }); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 82ad2276fef..b8110d7d551 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -2179,7 +2179,7 @@ export class GameData { } const cost = new Utils.NumberHolder(value); - applyChallenges(globalScene.gameMode, ChallengeType.STARTER_COST, speciesId, cost); + applyChallenges(ChallengeType.STARTER_COST, speciesId, cost); return cost.value; } diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 6a9b565989d..e46f86d4251 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -216,7 +216,7 @@ export default class PartyUiHandler extends MessageUiHandler { */ private FilterChallengeLegal = (pokemon: PlayerPokemon) => { const challengeAllowed = new Utils.BooleanHolder(true); - applyChallenges(globalScene.gameMode, ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); + applyChallenges(ChallengeType.POKEMON_IN_BATTLE, pokemon, challengeAllowed); if (!challengeAllowed.value) { return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: getPokemonNameWithAffix(pokemon), diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 8265ad827bc..9870309f52e 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2956,7 +2956,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { valueLimit.value = 10; } - Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); + Challenge.applyChallenges(Challenge.ChallengeType.STARTER_POINTS, valueLimit); return valueLimit.value; } From 82702d0b46add91ab8b92e4332804a0a37b13dbe Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Wed, 26 Mar 2025 21:29:57 -0400 Subject: [PATCH 08/18] Apply PP --- src/phases/move-phase.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 1670ec92154..fe33066910b 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -350,15 +350,11 @@ export class MovePhase extends BattlePhase { // "commit" to using the move, deducting PP. if (!this.ignorePp) { - let ppUsed = 1 + this.getPpIncreaseFromPressure(targets); + const ppUsed = new NumberHolder(1 + this.getPpIncreaseFromPressure(targets)); - const ignorePPChallenge = new BooleanHolder(false); - applyChallenges(ChallengeType.MODIFY_PP_USE, ignorePPChallenge); - if (ignorePPChallenge.value) { - ppUsed = 0; - } + applyChallenges(ChallengeType.MODIFY_PP_USE, this.pokemon, this.move.moveId, ppUsed); - this.move.usePp(ppUsed); + this.move.usePp(ppUsed.value); globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); } From 1a56f43c290301f0e5c164789c1756dbb83cb618 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Wed, 26 Mar 2025 22:19:13 -0400 Subject: [PATCH 09/18] Add considerations for Metronome mode in evolutions --- src/data/balance/pokemon-evolutions.ts | 32 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index c34bc229bd7..c2929276804 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -14,6 +14,8 @@ import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifi import { SpeciesFormKey } from "#enums/species-form-key"; import { speciesStarterCosts } from "./starters"; import i18next from "i18next"; +import { Challenges } from "#enums/challenges"; +import { Stat } from "#enums/stat"; export enum SpeciesWildEvolutionDelay { @@ -179,7 +181,7 @@ class TimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { class MoveEvolutionCondition extends SpeciesEvolutionCondition { public move: Moves; constructor(move: Moves) { - super(p => p.moveset.filter(m => m?.moveId === move).length > 0); + super(p => p.moveset.filter(m => m?.moveId === move).length > 0 || globalScene.gameMode.hasChallenge(Challenges.METRONOME)); this.move = move; const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) }); @@ -218,7 +220,12 @@ class FriendshipMoveTypeEvolutionCondition extends SpeciesEvolutionCondition { public amount: number; public type: PokemonType; constructor(amount: number, type: PokemonType) { - super(p => p.friendship >= amount && !!p.getMoveset().find(m => m?.getMove().type === type)); + super(p => + p.friendship >= amount && + (!!p.getMoveset().find(m => m?.getMove().type === type || + (globalScene.gameMode.hasChallenge(Challenges.METRONOME) && + !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(type) > -1) + )))); this.amount = amount; this.type = type; this.description = i18next.t("pokemonEvolutions:friendshipMoveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); @@ -262,7 +269,9 @@ class WeatherEvolutionCondition extends SpeciesEvolutionCondition { class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition { public type: PokemonType; constructor(type: PokemonType) { - super(p => p.moveset.filter(m => m?.getMove().type === type).length > 0); + super(p => p.moveset.filter(m => m?.getMove().type === type).length > 0 || + (globalScene.gameMode.hasChallenge(Challenges.METRONOME) && + !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(type) > -1))); this.type = type; this.description = i18next.t("pokemonEvolutions:moveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) }); } @@ -282,6 +291,11 @@ class TyrogueEvolutionCondition extends SpeciesEvolutionCondition { public move: Moves; constructor(move: Moves) { super(p => + (globalScene.gameMode.hasChallenge(Challenges.METRONOME) && ( // Metronome mode = no moves, do it the old fashioned way + (move === Moves.LOW_SWEEP && p.stats[Stat.ATK] > p.stats[Stat.DEF]) || + (move === Moves.MACH_PUNCH && p.stats[Stat.DEF] > p.stats[Stat.ATK]) || + (move === Moves.RAPID_SPIN && p.stats[Stat.DEF] === p.stats[Stat.ATK]) + )) || p.getMoveset(true).find(m => m && [ Moves.LOW_SWEEP, Moves.MACH_PUNCH, Moves.RAPID_SPIN ].includes(m?.moveId))?.moveId === move); this.move = move; const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); @@ -303,11 +317,17 @@ class MoveTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { public timesOfDay: TimeOfDay[]; constructor(move: Moves, tod: "day" | "night") { if (tod === "day") { - super(p => p.moveset.filter(m => m?.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); + super(p => + (p.moveset.filter(m => m?.moveId === move).length > 0 || + globalScene.gameMode.hasChallenge(Challenges.METRONOME)) && + (globalScene.arena.getTimeOfDay() === TimeOfDay.DAWN || globalScene.arena.getTimeOfDay() === TimeOfDay.DAY)); this.move = move; this.timesOfDay = [ TimeOfDay.DAWN, TimeOfDay.DAY ]; } else if (tod === "night") { - super(p => p.moveset.filter(m => m?.moveId === move).length > 0 && (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); + super(p => + (p.moveset.filter(m => m?.moveId === move).length > 0 || + globalScene.gameMode.hasChallenge(Challenges.METRONOME)) && + (globalScene.arena.getTimeOfDay() === TimeOfDay.DUSK || globalScene.arena.getTimeOfDay() === TimeOfDay.NIGHT)); this.move = move; this.timesOfDay = [ TimeOfDay.DUSK, TimeOfDay.NIGHT ]; } else { @@ -332,7 +352,7 @@ class DunsparceEvolutionCondition extends SpeciesEvolutionCondition { constructor() { super(p => { let ret = false; - if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0) { + if (p.moveset.filter(m => m?.moveId === Moves.HYPER_DRILL).length > 0 || globalScene.gameMode.hasChallenge(Challenges.METRONOME)) { globalScene.executeWithSeedOffset(() => ret = !Utils.randSeedInt(4), p.id); } return ret; From bb7bf170ef132774f6a1fa7904f179e739ccb854 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Wed, 26 Mar 2025 22:38:12 -0400 Subject: [PATCH 10/18] More modifier stuff --- src/data/challenge.ts | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 198a4d95c19..0e420a5c9b6 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1015,12 +1015,12 @@ export class MetronomeChallenge extends Challenge { } override applyStarterModify(pokemon: Pokemon): boolean { - pokemon.moveset = [new PokemonMove(Moves.METRONOME)]; + pokemon.moveset = [new PokemonMove(Moves.METRONOME, 0, 3)]; return true; } override applyEnemyPokemonModify(pokemon: EnemyPokemon): boolean { - pokemon.moveset = [new PokemonMove(Moves.METRONOME)]; + pokemon.moveset = [new PokemonMove(Moves.METRONOME, 0, 3)]; return true; } @@ -1037,38 +1037,26 @@ export class MetronomeChallenge extends Challenge { if (poolType !== ModifierPoolType.PLAYER) { return false; } - const common_block = ["TM_COMMON", "ETHER"]; - const great_block = ["ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "PP_UP", "MEMORY_MUSHROOM", "TM_GREAT"]; + const common_block = ["TM_COMMON", "ETHER", "MAX_ETHER"]; + const great_block = ["ELIXIR", "MAX_ELIXIR", "PP_UP", "MEMORY_MUSHROOM", "TM_GREAT"]; const ultra_block = ["TM_ULTRA", "PP_MAX"]; common_block.map(b => { - const idx = modifierPool[ModifierTier.COMMON].findIndex( - p => p.modifierType === getModifierType(getModifierTypeFuncById(b)), - ); + const idx = modifierPool[ModifierTier.COMMON].findIndex(p => p.modifierType.id === b); if (idx >= 0) { modifierPool[ModifierTier.COMMON].splice(idx, 1); - } else { - console.log(`${b} not found in Common tier!`); } }); great_block.map(b => { - const idx = modifierPool[ModifierTier.GREAT].findIndex( - p => p.modifierType === getModifierType(getModifierTypeFuncById(b)), - ); + const idx = modifierPool[ModifierTier.GREAT].findIndex(p => p.modifierType.id === b); if (idx >= 0) { modifierPool[ModifierTier.GREAT].splice(idx, 1); - } else { - console.log(`${b} not found in Great tier!`); } }); ultra_block.map(b => { - const idx = modifierPool[ModifierTier.ULTRA].findIndex( - p => p.modifierType === getModifierType(getModifierTypeFuncById(b)), - ); + const idx = modifierPool[ModifierTier.ULTRA].findIndex(p => p.modifierType.id === b); if (idx >= 0) { modifierPool[ModifierTier.ULTRA].splice(idx, 1); - } else { - console.log(`${b} not found in Ultra tier!`); } }); return true; @@ -1078,7 +1066,7 @@ export class MetronomeChallenge extends Challenge { const removals = ["ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "MEMORY_MUSHROOM"]; const opstart = options.length; removals.map(r => { - const idx = options.findIndex(o => o.type === getModifierType(getModifierTypeFuncById(r))); + const idx = options.findIndex(o => o.type.localeKey.split(".")[1] === r); // I don't wanna hear it! if (idx >= 0) { options.splice(idx, 1); } From 552fa2472099282bff60d7105e90d39356447d91 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Thu, 27 Mar 2025 19:21:13 -0400 Subject: [PATCH 11/18] ME stuff --- .../encounters/bug-type-superfan-encounter.ts | 2 + .../encounters/dancing-lessons-encounter.ts | 2 + .../department-store-sale-encounter.ts | 59 +++++++++++-------- .../encounters/part-timer-encounter.ts | 2 + .../can-learn-move-requirement.ts | 25 +++++--- src/field/pokemon.ts | 13 ++-- 6 files changed, 66 insertions(+), 37 deletions(-) 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 15cba1fa103..eee7a266a6b 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -57,6 +57,7 @@ import { allMoves } from "#app/data/moves/move"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/bugTypeSuperfan"; @@ -193,6 +194,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = MysteryEncounterBuilde ) .withMaxAllowedEncounters(1) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + .withDisallowedChallenges(Challenges.METRONOME) .withIntroSpriteConfigs([]) // These are set in onInit() .withAutoHideIntroVisuals(false) .withIntroDialogue([ diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 91f168371cf..332d44f3a04 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -41,6 +41,7 @@ import { PokeballType } from "#enums/pokeball"; import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; import i18next from "i18next"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/dancingLessons"; @@ -99,6 +100,7 @@ export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder ) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + .withDisallowedChallenges(Challenges.METRONOME) .withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals .withAnimations(EncounterAnim.DANCE) .withHideWildIntroMessage(true) 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 9b8e2e24d12..9804bbd77b2 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -11,6 +11,10 @@ import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounte import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; +import { CompatibleMoveRequirement } from "../mystery-encounter-requirements"; +import { Moves } from "#enums/moves"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/departmentStoreSale"; @@ -55,34 +59,37 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) - .withSimpleOption( - { - buttonLabel: `${namespace}:option.1.label`, - buttonTooltip: `${namespace}:option.1.tooltip`, - }, - async () => { - // Choose TMs - const modifiers: ModifierTypeFunc[] = []; - let i = 0; - while (i < 5) { - // 2/2/1 weight on TM rarity - const roll = randSeedInt(5); - if (roll < 2) { - modifiers.push(modifierTypes.TM_COMMON); - } else if (roll < 4) { - modifiers.push(modifierTypes.TM_GREAT); - } else { - modifiers.push(modifierTypes.TM_ULTRA); + .withOption( + MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) + .withPrimaryPokemonRequirement(new CompatibleMoveRequirement(Moves.PROTECT)) // Check Protect in compatible tms yeah this sucks + .withDialogue({ + buttonLabel: `${namespace}:option.1.label`, + buttonTooltip: `${namespace}:option.1.tooltip`, + }) + .withOptionPhase(async () => { + // Choose TMs + const modifiers: ModifierTypeFunc[] = []; + let i = 0; + while (i < 5) { + // 2/2/1 weight on TM rarity + const roll = randSeedInt(5); + if (roll < 2) { + modifiers.push(modifierTypes.TM_COMMON); + } else if (roll < 4) { + modifiers.push(modifierTypes.TM_GREAT); + } else { + modifiers.push(modifierTypes.TM_ULTRA); + } + i++; } - i++; - } - setEncounterRewards({ - guaranteedModifierTypeFuncs: modifiers, - fillRemaining: false, - }); - leaveEncounterWithoutBattle(); - }, + setEncounterRewards({ + guaranteedModifierTypeFuncs: modifiers, + fillRemaining: false, + }); + leaveEncounterWithoutBattle(); + }) + .build(), ) .withSimpleOption( { diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 61b48353997..9318a85d688 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -22,6 +22,7 @@ import type { PlayerPokemon } from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/partTimer"; @@ -36,6 +37,7 @@ export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.with ) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + .withDisallowedChallenges(Challenges.METRONOME) .withIntroSpriteConfigs([ { spriteKey: "part_timer_crate", diff --git a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts index a7ffe3e26ca..b272e00d760 100644 --- a/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts +++ b/src/data/mystery-encounters/requirements/can-learn-move-requirement.ts @@ -51,17 +51,23 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement { return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon; } + /** + * Queries party for mons meeting move requirements + * @param partyPokemon {@link PlayerPokemon} party to query + * @returns list of {@link PlayerPokemon} that match query + */ override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter(pokemon => - // every required move should be included - this.requiredMoves.every(requiredMove => this.getAllPokemonMoves(pokemon).includes(requiredMove)), + return partyPokemon.filter( + pokemon => + // every required move should be included + this.requiredMoves.length === this.getAllPokemonMoves(pokemon).length, ); } return partyPokemon.filter( pokemon => // none of the "required" moves should be included - !this.requiredMoves.some(requiredMove => this.getAllPokemonMoves(pokemon).includes(requiredMove)), + this.getAllPokemonMoves(pokemon).length === 0, ); } @@ -73,19 +79,24 @@ export class CanLearnMoveRequirement extends EncounterPokemonRequirement { return pkm.getLevelMoves().map(([_level, move]) => move); } + /** + * Gets all the moves with matching criteria + * @param pkm {@link PlayerPokemon} to check + * @returns list of {@link Moves} moves matching criteria + */ private getAllPokemonMoves(pkm: PlayerPokemon): Moves[] { const allPokemonMoves: Moves[] = []; if (!this.excludeLevelMoves) { - allPokemonMoves.push(...(this.getPokemonLevelMoves(pkm) ?? [])); + allPokemonMoves.push(...(this.getPokemonLevelMoves(pkm).filter(m => this.requiredMoves.includes(m)) ?? [])); } if (!this.excludeTmMoves) { - allPokemonMoves.push(...(pkm.compatibleTms ?? [])); + allPokemonMoves.push(...pkm.generateCompatibleTms(false, false).filter(m => this.requiredMoves.includes(m))); } if (!this.excludeEggMoves) { - allPokemonMoves.push(...(pkm.getEggMoves() ?? [])); + allPokemonMoves.push(...(pkm.getEggMoves()?.filter(m => this.requiredMoves.includes(m)) ?? [])); } return allPokemonMoves; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b49898389a8..ec54e6172c3 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -6318,8 +6318,8 @@ export class PlayerPokemon extends Pokemon { return this.getFieldIndex(); } - generateCompatibleTms(): void { - this.compatibleTms = []; + generateCompatibleTms(alsoSet: boolean = true, applyChal: boolean = true): Moves[] { + const ret: Moves[] = []; const tms = Object.keys(tmSpecies); for (const tm of tms) { @@ -6347,10 +6347,15 @@ export class PlayerPokemon extends Pokemon { if (reverseCompatibleTms.indexOf(moveId) > -1) { compatible = !compatible; } - if (compatible && !applyChallenges(ChallengeType.BAN_MOVE_LEARNING, this, moveId)) { - this.compatibleTms.push(moveId); + if (compatible && (!applyChal || !applyChallenges(ChallengeType.BAN_MOVE_LEARNING, this, moveId))) { + ret.push(moveId); } } + if (alsoSet) { + this.compatibleTms = ret; + } + + return ret; } tryPopulateMoveset(moveset: StarterMoveset): boolean { From d897d59dbf1761e11b6a11f3f50986d493004a4d Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Thu, 27 Mar 2025 19:44:47 -0400 Subject: [PATCH 12/18] No pp up --- .../encounters/department-store-sale-encounter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 9804bbd77b2..ba4e16c4a24 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -15,6 +15,8 @@ import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { CompatibleMoveRequirement } from "../mystery-encounter-requirements"; import { Moves } from "#enums/moves"; +import { globalScene } from "#app/global-scene"; +import { Challenges } from "#enums/challenges"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/departmentStoreSale"; @@ -103,7 +105,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu while (i < 3) { // 2/1 weight on base stat booster vs PP Up const roll = randSeedInt(3); - if (roll === 0) { + if (roll === 0 && !globalScene.gameMode.hasChallenge(Challenges.METRONOME)) { modifiers.push(modifierTypes.PP_UP); } else { modifiers.push(modifierTypes.BASE_STAT_BOOSTER); From 59617cfb340c1af3e8011e4acfcba35ce7c1e477 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Fri, 28 Mar 2025 13:33:36 -0400 Subject: [PATCH 13/18] Fix stupid test failing --- .../encounters/department-store-sale-encounter.ts | 10 +++++++++- .../encounters/department-store-sale-encounter.test.ts | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) 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 ba4e16c4a24..17e676c05f3 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,15 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBu .withQuery(`${namespace}:query`) .withOption( MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) - .withPrimaryPokemonRequirement(new CompatibleMoveRequirement(Moves.PROTECT)) // Check Protect in compatible tms yeah this sucks + .withPrimaryPokemonRequirement( + new CompatibleMoveRequirement([ + Moves.PROTECT, + Moves.TERA_BLAST, + Moves.INFESTATION, + Moves.BOUNCE, + Moves.ELECTROWEB, + ]), + ) // Check Protect in compatible tms yeah this sucks .withDialogue({ buttonLabel: `${namespace}:option.1.label`, buttonTooltip: `${namespace}:option.1.tooltip`, diff --git a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts index d4b0de30535..1321e91ec12 100644 --- a/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts +++ b/test/mystery-encounter/encounters/department-store-sale-encounter.test.ts @@ -84,7 +84,7 @@ describe("Department Store Sale - Mystery Encounter", () => { describe("Option 1 - TM Shop", () => { it("should have the correct properties", () => { const option = DepartmentStoreSaleEncounter.options[0]; - expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT); expect(option.dialogue).toBeDefined(); expect(option.dialogue).toStrictEqual({ buttonLabel: `${namespace}:option.1.label`, From 8a62d54dfd28d3b85bbe37f801de2bb8692f422b Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Sat, 29 Mar 2025 21:31:38 -0400 Subject: [PATCH 14/18] Clean up --- src/data/balance/pokemon-evolutions.ts | 8 +++++++- src/data/challenge.ts | 22 ++++++++++++++++++---- src/phases/evolution-phase.ts | 2 -- src/phases/level-up-phase.ts | 3 +-- src/phases/move-phase.ts | 2 +- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/data/balance/pokemon-evolutions.ts b/src/data/balance/pokemon-evolutions.ts index dd275b5bb4d..bc35d39e7cd 100644 --- a/src/data/balance/pokemon-evolutions.ts +++ b/src/data/balance/pokemon-evolutions.ts @@ -92,7 +92,7 @@ export class SpeciesFormEvolution { public evoFormKey: string | null; public level: number; public item: EvolutionItem | null; - public condition: SpeciesEvolutionCondition | null; + public condition: SpeciesEvolutionCondition | null; // TODO: Add a ChallengeType to change evolution conditions based on what kind of condition it is (use an enum) public wildDelay: SpeciesWildEvolutionDelay; public description = ""; @@ -181,6 +181,7 @@ class TimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { class MoveEvolutionCondition extends SpeciesEvolutionCondition { public move: Moves; constructor(move: Moves) { + // TODO: Remove deprecated Challenge check super(p => p.moveset.filter(m => m.moveId === move).length > 0 || globalScene.gameMode.hasChallenge(Challenges.METRONOME)); this.move = move; const moveKey = Moves[this.move].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); @@ -220,6 +221,7 @@ class FriendshipMoveTypeEvolutionCondition extends SpeciesEvolutionCondition { public amount: number; public type: PokemonType; constructor(amount: number, type: PokemonType) { + // TODO: Remove deprecated Challenge check super(p => p.friendship >= amount && (!!p.getMoveset().find(m => m?.getMove().type === type || @@ -269,6 +271,7 @@ class WeatherEvolutionCondition extends SpeciesEvolutionCondition { class MoveTypeEvolutionCondition extends SpeciesEvolutionCondition { public type: PokemonType; constructor(type: PokemonType) { + // TODO: Remove deprecated Challenge check super(p => p.moveset.filter(m => m?.getMove().type === type).length > 0 || (globalScene.gameMode.hasChallenge(Challenges.METRONOME) && !!globalScene.getPlayerParty().find(p => p.getTypes(false, false, true).indexOf(type) > -1))); @@ -290,6 +293,7 @@ class TreasureEvolutionCondition extends SpeciesEvolutionCondition { class TyrogueEvolutionCondition extends SpeciesEvolutionCondition { public move: Moves; constructor(move: Moves) { + // TODO: Remove deprecated Challenge check super(p => (globalScene.gameMode.hasChallenge(Challenges.METRONOME) && ( // Metronome mode = no moves, do it the old fashioned way (move === Moves.LOW_SWEEP && p.stats[Stat.ATK] > p.stats[Stat.DEF]) || @@ -316,6 +320,7 @@ class MoveTimeOfDayEvolutionCondition extends SpeciesEvolutionCondition { public move: Moves; public timesOfDay: TimeOfDay[]; constructor(move: Moves, tod: "day" | "night") { + // TODO: Remove deprecated Challenge check if (tod === "day") { super(p => (p.moveset.filter(m => m.moveId === move).length > 0 || @@ -350,6 +355,7 @@ class BiomeEvolutionCondition extends SpeciesEvolutionCondition { class DunsparceEvolutionCondition extends SpeciesEvolutionCondition { constructor() { + // TODO: Remove deprecated Challenge check super(p => { let ret = false; if (p.moveset.filter(m => m.moveId === Moves.HYPER_DRILL).length > 0 || globalScene.gameMode.hasChallenge(Challenges.METRONOME)) { diff --git a/src/data/challenge.ts b/src/data/challenge.ts index f91ce67274c..dbdb52ab7ad 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -114,7 +114,7 @@ export enum ChallengeType { */ MODIFY_PP_USE, /** - * Modifies modifier pools of specified type (PLEASE MODIFIER REWORK PLEASE I'M BEGGING YOU PLEASE PLEASE PLEASE PLEASE) + * Modifies modifier pools of specified type */ MODIFIER_POOL_MODIFY, /** @@ -914,19 +914,22 @@ export class FreshStartChallenge extends Challenge { if (poolType !== ModifierPoolType.PLAYER) { return false; } + let ret = false; let idx = modifierPool[ModifierTier.ULTRA].findIndex( p => p.modifierType === getModifierType(getModifierTypeFuncById("EVIOLITE")), ); if (idx >= 0) { modifierPool[ModifierTier.ULTRA].splice(idx, 1); + ret = true; } idx = modifierPool[ModifierTier.MASTER].findIndex( p => p.modifierType === getModifierType(getModifierTypeFuncById("MINI_BLACK_HOLE")), ); if (idx >= 0) { modifierPool[ModifierTier.MASTER].splice(idx, 1); + ret = true; } - return true; + return ret; } override getDifficulty(): number { @@ -1028,6 +1031,13 @@ export class MetronomeChallenge extends Challenge { return true; } + /** + * Makes sure 0 PP is used, called when applying other PP usage modifiers such as Pressure + * @param _pokemon {@link Pokemon} unused + * @param _move {@link Moves} unused + * @param usedPP + * @returns true + */ override applyModifyPPUsage(_pokemon: Pokemon, _move: Moves, usedPP: Utils.NumberHolder): boolean { usedPP.value = 0; return true; @@ -1037,6 +1047,7 @@ export class MetronomeChallenge extends Challenge { if (poolType !== ModifierPoolType.PLAYER) { return false; } + let ret = false; const common_block = ["TM_COMMON", "ETHER", "MAX_ETHER"]; const great_block = ["ELIXIR", "MAX_ELIXIR", "PP_UP", "MEMORY_MUSHROOM", "TM_GREAT"]; const ultra_block = ["TM_ULTRA", "PP_MAX"]; @@ -1045,21 +1056,24 @@ export class MetronomeChallenge extends Challenge { const idx = modifierPool[ModifierTier.COMMON].findIndex(p => p.modifierType.id === b); if (idx >= 0) { modifierPool[ModifierTier.COMMON].splice(idx, 1); + ret = true; } }); great_block.map(b => { const idx = modifierPool[ModifierTier.GREAT].findIndex(p => p.modifierType.id === b); if (idx >= 0) { modifierPool[ModifierTier.GREAT].splice(idx, 1); + ret = true; } }); ultra_block.map(b => { const idx = modifierPool[ModifierTier.ULTRA].findIndex(p => p.modifierType.id === b); if (idx >= 0) { modifierPool[ModifierTier.ULTRA].splice(idx, 1); + ret = true; } }); - return true; + return ret; } override applyShopModify(options: ModifierTypeOption[]): boolean { @@ -1335,7 +1349,7 @@ export function applyChallenges( ): boolean; /** - * Apply all challenges that modify the horrific abomination that is the modifier pools + * Apply all challenges that modify modifier pools //TODO: Modifier rework will need to look at this * @param challengeType {@link ChallengeType} ChallengeType.MODIFIER_POOL_MODIFY * @param poolType {@link ModifierPoolType} Which kind of pool is being changed (wild held items, player rewards etc) * @param modifierPool {@link ModifierPool} The item pool the challenge may attempt to modify diff --git a/src/phases/evolution-phase.ts b/src/phases/evolution-phase.ts index 7a8e7f0bf6b..bb283fa8139 100644 --- a/src/phases/evolution-phase.ts +++ b/src/phases/evolution-phase.ts @@ -17,8 +17,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; -import { BooleanHolder } from "#app/utils"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index f4680f69074..31c7fabf451 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -6,9 +6,8 @@ import { EvolutionPhase } from "#app/phases/evolution-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { LevelAchv } from "#app/system/achv"; -import { BooleanHolder, NumberHolder } from "#app/utils"; +import { NumberHolder } from "#app/utils"; import i18next from "i18next"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { protected lastLevel: number; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index fe33066910b..51fcae1f6df 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -43,7 +43,7 @@ import { MoveChargePhase } from "#app/phases/move-charge-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import { BooleanHolder, NumberHolder } from "#app/utils"; +import { NumberHolder } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { ArenaTagType } from "#enums/arena-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type"; From cf450afe5c4f902d787032c410c2a5d54494eeff Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:30:56 -0500 Subject: [PATCH 15/18] Add additional disallowed challenges --- .../mystery-encounters/encounters/clowning-around-encounter.ts | 2 +- .../encounters/global-trade-system-encounter.ts | 2 ++ .../mystery-encounters/encounters/mysterious-chest-encounter.ts | 2 ++ .../encounters/slumbering-snorlax-encounter.ts | 2 ++ .../encounters/the-expert-pokemon-breeder-encounter.ts | 2 ++ .../mystery-encounters/encounters/the-strong-stuff-encounter.ts | 2 ++ .../encounters/the-winstrate-challenge-encounter.ts | 2 ++ .../encounters/trash-to-treasure-encounter.ts | 2 ++ src/data/mystery-encounters/encounters/weird-dream-encounter.ts | 2 +- 9 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index eca99fc0c13..aabb2e6de8f 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -80,7 +80,7 @@ export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder MysteryEncounterType.CLOWNING_AROUND, ) .withEncounterTier(MysteryEncounterTier.ULTRA) - .withDisallowedChallenges(Challenges.SINGLE_TYPE) + .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.METRONOME) .withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withAnimations(EncounterAnim.SMOKESCREEN) .withAutoHideIntroVisuals(false) 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 c13501c4511..2b922d5bf9e 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -46,6 +46,7 @@ import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-en import type { PokeballType } from "#enums/pokeball"; import { doShinySparkleAnim } from "#app/field/anims"; import { TrainerType } from "#enums/trainer-type"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -100,6 +101,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil MysteryEncounterType.GLOBAL_TRADE_SYSTEM, ) .withEncounterTier(MysteryEncounterTier.COMMON) + .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withAutoHideIntroVisuals(false) .withIntroSpriteConfigs([ diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index c295e36749a..888fedc5a54 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -24,6 +24,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; +import { Challenges } from "#enums/challenges"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/mysteriousChest"; @@ -44,6 +45,7 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde MysteryEncounterType.MYSTERIOUS_CHEST, ) .withEncounterTier(MysteryEncounterTier.COMMON) + .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withScenePartySizeRequirement(2, 6, true) .withAutoHideIntroVisuals(false) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index bfa1204a8ba..e599598195e 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -29,6 +29,7 @@ import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { BerryType } from "#enums/berry-type"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; +import { Challenges } from "#enums/challenges"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounters/slumberingSnorlax"; @@ -42,6 +43,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil MysteryEncounterType.SLUMBERING_SNORLAX, ) .withEncounterTier(MysteryEncounterTier.GREAT) + .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withCatchAllowed(true) .withHideWildIntroMessage(true) diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index c189e341089..282b7bef4c2 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -30,6 +30,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { modifierTypes } from "#app/modifier/modifier-type"; import { PokemonType } from "#enums/pokemon-type"; import { getPokeballTintColor } from "#app/data/pokeball"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theExpertPokemonBreeder"; @@ -123,6 +124,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = MysteryEncount MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, ) .withEncounterTier(MysteryEncounterTier.ULTRA) + .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party .withIntroSpriteConfigs([]) // These are set in onInit() 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 e54df048430..7185b4acbd0 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -29,6 +29,7 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Stat } from "#enums/stat"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theStrongStuff"; @@ -46,6 +47,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder MysteryEncounterType.THE_STRONG_STUFF, ) .withEncounterTier(MysteryEncounterTier.COMMON) + .withDisallowedChallenges(Challenges.METRONOME) .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/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index aca04ad50ed..c62a0a578c4 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -34,6 +34,7 @@ import i18next from "i18next"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theWinstrateChallenge"; @@ -47,6 +48,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounter MysteryEncounterType.THE_WINSTRATE_CHALLENGE, ) .withEncounterTier(MysteryEncounterTier.ROGUE) + .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withIntroSpriteConfigs([ { 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 1c617aa2c7f..9994d6e4f05 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -27,6 +27,7 @@ import { Moves } from "#enums/moves"; import { BattlerIndex } from "#app/battle"; import { PokemonMove } from "#app/field/pokemon"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/trashToTreasure"; @@ -45,6 +46,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde MysteryEncounterType.TRASH_TO_TREASURE, ) .withEncounterTier(MysteryEncounterTier.ULTRA) + .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(60, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withMaxAllowedEncounters(1) .withFleeAllowed(false) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 22ec52e976c..11e40ab0020 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -129,7 +129,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit MysteryEncounterType.WEIRD_DREAM, ) .withEncounterTier(MysteryEncounterTier.ROGUE) - .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION) + .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION, Challenges.METRONOME) // TODO: should reset minimum wave to 10 when there are more Rogue tiers in pool. Matching Dark Deal minimum for now. .withSceneWaveRangeRequirement(30, 140) .withIntroSpriteConfigs([ From 5676a2ec3f5cf1109e858b9acb26bf08f4773d90 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Mon, 31 Mar 2025 13:58:45 -0400 Subject: [PATCH 16/18] Apply challenges to MEs --- .../encounters/global-trade-system-encounter.ts | 5 +++-- src/data/mystery-encounters/utils/encounter-phase-utils.ts | 3 +++ src/data/mystery-encounters/utils/encounter-pokemon-utils.ts | 2 ++ 3 files changed, 8 insertions(+), 2 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 2b922d5bf9e..df2322a38b7 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -46,7 +46,7 @@ import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-en import type { PokeballType } from "#enums/pokeball"; import { doShinySparkleAnim } from "#app/field/anims"; import { TrainerType } from "#enums/trainer-type"; -import { Challenges } from "#enums/challenges"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/globalTradeSystem"; @@ -101,7 +101,6 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil MysteryEncounterType.GLOBAL_TRADE_SYSTEM, ) .withEncounterTier(MysteryEncounterTier.COMMON) - .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withAutoHideIntroVisuals(false) .withIntroSpriteConfigs([ @@ -210,6 +209,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; + applyChallenges(ChallengeType.ENEMY_POKEMON_MODIFY, receivedPokemonData); const modifiers = tradedPokemon .getHeldItems() .filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); @@ -331,6 +331,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = MysteryEncounterBuil const encounter = globalScene.currentBattle.mysteryEncounter!; const tradedPokemon: PlayerPokemon = encounter.misc.tradedPokemon; const receivedPokemonData: EnemyPokemon = encounter.misc.receivedPokemon; + applyChallenges(ChallengeType.ENEMY_POKEMON_MODIFY, receivedPokemonData); const modifiers = tradedPokemon .getHeldItems() .filter(m => !(m instanceof PokemonFormChangeItemModifier) && !(m instanceof SpeciesStatBoosterModifier)); diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 5c6acf43e26..9d8d055a478 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -65,6 +65,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { PokemonType } from "#enums/pokemon-type"; import { getNatureName } from "#app/data/nature"; import { getPokemonNameWithAffix } from "#app/messages"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -394,6 +395,8 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig): enemyPokemon.mysteryEncounterBattleEffects = config.mysteryEncounterBattleEffects; } + applyChallenges(ChallengeType.ENEMY_POKEMON_MODIFY, enemyPokemon); + // Requires re-priming summon data to update everything properly enemyPokemon.primeSummonData(enemyPokemon.summonData); diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index a4787e819b8..ed48a64d375 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -38,6 +38,7 @@ import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import type { Abilities } from "#enums/abilities"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; @@ -643,6 +644,7 @@ export async function catchPokemon( showCatchObtainMessage = true, isObtain = false, ): Promise { + applyChallenges(ChallengeType.ENEMY_POKEMON_MODIFY, pokemon); const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); if ( From f71dd7787731659f33323e3de99ec2e466dac7d3 Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Mon, 31 Mar 2025 13:27:01 -0500 Subject: [PATCH 17/18] Allow Snorlax / Gimmighoul ME --- .../mystery-encounters/encounters/mysterious-chest-encounter.ts | 2 -- .../encounters/slumbering-snorlax-encounter.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 888fedc5a54..c295e36749a 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -24,7 +24,6 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; -import { Challenges } from "#enums/challenges"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/mysteriousChest"; @@ -45,7 +44,6 @@ export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilde MysteryEncounterType.MYSTERIOUS_CHEST, ) .withEncounterTier(MysteryEncounterTier.COMMON) - .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withScenePartySizeRequirement(2, 6, true) .withAutoHideIntroVisuals(false) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index e599598195e..bfa1204a8ba 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -29,7 +29,6 @@ import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { BerryType } from "#enums/berry-type"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; -import { Challenges } from "#enums/challenges"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounters/slumberingSnorlax"; @@ -43,7 +42,6 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuil MysteryEncounterType.SLUMBERING_SNORLAX, ) .withEncounterTier(MysteryEncounterTier.GREAT) - .withDisallowedChallenges(Challenges.METRONOME) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withCatchAllowed(true) .withHideWildIntroMessage(true) From 978e3e2a2ebf862b0ab38aee88d0e8e3aee6ff76 Mon Sep 17 00:00:00 2001 From: AJ Fontaine Date: Mon, 31 Mar 2025 14:35:32 -0400 Subject: [PATCH 18/18] Fix etern --- src/field/pokemon.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8859574e873..dc039746917 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -7074,6 +7074,7 @@ export class EnemyPokemon extends Pokemon { super.generateAndPopulateMoveset(); break; } + applyChallenges(ChallengeType.ENEMY_POKEMON_MODIFY, this); } /**