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 { 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(),
);
}

View File

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

View File

@ -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);
}
/**

View File

@ -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)),

View File

@ -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,6 +345,9 @@ 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
@ -354,6 +359,7 @@ export class EvolutionPhase extends Phase {
for (const lm of levelMoves) {
globalScene.unshiftPhase(new LearnMovePhase(globalScene.getPlayerParty().indexOf(this.pokemon), lm[1]));
}
}
globalScene.unshiftPhase(new EndEvolutionPhase());
globalScene.playSound("se/shine");

View File

@ -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,11 +64,15 @@ 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]));
}
}
}
if (!this.pokemon.pauseEvolutions) {
const evolution = this.pokemon.getEvolution();
if (evolution) {

View File

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