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; }