This commit is contained in:
AJ Fontaine 2025-03-31 18:36:27 +00:00 committed by GitHub
commit b7cfb46a9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 515 additions and 106 deletions

View File

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

View File

@ -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 {
@ -90,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 = "";
@ -179,7 +181,8 @@ 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);
// 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("");
this.description = i18next.t("pokemonEvolutions:move", { move: i18next.t(`move:${moveKey}.name`) });
@ -218,7 +221,13 @@ 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));
// TODO: Remove deprecated Challenge check
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 +271,10 @@ 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);
// 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)));
this.type = type;
this.description = i18next.t("pokemonEvolutions:moveType", { type: i18next.t(`pokemonInfo:Type.${PokemonType[this.type]}`) });
}
@ -281,7 +293,13 @@ 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]) ||
(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("");
@ -302,12 +320,19 @@ 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 && (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 {
@ -330,9 +355,10 @@ 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) {
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;

View File

@ -6,7 +6,7 @@ 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";
@ -15,12 +15,14 @@ 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 { ModifierTier } from "#app/modifier/modifier-tier";
import { globalScene } from "#app/global-scene";
import { pokemonFormChanges } from "./pokemon-forms";
import { pokemonEvolutions } from "./balance/pokemon-evolutions";
import { ModifierPoolType, type ModifierPool, type ModifierTypeOption } from "#app/modifier/modifier-type";
import type { LearnMoveType } from "#app/phases/learn-move-phase";
/** A constant for the default max cost of the starting party before a run */
const DEFAULT_PARTY_MAX_COST = 10;
@ -93,6 +95,26 @@ export enum ChallengeType {
* Modifies what the pokemon stats for Flip Stat Mode.
*/
FLIP_STAT,
/**
* Modifies enemy mons AFTER post process function
*/
ENEMY_POKEMON_MODIFY,
/**
* Prevents the learning of moves
*/
BAN_MOVE_LEARNING,
/**
* Negates PP Usage
*/
MODIFY_PP_USE,
/**
* Modifies modifier pools of specified type
*/
MODIFIER_POOL_MODIFY,
/**
* Modifies the shop options
*/
SHOP_MODIFY,
}
/**
@ -426,6 +448,57 @@ export abstract class Challenge {
applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) {
return false;
}
/**
* 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;
}
/**
* 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
* @param _learnType {@link LearnMoveType} How the move is being learned
* @param _valid: {@link BooleanHolder} Whether the move is valid for this challenge
* @returns {@link boolean} Whether the move should be restricted from learning
*/
applyBanMoveLearning(_pokemon: Pokemon, _move: Moves, _learnType: LearnMoveType, _valid: Utils.BooleanHolder) {
return false;
}
/**
* 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;
}
/**
* 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;
}
/**
* 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;
}
}
type ChallengeCondition = (data: GameData) => boolean;
@ -833,6 +906,29 @@ export class FreshStartChallenge extends Challenge {
return true;
}
override applyModifierPoolModify(poolType: ModifierPoolType, modifierPool: ModifierPool): boolean {
if (poolType !== ModifierPoolType.PLAYER) {
return false;
}
let ret = false;
let idx;
const bans = ["EVIOLITE", "MINI_BLACK_HOLE"];
const t = [ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.ULTRA, ModifierTier.ROGUE, ModifierTier.MASTER];
for (let i = 0; i < t.length; i++) {
idx = 0;
while (idx > -1) {
idx = modifierPool[t[i]].findIndex(p => bans.includes(p.modifierType.id));
if (idx > -1) {
modifierPool[t[i]].splice(idx, 1);
ret = true;
}
}
}
return ret;
}
override getDifficulty(): number {
return 0;
}
@ -864,6 +960,14 @@ export class InverseBattleChallenge extends Challenge {
return 0;
}
override applyEnemyPokemonModify(pokemon: EnemyPokemon): 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;
@ -905,6 +1009,97 @@ export class FlipStatChallenge extends Challenge {
}
}
export class MetronomeChallenge extends Challenge {
constructor() {
super(Challenges.METRONOME, 1);
}
override applyStarterModify(pokemon: Pokemon): boolean {
pokemon.moveset = [new PokemonMove(Moves.METRONOME, 0, 3)];
return true;
}
override applyEnemyPokemonModify(pokemon: EnemyPokemon): boolean {
pokemon.moveset = [new PokemonMove(Moves.METRONOME, 0, 3)];
return true;
}
override applyBanMoveLearning(
_pokemon: Pokemon,
_move: Moves,
_learnType: LearnMoveType,
valid: Utils.BooleanHolder,
): boolean {
valid.value = false;
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;
}
override applyModifierPoolModify(poolType: ModifierPoolType, modifierPool: ModifierPool): boolean {
if (poolType !== ModifierPoolType.PLAYER) {
return false;
}
let ret = false;
let idx;
const bans = [
"TM_COMMON",
"ETHER",
"MAX_ETHER",
"ELIXIR",
"MAX_ELIXIR",
"PP_UP",
"MEMORY_MUSHROOM",
"TM_GREAT",
"TM_ULTRA",
"PP_MAX",
];
const t = [ModifierTier.COMMON, ModifierTier.GREAT, ModifierTier.ULTRA, ModifierTier.ROGUE, ModifierTier.MASTER];
for (let i = 0; i < t.length; i++) {
idx = 0;
while (idx > -1) {
idx = modifierPool[t[i]].findIndex(p => bans.includes(p.modifierType.id));
if (idx > -1) {
modifierPool[t[i]].splice(idx, 1);
ret = true;
}
}
}
return ret;
}
override applyShopModify(options: ModifierTypeOption[]): boolean {
const removals = ["ETHER", "MAX_ETHER", "ELIXIR", "MAX_ELIXIR", "MEMORY_MUSHROOM"]; // Pending rework, these need to match locale key
const opstart = options.length;
removals.map(r => {
const idx = options.findIndex(o => o.type.localeKey.split(".")[1] === r); // Currently the quickest way to get the id
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;
newChallenge.severity = source.severity;
return newChallenge;
}
}
/**
* Lowers the amount of starter points available.
*/
@ -1125,6 +1320,66 @@ export function applyChallenges(
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
* @param learnType {@link LearnMoveType} how the move is being learned
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(
challengeType: ChallengeType.BAN_MOVE_LEARNING,
pokemon: Pokemon,
move: Moves,
learnType: LearnMoveType,
valid: Utils.BooleanHolder,
): 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(
challengeType: ChallengeType.MODIFY_PP_USE,
pokemon: Pokemon,
move: Moves,
usedPP: Utils.NumberHolder,
): boolean;
/**
* 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
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(
challengeType: ChallengeType.MODIFIER_POOL_MODIFY,
poolType: ModifierPoolType,
modifierPool: ModifierPool,
): 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(challengeType: ChallengeType, ...args: any[]): boolean {
let ret = false;
globalScene.gameMode.challenges.forEach(c => {
@ -1172,6 +1427,21 @@ export function applyChallenges(challengeType: ChallengeType, ...args: any[]): b
case ChallengeType.FLIP_STAT:
ret ||= c.applyFlipStat(args[0], args[1]);
break;
case ChallengeType.ENEMY_POKEMON_MODIFY:
ret ||= c.applyEnemyPokemonModify(args[0]);
break;
case ChallengeType.BAN_MOVE_LEARNING:
ret ||= c.applyBanMoveLearning(args[0], args[1], args[2], args[3]);
break;
case ChallengeType.MODIFY_PP_USE:
ret ||= c.applyModifyPPUsage(args[0], args[1], args[2]);
break;
case ChallengeType.MODIFIER_POOL_MODIFY:
ret ||= c.applyModifierPoolModify(args[0], args[1]);
break;
case ChallengeType.SHOP_MODIFY:
ret ||= c.applyShopModify(args[0]);
break;
}
}
});
@ -1199,6 +1469,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");
}
@ -1212,6 +1484,7 @@ export function initChallenges() {
new FreshStartChallenge(),
new InverseBattleChallenge(),
new FlipStatChallenge(),
new MetronomeChallenge(),
);
}

View File

@ -8090,7 +8090,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;
}
@ -8106,7 +8106,7 @@ 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++) {

View File

@ -54,6 +54,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";
@ -190,6 +191,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([

View File

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

View File

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

View File

@ -11,6 +11,11 @@ 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 { CanLearnTMRequirement } from "../mystery-encounter-requirements";
import { globalScene } from "#app/global-scene";
import { Challenges } from "#enums/challenges";
/** i18n namespace for encounter */
const namespace = "mysteryEncounters/departmentStoreSale";
@ -55,34 +60,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 CanLearnTMRequirement())
.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(
{
@ -96,7 +104,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);

View File

@ -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 { applyChallenges, ChallengeType } from "#app/data/challenge";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/globalTradeSystem";
@ -208,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));
@ -329,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));

View File

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

View File

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

View File

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

View File

@ -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([
{

View File

@ -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";
import { randSeedInt } from "#app/utils";
/** the i18n namespace for this encounter */
@ -46,6 +47,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)

View File

@ -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([

View File

@ -650,6 +650,39 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
}
}
export class CanLearnTMRequirement extends EncounterPokemonRequirement {
min: number;
excludeDisallowedPokemon: boolean;
/**
* Constructs a new CanLearnTMRequirement
* @param min Minimum number of party members who can learn a TM, defaults to 1.
* @param excludeDisallowed Whether to exclude ineligible party members, defaults to false
*/
constructor(min = 1, excludeDisallowed = false) {
super();
this.min = min;
this.excludeDisallowedPokemon = excludeDisallowed;
}
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon)) {
return false;
}
return this.queryParty(partyPokemon).length >= this.min;
}
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
return partyPokemon.filter(
pokemon => (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) && pokemon.compatibleTms.length >= 0,
);
}
override getDialogueToken(pokemon?: PlayerPokemon): [string, string] {
return ["canLearnTM", ""];
}
}
export class AbilityRequirement extends EncounterPokemonRequirement {
requiredAbilities: Abilities[];
minNumberOfPokemon: number;

View File

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

View File

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

View File

@ -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<void> {
applyChallenges(ChallengeType.ENEMY_POKEMON_MODIFY, pokemon);
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
if (

View File

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

View File

@ -241,7 +241,7 @@ import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages";
import { DamageAnimPhase } from "#app/phases/damage-anim-phase";
import { FaintPhase } from "#app/phases/faint-phase";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { ObtainStatusEffectPhase } from "#app/phases/obtain-status-effect-phase";
@ -6360,13 +6360,20 @@ export class PlayerPokemon extends Pokemon {
return this.getFieldIndex();
}
generateCompatibleTms(): void {
this.compatibleTms = [];
/**
* Finds the list of TMs this PlayerPokemon is compatible with based on species, form, and challenges
* @param alsoSet Whether this PlayerPokemon's compatibleTms list should be set by this function
* @param applyChal Whether to apply challenges which would ban the mon from learning specific moves
* @returns {@link Moves} the list of compatible TM moves for this PlayerPokemon
*/
generateCompatibleTms(alsoSet: boolean = true, applyChal: boolean = true): Moves[] {
const ret: Moves[] = [];
const compatible = new Utils.BooleanHolder(false);
const tms = Object.keys(tmSpecies);
for (const tm of tms) {
const moveId = Number.parseInt(tm) as Moves;
let compatible = false;
compatible.value = false;
for (const p of tmSpecies[tm]) {
if (Array.isArray(p)) {
const [pkm, form] = p;
@ -6375,24 +6382,32 @@ export class PlayerPokemon extends Pokemon {
(this.fusionSpecies && pkm === this.fusionSpecies.speciesId)) &&
form === this.getFormKey()
) {
compatible = true;
compatible.value = true;
break;
}
} else if (
p === this.species.speciesId ||
(this.fusionSpecies && p === this.fusionSpecies.speciesId)
) {
compatible = true;
compatible.value = true;
break;
}
}
if (reverseCompatibleTms.indexOf(moveId) > -1) {
compatible = !compatible;
compatible.value = !compatible.value;
}
if (compatible) {
this.compatibleTms.push(moveId);
if (compatible.value && applyChal) {
applyChallenges(ChallengeType.BAN_MOVE_LEARNING, this, moveId, LearnMoveType.TM, compatible);
}
if (compatible.value) {
ret.push(moveId);
}
}
if (alsoSet) {
this.compatibleTms = ret;
}
return ret;
}
tryPopulateMoveset(moveset: StarterMoveset): boolean {
@ -7054,14 +7069,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(ChallengeType.ENEMY_POKEMON_MODIFY, this);
}
/**

View File

@ -127,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;
@ -2032,10 +2033,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 ppReduction = new NumberHolder(1); // How much PP is reduced when using a move in this game mode
applyChallenges(ChallengeType.MODIFY_PP_USE, party[0], Moves.NONE, ppReduction);
const berryTypes = getEnumValues(BerryType);
let randBerryType: BerryType;
const rand = randSeedInt(12);
@ -2043,7 +2046,8 @@ export const modifierTypes = {
randBerryType = BerryType.SITRUS;
} else if (rand < 4) {
randBerryType = BerryType.LUM;
} else if (rand < 6) {
} else if (rand < 6 && ppReduction.value !== 0) {
// If PP isn't reduced, it doesn't need to be restored
randBerryType = BerryType.LEPPA;
} else {
randBerryType = berryTypes[randSeedInt(berryTypes.length - 3) + 2];
@ -2392,7 +2396,7 @@ export const modifierTypes = {
),
};
interface ModifierPool {
export interface ModifierPool {
[tier: string]: WeightedModifierType[];
}
@ -2691,7 +2695,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 (
@ -2948,11 +2952,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 => {
@ -3154,6 +3154,7 @@ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool
pool = dailyStarterModifierPool;
break;
}
applyChallenges(ChallengeType.MODIFIER_POOL_MODIFY, poolType, pool);
return pool;
}
@ -3165,9 +3166,6 @@ export const itemPoolChecks: Map<ModifierTypeKeys, boolean | undefined> = new Ma
export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: ModifierPoolType, rerollCount = 0) {
const pool = getModifierPoolForType(poolType);
itemPoolChecks.forEach((_v, k) => {
itemPoolChecks.set(k, false);
});
const ignoredIndexes = {};
const modifierTableData = {};
@ -3452,7 +3450,9 @@ 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 opts = options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat();
applyChallenges(ChallengeType.SHOP_MODIFY, opts);
return opts;
}
export function getEnemyBuffModifierForWave(

View File

@ -13,6 +13,8 @@ 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";
import { BooleanHolder } from "#app/utils";
export enum LearnMoveType {
/** For learning a move via level-up, evolution, or other non-item-based event */
@ -48,6 +50,13 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
const move = allMoves[this.moveId];
const currentMoveset = pokemon.getMoveset();
const canLearn = new BooleanHolder(true);
applyChallenges(ChallengeType.BAN_MOVE_LEARNING, pokemon, this.moveId, 0, canLearn);
if (!canLearn.value) {
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) {

View File

@ -50,6 +50,7 @@ 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;
@ -349,9 +350,11 @@ export class MovePhase extends BattlePhase {
// "commit" to using the move, deducting PP.
if (!this.ignorePp) {
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
const ppUsed = new NumberHolder(1 + this.getPpIncreaseFromPressure(targets));
this.move.usePp(ppUsed);
applyChallenges(ChallengeType.MODIFY_PP_USE, this.pokemon, this.move.moveId, ppUsed);
this.move.usePp(ppUsed.value);
globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
}

View File

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

View File

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