Metronome challenge

This commit is contained in:
AJ Fontaine 2025-03-21 23:06:31 -04:00
parent 585f040057
commit 16df3fd806
7 changed files with 194 additions and 27 deletions

View File

@ -16,7 +16,7 @@ import { Challenges } from "#enums/challenges";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { TypeColor, TypeShadow } from "#enums/color"; import { TypeColor, TypeShadow } from "#enums/color";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { pokemonFormChanges } from "#app/data/pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms";
@ -93,6 +93,18 @@ export enum ChallengeType {
* Modifies what the pokemon stats for Flip Stat Mode. * Modifies what the pokemon stats for Flip Stat Mode.
*/ */
FLIP_STAT, 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[]) { applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) {
return false; return false;
} }
applyMovesetModify(_pokemon: Pokemon) {
return false;
}
applyNoMoveLearning(_valid: Utils.BooleanHolder) {
return false;
}
applyNoPPUsage(_valid: Utils.BooleanHolder) {
return false;
}
} }
type ChallengeCondition = (data: GameData) => boolean; type ChallengeCondition = (data: GameData) => boolean;
@ -917,6 +941,14 @@ export class InverseBattleChallenge extends Challenge {
return 0; 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 { applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean {
if (effectiveness.value < 1) { if (effectiveness.value < 1) {
effectiveness.value = 2; 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. * Lowers the amount of starter points available.
*/ */
@ -1216,6 +1283,24 @@ export function applyChallenges(
baseStats: number[], baseStats: number[],
): boolean; ): 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 { export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false; let ret = false;
gameMode.challenges.forEach(c => { gameMode.challenges.forEach(c => {
@ -1263,6 +1348,15 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
case ChallengeType.FLIP_STAT: case ChallengeType.FLIP_STAT:
ret ||= c.applyFlipStat(args[0], args[1]); ret ||= c.applyFlipStat(args[0], args[1]);
break; 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); return InverseBattleChallenge.loadChallenge(source);
case Challenges.FLIP_STAT: case Challenges.FLIP_STAT:
return FlipStatChallenge.loadChallenge(source); return FlipStatChallenge.loadChallenge(source);
case Challenges.METRONOME:
return MetronomeChallenge.loadChallenge(source);
} }
throw new Error("Unknown challenge copied"); throw new Error("Unknown challenge copied");
} }
@ -1303,5 +1399,6 @@ export function initChallenges() {
new FreshStartChallenge(), new FreshStartChallenge(),
new InverseBattleChallenge(), new InverseBattleChallenge(),
new FlipStatChallenge(), new FlipStatChallenge(),
new MetronomeChallenge(),
); );
} }

View File

@ -6,4 +6,5 @@ export enum Challenges {
FRESH_START, FRESH_START,
INVERSE_BATTLE, INVERSE_BATTLE,
FLIP_STAT, FLIP_STAT,
METRONOME,
} }

View File

@ -7024,14 +7024,12 @@ export class EnemyPokemon extends Pokemon {
new PokemonMove(Moves.FLAMETHROWER), new PokemonMove(Moves.FLAMETHROWER),
new PokemonMove(Moves.COSMIC_POWER), new PokemonMove(Moves.COSMIC_POWER),
]; ];
if (globalScene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) {
this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT);
}
break; break;
default: default:
super.generateAndPopulateMoveset(); super.generateAndPopulateMoveset();
break; break;
} }
applyChallenges(globalScene.gameMode, ChallengeType.MOVESET_MODIFY, this);
} }
/** /**

View File

@ -106,6 +106,7 @@ import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party
import PartyUiHandler from "#app/ui/party-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler";
import { getModifierTierTextTint } from "#app/ui/text"; import { getModifierTierTextTint } from "#app/ui/text";
import { import {
BooleanHolder,
formatMoney, formatMoney,
getEnumKeys, getEnumKeys,
getEnumValues, getEnumValues,
@ -126,6 +127,7 @@ import type { PermanentStat, TempBattleStat } from "#enums/stat";
import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; import i18next from "i18next";
import { applyChallenges, ChallengeType } from "#app/data/challenge";
const outputModifierData = false; const outputModifierData = false;
const useMaxWeightForOutput = false; const useMaxWeightForOutput = false;
@ -2028,6 +2030,8 @@ export const modifierTypes = {
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in BerryType) {
return new BerryModifierType(pregenArgs[0] as 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); const berryTypes = getEnumValues(BerryType);
let randBerryType: BerryType; let randBerryType: BerryType;
const rand = randSeedInt(12); const rand = randSeedInt(12);
@ -2035,7 +2039,7 @@ export const modifierTypes = {
randBerryType = BerryType.SITRUS; randBerryType = BerryType.SITRUS;
} else if (rand < 4) { } else if (rand < 4) {
randBerryType = BerryType.LUM; randBerryType = BerryType.LUM;
} else if (rand < 6) { } else if (rand < 6 && !noMoveLearning.value) {
randBerryType = BerryType.LEPPA; randBerryType = BerryType.LEPPA;
} else { } else {
randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2]; randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2];
@ -2464,14 +2468,30 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)), new WeightedModifierType(modifierTypes.LURE, lureWeightFunc(10, 2)),
new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4), new WeightedModifierType(modifierTypes.TEMP_STAT_STAGE_BOOSTER, 4),
new WeightedModifierType(modifierTypes.BERRY, 2), 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 => { ].map(m => {
m.setTier(ModifierTier.COMMON); m.setTier(ModifierTier.COMMON);
return m; return m;
}), }),
[ModifierTier.GREAT]: [ [ModifierTier.GREAT]: [
new WeightedModifierType(modifierTypes.GREAT_BALL, () => (hasMaximumBalls(PokeballType.GREAT_BALL) ? 0 : 6), 6), 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( new WeightedModifierType(
modifierTypes.FULL_HEAL, modifierTypes.FULL_HEAL,
(party: Pokemon[]) => { (party: Pokemon[]) => {
@ -2567,6 +2587,11 @@ const modifierPool: ModifierPool = {
new WeightedModifierType( new WeightedModifierType(
modifierTypes.ELIXIR, modifierTypes.ELIXIR,
(party: Pokemon[]) => { (party: Pokemon[]) => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning);
if (noMoveLearning.value) {
return 0;
}
const thresholdPartyMemberCount = Math.min( const thresholdPartyMemberCount = Math.min(
party.filter( party.filter(
p => p =>
@ -2586,6 +2611,11 @@ const modifierPool: ModifierPool = {
new WeightedModifierType( new WeightedModifierType(
modifierTypes.MAX_ELIXIR, modifierTypes.MAX_ELIXIR,
(party: Pokemon[]) => { (party: Pokemon[]) => {
const noMoveLearning = new BooleanHolder(false);
applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, noMoveLearning);
if (noMoveLearning.value) {
return 0;
}
const thresholdPartyMemberCount = Math.min( const thresholdPartyMemberCount = Math.min(
party.filter( party.filter(
p => p =>
@ -2618,11 +2648,21 @@ const modifierPool: ModifierPool = {
2, 2,
), ),
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 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( new WeightedModifierType(
modifierTypes.MEMORY_MUSHROOM, modifierTypes.MEMORY_MUSHROOM,
(party: Pokemon[]) => { (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; return 0;
} }
const highestPartyLevel = party 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.ULTRA_BALL, () => (hasMaximumBalls(PokeballType.ULTRA_BALL) ? 0 : 15), 15),
new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)), new WeightedModifierType(modifierTypes.MAX_LURE, lureWeightFunc(30, 4)),
new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)), 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.MINT, 4),
new WeightedModifierType( new WeightedModifierType(
modifierTypes.RARE_EVOLUTION_ITEM, modifierTypes.RARE_EVOLUTION_ITEM,
@ -2813,7 +2861,15 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4), new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)), new WeightedModifierType(modifierTypes.CANDY_JAR, skipInLastClassicWaveOrDefault(5)),
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9), 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.RARER_CANDY, 4),
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)), new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, skipInLastClassicWaveOrDefault(2)),
new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)), new WeightedModifierType(modifierTypes.IV_SCANNER, skipInLastClassicWaveOrDefault(4)),

View File

@ -17,6 +17,8 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase"; import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves"; 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 { export class EvolutionPhase extends Phase {
protected pokemon: PlayerPokemon; protected pokemon: PlayerPokemon;
@ -343,16 +345,20 @@ export class EvolutionPhase extends Phase {
this.evolutionHandler.canCancel = false; this.evolutionHandler.canCancel = false;
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => { this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => {
const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved const skipMoveLearn = new BooleanHolder(false);
? LearnMoveSituation.EVOLUTION_FUSED applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, skipMoveLearn);
: this.pokemon.fusionSpecies if (!skipMoveLearn.value) {
? LearnMoveSituation.EVOLUTION_FUSED_BASE const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved
: LearnMoveSituation.EVOLUTION; ? LearnMoveSituation.EVOLUTION_FUSED
const levelMoves = this.pokemon : this.pokemon.fusionSpecies
.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation) ? LearnMoveSituation.EVOLUTION_FUSED_BASE
.filter(lm => lm[0] === EVOLVE_MOVE); : LearnMoveSituation.EVOLUTION;
for (const lm of levelMoves) { const levelMoves = this.pokemon
globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1])); .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()); globalScene.unshiftPhase(new EndEvolutionPhase());

View File

@ -6,8 +6,9 @@ import { EvolutionPhase } from "#app/phases/evolution-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase"; import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
import { LevelAchv } from "#app/system/achv"; import { LevelAchv } from "#app/system/achv";
import { NumberHolder } from "#app/utils"; import { BooleanHolder, NumberHolder } from "#app/utils";
import i18next from "i18next"; import i18next from "i18next";
import { applyChallenges, ChallengeType } from "#app/data/challenge";
export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { export class LevelUpPhase extends PlayerPartyMemberPokemonPhase {
protected lastLevel: number; protected lastLevel: number;
@ -63,9 +64,13 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase {
public override end() { public override end() {
if (this.lastLevel < 100) { if (this.lastLevel < 100) {
// this feels like an unnecessary optimization // this feels like an unnecessary optimization
const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1); const skipMoveLearn = new BooleanHolder(false);
for (const lm of levelMoves) { applyChallenges(globalScene.gameMode, ChallengeType.NO_MOVE_LEARNING, skipMoveLearn);
globalScene.unshiftPhase(new LearnMovePhase(this.partyMemberIndex, lm[1])); 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) { if (!this.pokemon.pauseEvolutions) {

View File

@ -43,13 +43,14 @@ import { MoveChargePhase } from "#app/phases/move-charge-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-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 { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; import i18next from "i18next";
import { applyChallenges, ChallengeType } from "#app/data/challenge";
export class MovePhase extends BattlePhase { export class MovePhase extends BattlePhase {
protected _pokemon: Pokemon; protected _pokemon: Pokemon;
@ -104,11 +105,14 @@ export class MovePhase extends BattlePhase {
) { ) {
super(); super();
const ignorePPChallenge = new BooleanHolder(ignorePp);
applyChallenges(globalScene.gameMode, ChallengeType.NO_PP_USE, ignorePPChallenge);
this.pokemon = pokemon; this.pokemon = pokemon;
this.targets = targets; this.targets = targets;
this.move = move; this.move = move;
this.followUp = followUp; this.followUp = followUp;
this.ignorePp = ignorePp; this.ignorePp = ignorePPChallenge.value;
this.reflected = reflected; this.reflected = reflected;
this.forcedLast = forcedLast; this.forcedLast = forcedLast;
} }