diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 2205519c532..4379ce56982 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,20 +1,21 @@ -import * as Utils from "../utils"; -import i18next from "i18next"; -import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data"; -import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species"; -import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { BattleType, FixedBattleConfig } from "#app/battle"; +import { Nature } from "#app/data/nature"; +import { pokemonEvolutions } from "#app/data/pokemon-evolutions"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "#app/data/pokemon-species"; +import { Type } from "#app/data/type"; +import { TypeColor, TypeShadow } from "#app/enums/color"; +import { Moves } from "#app/enums/moves"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import Trainer, { TrainerVariant } from "#app/field/trainer"; import { GameMode } from "#app/game-mode"; -import { Type } from "./type"; +import { ModifierTypeOption } from "#app/modifier/modifier-type"; +import { defaultStarterSpecies, DexAttrProps, GameData } from "#app/system/game-data"; +import { BooleanHolder, NumberHolder, randSeedItem } from "#app/utils"; import { Challenges } from "#enums/challenges"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; -import { Nature } from "./nature"; -import { Moves } from "#app/enums/moves"; -import { TypeColor, TypeShadow } from "#app/enums/color"; -import { pokemonEvolutions } from "./pokemon-evolutions"; -import { pokemonFormChanges } from "./pokemon-forms"; +import i18next from "i18next"; /** A constant for the default max cost of the starting party before a run */ const DEFAULT_PARTY_MAX_COST = 10; @@ -26,32 +27,32 @@ const DEFAULT_PARTY_MAX_COST = 10; export enum ChallengeType { /** * Challenges which modify what starters you can choose - * @see {@link Challenge.applyStarterChoice} + * @see {@linkcode Challenge.applyStarterChoice} */ STARTER_CHOICE, /** * Challenges which modify how many starter points you have - * @see {@link Challenge.applyStarterPoints} + * @see {@linkcode Challenge.applyStarterPoints} */ STARTER_POINTS, /** * Challenges which modify how many starter points you have - * @see {@link Challenge.applyStarterPointCost} + * @see {@linkcode Challenge.applyStarterPointCost} */ STARTER_COST, /** * Challenges which modify your starters in some way - * @see {@link Challenge.applyStarterModify} + * @see {@linkcode Challenge.applyStarterModify} */ STARTER_MODIFY, /** * Challenges which limit which pokemon you can have in battle. - * @see {@link Challenge.applyPokemonInBattle} + * @see {@linkcode Challenge.applyPokemonInBattle} */ POKEMON_IN_BATTLE, /** * Adds or modifies the fixed battles in a run - * @see {@link Challenge.applyFixedBattle} + * @see {@linkcode Challenge.applyFixedBattle} */ FIXED_BATTLES, /** @@ -83,6 +84,36 @@ export enum ChallengeType { * Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED. */ MOVE_WEIGHT, + /** + * Checks if the heal phase should be run + * @see {@linkcode Challenge.applyNoHealPhase} + */ + NO_HEAL_PHASE, + /** + * Checks if the shop item is blacklisted + * @see {@linkcode Challenge.applyShopItemBlacklist} + */ + SHOP_ITEM_BLACKLIST, + /** + * Checks if the random item is blacklisted + * @see {@linkcode Challenge.applyRandomItemBlacklist} + */ + RANDOM_ITEM_BLACKLIST, + /** + * Checks if the cought pokemon can be add to the team + * @see {@linkcode Challenge.applyAddPokemonToParty} + */ + ADD_POKEMON_TO_PARTY, + /** + * Checks if the move is blacklisted + * @see {@linkcode Challenge.applyMoveBlacklist} + */ + MOVE_BLACKLIST, + /** + * Checks if pokemon are allowed to be revived from fainting + * @see {@linkcode Challenge.applyRevivePrevention} + */ + PREVENT_REVIVE, } /** @@ -102,19 +133,23 @@ export enum MoveSourceType { * A challenge object. Exists only to serve as a base class. */ export abstract class Challenge { - public id: Challenges; // The id of the challenge - - public value: integer; // The "strength" of the challenge, all challenges have a numerical value. - public maxValue: integer; // The maximum strength of the challenge. - public severity: integer; // The current severity of the challenge. Some challenges have multiple severities in addition to strength. - public maxSeverity: integer; // The maximum severity of the challenge. - + /** The id of the challenge */ + public id: Challenges; + /** The "strength" of the challenge. All challenges have a numerical value. */ + public value: number; + /** The maximum strength of the challenge. */ + public maxValue: number; + /** The current severity of the challenge. Some challenges have multiple severities in addition to strength. */ + public severity: number; + /** The maximum severity of the challenge. */ + public maxSeverity: number; + /** Unlock conditions of the challenge. */ public conditions: ChallengeCondition[]; /** - * @param id {@link Challenges} The enum value for the challenge + * @param id {@linkcode Challenges} The enum value for the challenge */ - constructor(id: Challenges, maxValue: integer = Number.MAX_SAFE_INTEGER) { + constructor(id: Challenges, maxValue: number = Number.MAX_SAFE_INTEGER) { this.id = id; this.value = 0; @@ -134,7 +169,7 @@ export abstract class Challenge { /** * Gets the localisation key for the challenge - * @returns {@link string} The i18n key for this challenge + * @returns The i18n key for this challenge */ geti18nKey(): string { return Challenges[this.id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); @@ -143,7 +178,7 @@ export abstract class Challenge { /** * Used for unlockable challenges to check if they're unlocked. * @param data {@link GameData} The save data. - * @returns {@link boolean} Whether this challenge is unlocked. + * @returns `true` if this challenge is unlocked. */ isUnlocked(data: GameData): boolean { return this.conditions.every(f => f(data)); @@ -152,7 +187,7 @@ export abstract class Challenge { /** * Adds an unlock condition to this challenge. * @param condition {@link ChallengeCondition} The condition to add. - * @returns {@link Challenge} This challenge + * @returns This {@linkcode Challenge} */ condition(condition: ChallengeCondition): Challenge { this.conditions.push(condition); @@ -161,7 +196,7 @@ export abstract class Challenge { } /** - * @returns {@link string} The localised name of this challenge. + * @returns The localised name of this challenge. */ getName(): string { return i18next.t(`challenges:${this.geti18nKey()}.name`); @@ -169,10 +204,10 @@ export abstract class Challenge { /** * Returns the textual representation of a challenge's current value. - * @param overrideValue {@link integer} The value to check for. If undefined, gets the current value. - * @returns {@link string} The localised name for the current value. + * @param overrideValue The value to check for. If `undefined`, gets the current value. + * @returns The localised name for the current value. */ - getValue(overrideValue?: integer): string { + getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -181,10 +216,10 @@ export abstract class Challenge { /** * Returns the description of a challenge's current value. - * @param overrideValue {@link integer} The value to check for. If undefined, gets the current value. - * @returns {@link string} The localised description for the current value. + * @param overrideValue The value to check for. If `undefined`, gets the current value. + * @returns The localised description for the current value. */ - getDescription(overrideValue?: integer): string { + getDescription(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -193,7 +228,7 @@ export abstract class Challenge { /** * Increase the value of the challenge - * @returns {@link boolean} Returns true if the value changed + * @returns Returns `true` if the value changed */ increaseValue(): boolean { if (this.value < this.maxValue) { @@ -205,7 +240,7 @@ export abstract class Challenge { /** * Decrease the value of the challenge - * @returns {@link boolean} Returns true if the value changed + * @returns Returns `true` if the value changed */ decreaseValue(): boolean { if (this.value > 0) { @@ -216,7 +251,7 @@ export abstract class Challenge { } /** - * Whether to allow choosing this challenge's severity. + * @returns Whether to allow choosing this challenge's severity. */ hasSeverity(): boolean { return this.value !== 0 && this.maxSeverity > 0; @@ -224,7 +259,7 @@ export abstract class Challenge { /** * Decrease the severity of the challenge - * @returns {@link boolean} Returns true if the value changed + * @returns Returns `true` if the value changed */ decreaseSeverity(): boolean { if (this.severity > 0) { @@ -236,7 +271,7 @@ export abstract class Challenge { /** * Increase the severity of the challenge - * @returns {@link boolean} Returns true if the value changed + * @returns Returns `true` if the value changed */ increaseSeverity(): boolean { if (this.severity < this.maxSeverity) { @@ -248,160 +283,220 @@ export abstract class Challenge { /** * Gets the "difficulty" value of this challenge. - * @returns {@link integer} The difficulty value. + * @returns The difficulty value. */ - getDifficulty(): integer { + getDifficulty(): number { return this.value; } /** * Gets the minimum difficulty added by this challenge. - * @returns {@link integer} The difficulty value. + * @returns The minimum difficulty value. */ - getMinDifficulty(): integer { + getMinDifficulty(): number { return 0; } /** * Clones a challenge, either from another challenge or json. Chainable. * @param source The source challenge or json. - * @returns This challenge. + * @returns This {@linkcode Challenge}. */ static loadChallenge(source: Challenge | any): Challenge { throw new Error("Method not implemented! Use derived class"); } /** - * An apply function for STARTER_CHOICE challenges. Derived classes should alter this. - * @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. - * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. - * @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon. - * @param soft {@link boolean} If true, allow it if it could become a valid pokemon. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.STARTER_CHOICE} challenges. Derived classes should alter this. + * @param pokemon {@linkcode PokemonSpecies} The pokemon to check the validity of. + * @param valid A {@linkcode BooleanHolder}, the value gets set to false if the pokemon isn't allowed. + * @param dexAttr {@linkcode DexAttrProps} The dex attributes of the pokemon. + * @param soft If `true`, allow it if it could become a valid pokemon. + * @returns `true` if this function did anything. */ - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { + applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { return false; } /** - * An apply function for STARTER_POINTS challenges. Derived classes should alter this. - * @param points {@link Utils.NumberHolder} The amount of points you have available. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.STARTER_POINTS} challenges. Derived classes should alter this. + * @param points {@linkcode NumberHolder} The amount of points you have available. + * @returns `true` if this function did anything. */ - applyStarterPoints(points: Utils.NumberHolder): boolean { + applyStarterPoints(points: NumberHolder): boolean { return false; } /** - * An apply function for STARTER_COST challenges. Derived classes should alter this. - * @param species {@link Species} The pokemon to change the cost of. - * @param cost {@link Utils.NumberHolder} The cost of the starter. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.STARTER_COST} challenges. Derived classes should alter this. + * @param species {@linkcode Species} The pokemon to change the cost of. + * @param cost {@link NumberHolder} The cost of the starter. + * @returns `true` if this function did anything. */ - applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { + applyStarterCost(species: Species, cost: NumberHolder): boolean { return false; } /** - * An apply function for STARTER_MODIFY challenges. Derived classes should alter this. - * @param pokemon {@link Pokemon} The starter pokemon to modify. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.STARTER_MODIFY} challenges. Derived classes should alter this. + * @param pokemon {@linkcode Pokemon} The starter pokemon to modify. + * @returns `true` if this function did anything. */ applyStarterModify(pokemon: Pokemon): boolean { return false; } /** - * An apply function for POKEMON_IN_BATTLE challenges. Derived classes should alter this. - * @param pokemon {@link Pokemon} The pokemon to check the validity of. - * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.POKEMON_IN_BATTLE} challenges. Derived classes should alter this. + * @param pokemon {@linkcode Pokemon} The pokemon to check the validity of. + * @param valid {@link BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. + * @returns `true` if this function did anything. */ - applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { + applyPokemonInBattle(pokemon: Pokemon, valid: BooleanHolder): boolean { return false; } /** - * An apply function for FIXED_BATTLE challenges. Derived classes should alter this. - * @param waveIndex {@link Number} The current wave index. - * @param battleConfig {@link FixedBattleConfig} The battle config to modify. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.FIXED_BATTLE} challenges. Derived classes should alter this. + * @param waveIndex The current wave index. + * @param battleConfig {@linkcode FixedBattleConfig} The battle config to modify. + * @returns `true` if this function did anything. */ - applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { + applyFixedBattle(waveIndex: number, battleConfig: FixedBattleConfig): boolean { return false; } /** - * An apply function for TYPE_EFFECTIVENESS challenges. Derived classes should alter this. - * @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. - * @returns Whether this function did anything. + * An apply function for {@linkcode ChallengeType.TYPE_EFFECTIVENESS} challenges. Derived classes should alter this. + * @param effectiveness {@linkcode NumberHolder} The current effectiveness of the move. + * @returns `true` if this function did anything. */ - applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean { + applyTypeEffectiveness(effectiveness: NumberHolder): boolean { return false; } /** - * An apply function for AI_LEVEL challenges. Derived classes should alter this. - * @param level {@link Utils.IntegerHolder} The generated level. - * @param levelCap {@link Number} The current level cap. - * @param isTrainer {@link Boolean} Whether this is a trainer pokemon. - * @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.AI_LEVEL} challenges. Derived classes should alter this. + * @param level {@linkcode NumberHolder} The generated level. + * @param levelCap The current level cap. + * @param isTrainer Whether this is a trainer pokemon. + * @param isBoss Whether this is a non-trainer boss pokemon. + * @returns `true` if this function did anything. */ - applyLevelChange(level: Utils.IntegerHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean { + applyLevelChange(level: NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean { return false; } /** - * An apply function for AI_MOVE_SLOTS challenges. Derived classes should alter this. - * @param pokemon {@link Pokemon} The pokemon that is being considered. - * @param moveSlots {@link Utils.IntegerHolder} The amount of move slots. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.AI_MOVE_SLOTS} challenges. Derived classes should alter this. + * @param pokemon {@linkcode Pokemon} The pokemon that is being considered. + * @param moveSlots {@linkcode NumberHolder} The amount of move slots. + * @returns `true` if this function did anything. */ - applyMoveSlot(pokemon: Pokemon, moveSlots: Utils.IntegerHolder): boolean { + applyMoveSlot(pokemon: Pokemon, moveSlots: NumberHolder): boolean { return false; } /** - * An apply function for PASSIVE_ACCESS challenges. Derived classes should alter this. - * @param pokemon {@link Pokemon} The pokemon to change. - * @param hasPassive {@link Utils.BooleanHolder} Whether it should have its passive. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.PASSIVE_ACCESS} challenges. Derived classes should alter this. + * @param pokemon {@linkcode Pokemon} The pokemon to change. + * @param hasPassive Whether it should have its passive. + * @returns `true` if this function did anything. */ - applyPassiveAccess(pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean { + applyPassiveAccess(pokemon: Pokemon, hasPassive: BooleanHolder): boolean { return false; } /** - * An apply function for GAME_MODE_MODIFY challenges. Derived classes should alter this. - * @param gameMode {@link GameMode} The current game mode. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.GAME_MODE_MODIFY} challenges. Derived classes should alter this. + * @param gameMode {@linkcode GameMode} The current game mode. + * @returns `true` if this function did anything. */ applyGameModeModify(gameMode: GameMode): boolean { return false; } /** - * An apply function for MOVE_ACCESS. Derived classes should alter this. - * @param pokemon {@link Pokemon} What pokemon would learn the move. - * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. - * @param move {@link Moves} The move in question. - * @param level {@link Utils.IntegerHolder} The level threshold for access. - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.MOVE_ACCESS} challenges. Derived classes should alter this. + * @param pokemon {@linkcode Pokemon} What pokemon would learn the move. + * @param moveSource {@linkcode MoveSourceType} What source the pokemon would get the move from. + * @param move {@linkcode Moves} The move in question. + * @param level {@linkcode NumberHolder} The level threshold for access. + * @returns `true` if this function did anything. */ - applyMoveAccessLevel(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.IntegerHolder): boolean { + applyMoveAccessLevel(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: NumberHolder): boolean { return false; } /** - * An apply function for MOVE_WEIGHT. Derived classes should alter this. - * @param pokemon {@link Pokemon} What pokemon would learn the move. - * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. - * @param move {@link Moves} The move in question. - * @param weight {@link Utils.IntegerHolder} The base weight of the move - * @returns {@link boolean} Whether this function did anything. + * An apply function for {@linkcode ChallengeType.MOVE_WEIGHT} challenges. Derived classes should alter this. + * @param pokemon {@linkcode Pokemon} What pokemon would learn the move. + * @param moveSource {@linkcode MoveSourceType} What source the pokemon would get the move from. + * @param move {@linkcode Moves} The move in question. + * @param weight {@linkcode NumberHolder} The base weight of the move + * @returns `true` if this function did anything. */ - applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.IntegerHolder): boolean { + applyMoveWeight(pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: NumberHolder): boolean { + return false; + } + + /** + * An apply function for {@linkcode ChallengeType.NO_HEAL_PHASE} challenges. Derived classes should alter this. + * @param applyHealPhase {@link BooleanHolder} Whether it should apply the heal phase. + * @returns `true` if this function did anything. + */ + applyNoHealPhase(applyHealPhase: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for {@linkcode ChallengeType.SHOP_ITEM_BLACKLIST} challenges. Derived classes should alter this. + * @param shopItem {@linkcode ModifierTypeOption} The shop item. + * @param isValid {@linkcode BooleanHolder} Whether this item is valid for this challenge. + * @returns `true` if this function did anything. + */ + applyShopItemBlacklist(shopItem: ModifierTypeOption, isValid: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for {@linkcode ChallengeType.RANDOM_ITEM_BLACKLIST} challenges. Derived classes should alter this. + * @param randomItem {@linkcode ModifierTypeOption} The random item. + * @param isValid {@linkcode BooleanHolder} Whether this item is valid for this challenge. + * @returns `true` if this function did anything. + */ + applyRandomItemBlacklist(randomItem: ModifierTypeOption, isValid: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for {@linkcode ChallengeType.ADD_POKEMON_TO_PARTY} challenges. Derived classes should alter this. + * @param pokemon {@linkcode EnemyPokemon} The pokemon cought. + * @param waveIndex Current wave index. + * @param canAddToParty {@linkcode BooleanHolder} Whether this pokemon can be added to the party. + * @returns `true` if this function did anything. + */ + applyAddPokemonToParty(pokemon: EnemyPokemon, waveIndex: number, canAddToParty: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for {@linkcode ChallengeType.MOVE_BLACKLIST} challenges. Derived classes should alter this. + * @param move {@linkcode PokemonMove} The move being attempted. + * @param moveCanBeUsed {@linkcode BooleanHolder} Whether this move can be used. + * @returns `true` if this function did anything. + */ + applyMoveBlacklist(move: PokemonMove, moveCanBeUsed: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for {@linkcode ChallengeType.PREVENT_REVIVE} challenges. Derived classes should alter this. + * @param pokemon The {@linkcode PlayerPokemon} being revived + * @param canBeRevived {@linkcode BooleanHolder} Whether the pokemon can be revived. + * @returns `true` if this function did anything. + */ + applyRevivePrevention(pokemon: PlayerPokemon, canBeRevived: BooleanHolder): boolean { return false; } } @@ -416,7 +511,7 @@ export class SingleGenerationChallenge extends Challenge { super(Challenges.SINGLE_GENERATION, 9); } - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { + override applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { const generations = [pokemon.generation]; if (soft) { const speciesToCheck = [pokemon.speciesId]; @@ -438,7 +533,7 @@ export class SingleGenerationChallenge extends Challenge { return false; } - applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { + override applyPokemonInBattle(pokemon: Pokemon, valid: BooleanHolder): boolean { const baseGeneration = pokemon.species.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.species.speciesId).generation; const fusionGeneration = pokemon.isFusion() ? pokemon.fusionSpecies?.speciesId === Species.VICTINI ? 5 : getPokemonSpecies(pokemon.fusionSpecies!.speciesId).generation : 0; // TODO: is the bang on fusionSpecies correct? if (pokemon.isPlayer() && (baseGeneration !== this.value || (pokemon.isFusion() && fusionGeneration !== this.value))) { @@ -448,23 +543,23 @@ export class SingleGenerationChallenge extends Challenge { return false; } - applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { + override applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { let trainerTypes: TrainerType[] = []; switch (waveIndex) { case 182: - trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; + trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; break; case 184: trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; break; case 186: - trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE]), TrainerType.LARRY_ELITE ]; + trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, randSeedItem([TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE]), TrainerType.LARRY_ELITE ]; break; case 188: trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; break; case 190: - trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; + trainerTypes = [ TrainerType.BLUE, randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; break; } if (trainerTypes.length === 0) { @@ -475,19 +570,16 @@ export class SingleGenerationChallenge extends Challenge { } } - /** - * @overrides - */ - getDifficulty(): number { + override getDifficulty(): number { return this.value > 0 ? 1 : 0; } /** * Returns the textual representation of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised name for the current value. + * @param overrideValue The value to check for. If `undefined`, gets the current value. + * @returns The localised name for the current value. */ - getValue(overrideValue?: integer): string { + override getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -499,10 +591,10 @@ export class SingleGenerationChallenge extends Challenge { /** * Returns the description of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised description for the current value. + * @param overrideValue The value to check for. If `undefined`, gets the current value. + * @returns The localised description for the current value. */ - getDescription(overrideValue?: integer): string { + override getDescription(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -513,7 +605,7 @@ export class SingleGenerationChallenge extends Challenge { } - static loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge { + static override loadChallenge(source: SingleGenerationChallenge | any): SingleGenerationChallenge { const newChallenge = new SingleGenerationChallenge(); newChallenge.value = source.value; newChallenge.severity = source.severity; @@ -543,7 +635,7 @@ export class SingleTypeChallenge extends Challenge { super(Challenges.SINGLE_TYPE, 18); } - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { + override applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean { const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex); const types = [speciesForm.type1, speciesForm.type2]; if (soft) { @@ -574,7 +666,7 @@ export class SingleTypeChallenge extends Challenge { return false; } - applyPokemonInBattle(pokemon: Pokemon, valid: Utils.BooleanHolder): boolean { + override applyPokemonInBattle(pokemon: Pokemon, valid: BooleanHolder): boolean { if (pokemon.isPlayer() && !pokemon.isOfType(this.value - 1, false, false, true) && !SingleTypeChallenge.TYPE_OVERRIDES.some(o => o.type === (this.value - 1) && (pokemon.isFusion() && o.fusion ? pokemon.fusionSpecies! : pokemon.species).speciesId === o.species)) { // TODO: is the bang on fusionSpecies correct? valid.value = false; @@ -583,19 +675,16 @@ export class SingleTypeChallenge extends Challenge { return false; } - /** - * @overrides - */ - getDifficulty(): number { + override getDifficulty(): number { return this.value > 0 ? 1 : 0; } /** * Returns the textual representation of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised name for the current value. + * @param overrideValue The value to check for. If `undefined`, gets the current value. + * @returns The localised name for the current value. */ - getValue(overrideValue?: integer): string { + override getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -604,10 +693,10 @@ export class SingleTypeChallenge extends Challenge { /** * Returns the description of a challenge's current value. - * @param {value} overrideValue The value to check for. If undefined, gets the current value. - * @returns {string} The localised description for the current value. + * @param overrideValue The value to check for. If `undefined`, gets the current value. + * @returns The localised description for the current value. */ - getDescription(overrideValue?: integer): string { + override getDescription(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } @@ -618,7 +707,7 @@ export class SingleTypeChallenge extends Challenge { return this.value === 0 ? defaultDesc : typeDesc; } - static loadChallenge(source: SingleTypeChallenge | any): SingleTypeChallenge { + static override loadChallenge(source: SingleTypeChallenge | any): SingleTypeChallenge { const newChallenge = new SingleTypeChallenge(); newChallenge.value = source.value; newChallenge.severity = source.severity; @@ -634,7 +723,7 @@ export class FreshStartChallenge extends Challenge { super(Challenges.FRESH_START, 1); } - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { + override applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean { if (!defaultStarterSpecies.includes(pokemon.speciesId)) { valid.value = false; return true; @@ -642,7 +731,7 @@ export class FreshStartChallenge extends Challenge { return false; } - applyStarterCost(species: Species, cost: Utils.NumberHolder): boolean { + override applyStarterCost(species: Species, cost: NumberHolder): boolean { if (defaultStarterSpecies.includes(species)) { cost.value = speciesStarters[species]; return true; @@ -650,7 +739,7 @@ export class FreshStartChallenge extends Challenge { return false; } - applyStarterModify(pokemon: Pokemon): boolean { + override applyStarterModify(pokemon: Pokemon): boolean { pokemon.abilityIndex = 0; // Always base ability, not hidden ability pokemon.passive = false; // Passive isn't unlocked pokemon.nature = Nature.HARDY; // Neutral nature @@ -667,7 +756,7 @@ export class FreshStartChallenge extends Challenge { return 0; } - static loadChallenge(source: FreshStartChallenge | any): FreshStartChallenge { + static override loadChallenge(source: FreshStartChallenge | any): FreshStartChallenge { const newChallenge = new FreshStartChallenge(); newChallenge.value = source.value; newChallenge.severity = source.severity; @@ -683,7 +772,7 @@ export class InverseBattleChallenge extends Challenge { super(Challenges.INVERSE_BATTLE, 1); } - static loadChallenge(source: InverseBattleChallenge | any): InverseBattleChallenge { + static override loadChallenge(source: InverseBattleChallenge | any): InverseBattleChallenge { const newChallenge = new InverseBattleChallenge(); newChallenge.value = source.value; newChallenge.severity = source.severity; @@ -694,7 +783,7 @@ export class InverseBattleChallenge extends Challenge { return 0; } - applyTypeEffectiveness(effectiveness: Utils.NumberHolder): boolean { + override applyTypeEffectiveness(effectiveness: NumberHolder): boolean { if (effectiveness.value < 1) { effectiveness.value = 2; return true; @@ -715,17 +804,14 @@ export class LowerStarterMaxCostChallenge extends Challenge { super(Challenges.LOWER_MAX_STARTER_COST, 9); } - /** - * @override - */ - getValue(overrideValue?: integer): string { + override getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } return (DEFAULT_PARTY_MAX_COST - overrideValue).toString(); } - applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder): boolean { + override applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder): boolean { if (speciesStarters[pokemon.speciesId] > DEFAULT_PARTY_MAX_COST - this.value) { valid.value = false; return true; @@ -733,7 +819,7 @@ export class LowerStarterMaxCostChallenge extends Challenge { return false; } - static loadChallenge(source: LowerStarterMaxCostChallenge | any): LowerStarterMaxCostChallenge { + static override loadChallenge(source: LowerStarterMaxCostChallenge | any): LowerStarterMaxCostChallenge { const newChallenge = new LowerStarterMaxCostChallenge(); newChallenge.value = source.value; newChallenge.severity = source.severity; @@ -749,22 +835,19 @@ export class LowerStarterPointsChallenge extends Challenge { super(Challenges.LOWER_STARTER_POINTS, 9); } - /** - * @override - */ - getValue(overrideValue?: integer): string { + override getValue(overrideValue?: number): string { if (overrideValue === undefined) { overrideValue = this.value; } return (DEFAULT_PARTY_MAX_COST - overrideValue).toString(); } - applyStarterPoints(points: Utils.NumberHolder): boolean { + override applyStarterPoints(points: NumberHolder): boolean { points.value -= this.value; return true; } - static loadChallenge(source: LowerStarterPointsChallenge | any): LowerStarterPointsChallenge { + static override loadChallenge(source: LowerStarterPointsChallenge | any): LowerStarterPointsChallenge { const newChallenge = new LowerStarterPointsChallenge(); newChallenge.value = source.value; newChallenge.severity = source.severity; @@ -772,126 +855,275 @@ export class LowerStarterPointsChallenge extends Challenge { } } +/** Challenge that removes the {@linkcode PartyHealPhase} that occurs after every 10 waves */ +export class NoAutomaticHealChallenge extends Challenge { + constructor() { + super(Challenges.NO_AUTO_HEAL, 1); + } + + override applyNoHealPhase(applyHealPhase: BooleanHolder): boolean { + applyHealPhase.value = false; + return true; + } + + static override loadChallenge(source: NoAutomaticHealChallenge | any): NoAutomaticHealChallenge { + const newChallenge = new NoAutomaticHealChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** Challenge that removes the ability to revive fallen pokemon */ +export class HardcoreChallenge extends Challenge { + private itemBlackList = ["modifierType:ModifierType.REVIVE", "modifierType:ModifierType.MAX_REVIVE", "modifierType:ModifierType.SACRED_ASH", "modifierType:ModifierType.REVIVER_SEED"]; + + constructor() { + super(Challenges.HARDCORE, 1); + } + + override applyRandomItemBlacklist(randomItem: ModifierTypeOption, isValid: BooleanHolder): boolean { + isValid.value = !this.itemBlackList.includes(randomItem.type.localeKey); + return true; + } + + override applyShopItemBlacklist(shopItem: ModifierTypeOption, isValid: BooleanHolder): boolean { + isValid.value = !this.itemBlackList.includes(shopItem.type.localeKey); + return true; + } + + override applyMoveBlacklist(move: PokemonMove, moveCanBeUsed: BooleanHolder): boolean { + const moveBlacklist = [Moves.REVIVAL_BLESSING]; + moveCanBeUsed.value = !moveBlacklist.includes(move.moveId); + return true; + } + + override applyRevivePrevention(pokemon: PlayerPokemon, canBeRevived: BooleanHolder): boolean { + canBeRevived.value = false; + return true; + } + + static override loadChallenge(source: HardcoreChallenge | any): HardcoreChallenge { + const newChallenge = new HardcoreChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** Challenge that prevents choosing legendary, sub-legendary or mythical pokemon as starters */ +export class NoStarterLegendsChallenge extends Challenge { + constructor() { + super(Challenges.NO_LEGENDS, 1); + } + + override applyStarterChoice(pokemon: PokemonSpecies, valid: BooleanHolder, dexAttr: DexAttrProps, soft?: boolean): boolean { + valid.value = !pokemon.legendary && !pokemon.mythical && !pokemon.subLegendary; + return true; + } + static override loadChallenge(source: NoStarterLegendsChallenge | any): NoStarterLegendsChallenge { + const newChallenge = new NoStarterLegendsChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + +/** Challenge that only allows the first pokemon of every 10 waves to be caught */ +export class LimitedCatchChallenge extends Challenge { + constructor() { + super(Challenges.LIMITED_CATCH, 1); + } + + override applyAddPokemonToParty(pokemon: EnemyPokemon, waveIndex: number, canAddToParty: BooleanHolder): boolean { + if (!(waveIndex % 10 === 1) && !(pokemon.scene.lastMysteryEncounter && (waveIndex % 10 === 2))) { + canAddToParty.value = false; + } + return true; + } + + static override loadChallenge(source: LimitedCatchChallenge | any): LimitedCatchChallenge { + const newChallenge = new LimitedCatchChallenge(); + newChallenge.value = source.value; + newChallenge.severity = source.severity; + return newChallenge; + } +} + /** * Apply all challenges that modify starter choice. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_CHOICE - * @param pokemon {@link PokemonSpecies} The pokemon to check the validity of. - * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. - * @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon. - * @param soft {@link boolean} If true, allow it if it could become a valid pokemon. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.STARTER_CHOICE} + * @param pokemon The {@linkcode PokemonSpecies} to check the validity of. + * @param valid {@link BooleanHolder} `false` if the pokemon isn't allowed. + * @param dexAttr {@linkcode DexAttrProps} The dex attributes of the pokemon. + * @param soft If `true`, allow it if it could become a valid pokemon. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: BooleanHolder, dexAttr: DexAttrProps, soft: boolean): boolean; /** * Apply all challenges that modify available total starter points. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_POINTS - * @param points {@link Utils.NumberHolder} The amount of points you have available. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.STARTER_POINTS} + * @param points {@link NumberHolder} The amount of points you have available. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_POINTS, points: Utils.NumberHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_POINTS, points: NumberHolder): boolean; /** * Apply all challenges that modify the cost of a starter. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_COST - * @param species {@link Species} The pokemon to change the cost of. - * @param points {@link Utils.NumberHolder} The cost of the pokemon. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.STARTER_COST} + * @param species The pokemon {@linkcode Species} to change the cost of. + * @param points {@linkcode NumberHolder} The cost of the pokemon. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_COST, species: Species, cost: Utils.NumberHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_COST, species: Species, cost: NumberHolder): boolean; /** * Apply all challenges that modify a starter after selection. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.STARTER_MODIFY - * @param pokemon {@link Pokemon} The starter pokemon to modify. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.STARTER_MODIFY} + * @param pokemon The starter {@linkcode Pokemon} to modify. + * @returns `true` if any challenge was successfully applied. */ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_MODIFY, pokemon: Pokemon): boolean; /** * Apply all challenges that what pokemon you can have in battle. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.POKEMON_IN_BATTLE - * @param pokemon {@link Pokemon} The pokemon to check the validity of. - * @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.POKEMON_IN_BATTLE} + * @param pokemon The {@linkcode Pokemon} Tcheck the validity of. + * @param valid {@link BooleanHolder} `false` if the pokemon isn't allowed. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.POKEMON_IN_BATTLE, pokemon: Pokemon, valid: Utils.BooleanHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.POKEMON_IN_BATTLE, pokemon: Pokemon, valid: BooleanHolder): boolean; /** * Apply all challenges that modify what fixed battles there are. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.FIXED_BATTLES - * @param waveIndex {@link Number} The current wave index. - * @param battleConfig {@link FixedBattleConfig} The battle config to modify. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.FIXED_BATTLES} + * @param waveIndex The current wave index. + * @param battleConfig The {@link FixedBattleConfig} to modify. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: Number, battleConfig: FixedBattleConfig): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.FIXED_BATTLES, waveIndex: number, battleConfig: FixedBattleConfig): boolean; /** * Apply all challenges that modify type effectiveness. - * @param gameMode {@linkcode GameMode} The current gameMode - * @param challengeType {@linkcode ChallengeType} ChallengeType.TYPE_EFFECTIVENESS - * @param effectiveness {@linkcode Utils.NumberHolder} The current effectiveness of the move. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.TYPE_EFFECTIVENESS} + * @param effectiveness {@linkcode NumberHolder} The current effectiveness of the move. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: Utils.NumberHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.TYPE_EFFECTIVENESS, effectiveness: NumberHolder): boolean; /** * Apply all challenges that modify what level AI are. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.AI_LEVEL - * @param level {@link Utils.IntegerHolder} The generated level of the pokemon. - * @param levelCap {@link Number} The maximum level cap for the current wave. - * @param isTrainer {@link Boolean} Whether this is a trainer pokemon. - * @param isBoss {@link Boolean} Whether this is a non-trainer boss pokemon. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.AI_LEVEL} + * @param level {@link NumberHolder} The generated level of the pokemon. + * @param levelCap The maximum level cap for the current wave. + * @param isTrainer Whether this is a trainer pokemon. + * @param isBoss Whether this is a non-trainer boss pokemon. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_LEVEL, level: Utils.IntegerHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_LEVEL, level: NumberHolder, levelCap: number, isTrainer: boolean, isBoss: boolean): boolean; /** * Apply all challenges that modify how many move slots the AI has. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.AI_MOVE_SLOTS - * @param pokemon {@link Pokemon} The pokemon being considered. - * @param moveSlots {@link Utils.IntegerHolder} The amount of move slots. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.AI_MOVE_SLOTS} + * @param pokemon The {@linkcode Pokemon} being considered. + * @param moveSlots {@linkcode NumberHolder} The amount of move slots. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_MOVE_SLOTS, pokemon: Pokemon, moveSlots: Utils.IntegerHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.AI_MOVE_SLOTS, pokemon: Pokemon, moveSlots: NumberHolder): boolean; /** * Apply all challenges that modify whether a pokemon has its passive. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.PASSIVE_ACCESS - * @param pokemon {@link Pokemon} The pokemon to modify. - * @param hasPassive {@link Utils.BooleanHolder} Whether it has its passive. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.PASSIVE_ACCESS} + * @param pokemon The {@linkcode Pokemon} to modify. + * @param hasPassive {@linkcode BooleanHolder} Whether it has its passive. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.PASSIVE_ACCESS, pokemon: Pokemon, hasPassive: Utils.BooleanHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.PASSIVE_ACCESS, pokemon: Pokemon, hasPassive: BooleanHolder): boolean; /** * Apply all challenges that modify the game modes settings. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.GAME_MODE_MODIFY - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.GAME_MODE_MODIFY} + * @returns `true` if any challenge was successfully applied. */ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.GAME_MODE_MODIFY): boolean; /** * Apply all challenges that modify what level a pokemon can access a move. - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.MOVE_ACCESS - * @param pokemon {@link Pokemon} What pokemon would learn the move. - * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. - * @param move {@link Moves} The move in question. - * @param level {@link Utils.IntegerHolder} The level threshold for access. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.MOVE_ACCESS} + * @param pokemon What {@linkcode Pokemon} would learn the move. + * @param moveSource {@linkcode MoveSourceType} What source the pokemon would get the move from. + * @param move {@linkcode Moves} The move in question. + * @param level {@linkcode NumberHolder} The level threshold for access. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_ACCESS, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: Utils.IntegerHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_ACCESS, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, level: NumberHolder): boolean; /** * Apply all challenges that modify what weight a pokemon gives to move generation - * @param gameMode {@link GameMode} The current gameMode - * @param challengeType {@link ChallengeType} ChallengeType.MOVE_WEIGHT - * @param pokemon {@link Pokemon} What pokemon would learn the move. - * @param moveSource {@link MoveSourceType} What source the pokemon would get the move from. - * @param move {@link Moves} The move in question. - * @param weight {@link Utils.IntegerHolder} The weight of the move. - * @returns True if any challenge was successfully applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.MOVE_WEIGHT} + * @param pokemon What {@linkcode Pokemon} would learn the move. + * @param moveSource {@linkcode MoveSourceType} What source the pokemon would get the move from. + * @param move {@linkcode Moves} The move in question. + * @param weight {@linkcode NumberHolder} The weight of the move. + * @returns `true` if any challenge was successfully applied. */ -export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: Utils.IntegerHolder): boolean; +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_WEIGHT, pokemon: Pokemon, moveSource: MoveSourceType, move: Moves, weight: NumberHolder): boolean; +/** + * Apply all challenges that modify if the heal phase should be applied. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.NO_HEAL_PHASE} + * @param applyHealPhase {@linkcode BooleanHolder} Whether it should apply the heal phase. + * @returns `true` if any challenge was successfully applied. + */ +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.NO_HEAL_PHASE, applyHealPhase: BooleanHolder): boolean; +/** + * Apply all challenges that modify if this shop item can be bought. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.SHOP_ITEM_BLACKLIST} + * @param shopItem {@linkcode ModifierTypeOption} The shop item. + * @param isValid {@linkcode BooleanHolder} Whether this item is valid for this challenge. + * @returns `true` if any challenge was successfully applied. + */ +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.SHOP_ITEM_BLACKLIST, shopItem: ModifierTypeOption, isValid: BooleanHolder): boolean; +/** + * Apply all challenges that modify if this random item can be generated. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.RANDOM_ITEM_BLACKLIS} + * @param randomItem {@linkcode ModifierTypeOption} The random item. + * @param isValid {@linkcode BooleanHolder} Whether this item is valid for this challenge. + * @returns `true` if any challenge was successfully applied. + */ +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.RANDOM_ITEM_BLACKLIST, randomItem: ModifierTypeOption, isValid: BooleanHolder): boolean; +/** + * Apply all challenges that modify if that pokemon can be added to the party. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.ADD_POKEMON_TO_PARTY} + * @param pokemon {@linkcode EnemyPokemon} The pokemon cought. + * @param waveIndex Current wave index. + * @param canBeAddToParty {@linkcode BooleanHolder} Whether this pokemon can be added to the party. + * @returns `true` if any challenge was successfully applied. + */ +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.ADD_POKEMON_TO_PARTY, pokemon: EnemyPokemon, waveIndex: number, canBeAddToParty: BooleanHolder): boolean; +/** + * Apply all challenges that modify if that move can be used. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.MOVE_BLACKLIST} + * @param move {@linkcode PokemonMove} The move being attempted. + * @param moveCanBeUsed {@linkcode BooleanHolder} Whether this move can be used. + * @returns `true` if any challenge was successfully applied. + */ +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.MOVE_BLACKLIST, move: PokemonMove, moveCanBeUsed: BooleanHolder): boolean; +/** + * Apply all challenges that modify if pokemon can be revived. + * @param gameMode The current {@linkcode GameMode} + * @param challengeType {@linkcode ChallengeType.PREVENT_REVIVE} + * @param pokemon The {@linkcode PlayerPokemon} being revived. + * @param canBeRevived {@linkcode BooleanHolder} Whether the pokemon can be revived. + * @returns `true` if any challenge was successfully applied. + */ +export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.PREVENT_REVIVE, pokemon: PlayerPokemon, canBeRevived: BooleanHolder): boolean; export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType, ...args: any[]): boolean { let ret = false; gameMode.challenges.forEach(c => { @@ -936,6 +1168,24 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType case ChallengeType.MOVE_WEIGHT: ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); break; + case ChallengeType.NO_HEAL_PHASE: + ret ||= c.applyNoHealPhase(args[0]); + break; + case ChallengeType.SHOP_ITEM_BLACKLIST: + ret ||= c.applyShopItemBlacklist(args[0], args[1]); + break; + case ChallengeType.RANDOM_ITEM_BLACKLIST: + ret ||= c.applyShopItemBlacklist(args[0], args[1]); + break; + case ChallengeType.ADD_POKEMON_TO_PARTY: + ret ||= c.applyAddPokemonToParty(args[0], args[1], args[2]); + break; + case ChallengeType.MOVE_BLACKLIST: + ret ||= c.applyMoveBlacklist(args[0], args[1]); + break; + case ChallengeType.PREVENT_REVIVE: + ret ||= c.applyRevivePrevention(args[0], args[1]); + break; } } }); @@ -961,6 +1211,14 @@ export function copyChallenge(source: Challenge | any): Challenge { return FreshStartChallenge.loadChallenge(source); case Challenges.INVERSE_BATTLE: return InverseBattleChallenge.loadChallenge(source); + case Challenges.NO_AUTO_HEAL: + return NoAutomaticHealChallenge.loadChallenge(source); + case Challenges.HARDCORE: + return HardcoreChallenge.loadChallenge(source); + case Challenges.NO_LEGENDS: + return NoStarterLegendsChallenge.loadChallenge(source); + case Challenges.LIMITED_CATCH: + return LimitedCatchChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } @@ -973,5 +1231,9 @@ export function initChallenges() { new SingleTypeChallenge(), new FreshStartChallenge(), new InverseBattleChallenge(), + new NoAutomaticHealChallenge(), + new HardcoreChallenge(), + new NoStarterLegendsChallenge(), + new LimitedCatchChallenge(), ); } diff --git a/src/enums/challenges.ts b/src/enums/challenges.ts index c4dc7460dfe..d11a87ec93d 100644 --- a/src/enums/challenges.ts +++ b/src/enums/challenges.ts @@ -5,4 +5,8 @@ export enum Challenges { LOWER_STARTER_POINTS, FRESH_START, INVERSE_BATTLE, + NO_AUTO_HEAL, + HARDCORE, + NO_LEGENDS, + LIMITED_CATCH, } diff --git a/src/locales/en/challenges.json b/src/locales/en/challenges.json index 7792755d626..02472e6d055 100644 --- a/src/locales/en/challenges.json +++ b/src/locales/en/challenges.json @@ -33,5 +33,29 @@ "desc": "Type matchups are reversed and no type is immune to any other type.\nDisables other challenges' achievements.", "value.0": "Off", "value.1": "On" + }, + "noAutoHeal": { + "name": "No Free Heal", + "desc": "Disables the free automatic healing which normally occurs after every 10th wave.", + "value.0": "Off", + "value.1": "On" + }, + "hardcore": { + "name": "Hardcore", + "desc": "You can no longer revive Pokemon that have fainted.", + "value.0": "Off", + "value.1": "On" + }, + "noLegends": { + "name": "No Starter Legends", + "desc": "You can't choose any legendary, sub-legendary or mythical Pokemon as a starter.", + "value.0": "Off", + "value.1": "On" + }, + "limitedCatch": { + "name": "Limited Catch", + "desc": "You can only add the first Pokemon of a biome to your current party.", + "value.0": "Off", + "value.1": "On" } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index a23a9c5ece2..0bafe0558c2 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1,32 +1,34 @@ -import * as Modifiers from "./modifier"; -import { MoneyMultiplierModifier } from "./modifier"; -import { allMoves, AttackMove, selfStatLowerMoves } from "../data/move"; -import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball"; -import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "../field/pokemon"; -import { EvolutionItem, pokemonEvolutions } from "../data/pokemon-evolutions"; -import { tmPoolTiers, tmSpecies } from "../data/tms"; -import { Type } from "../data/type"; -import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "../ui/party-ui-handler"; -import * as Utils from "../utils"; -import { getBerryEffectDescription, getBerryName } from "../data/berry"; -import { Unlockables } from "../system/unlockables"; -import { getStatusEffectDescriptor, StatusEffect } from "../data/status-effect"; -import { SpeciesFormKey } from "../data/pokemon-species"; -import BattleScene from "../battle-scene"; -import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "../system/voucher"; -import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "../data/pokemon-forms"; -import { ModifierTier } from "./modifier-tier"; +import BattleScene from "#app/battle-scene"; +import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { allMoves, AttackMove, selfStatLowerMoves } from "#app/data/move"; import { getNatureName, getNatureStatMultiplier, Nature } from "#app/data/nature"; -import i18next from "i18next"; -import { getModifierTierTextTint } from "#app/ui/text"; +import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "#app/data/pokeball"; +import { EvolutionItem, pokemonEvolutions } from "#app/data/pokemon-evolutions"; +import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormKey } from "#app/data/pokemon-species"; +import { getStatusEffectDescriptor, StatusEffect } from "#app/data/status-effect"; +import { tmPoolTiers, tmSpecies } from "#app/data/tms"; +import { Type } from "#app/data/type"; +import { getStatKey, PermanentStat, Stat, TEMP_BATTLE_STATS, TempBattleStat } from "#app/enums/stat"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { GameMode } from "#app/game-mode"; +import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; +import { Unlockables } from "#app/system/unlockables"; +import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system/voucher"; +import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; +import { getModifierTierTextTint } from "#app/ui/text"; +import * as Utils from "#app/utils"; import { Abilities } from "#enums/abilities"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { getPokemonNameWithAffix } from "#app/messages"; -import { PermanentStat, TEMP_BATTLE_STATS, TempBattleStat, Stat, getStatKey } from "#app/enums/stat"; +import i18next from "i18next"; +import * as Modifiers from "./modifier"; +import { MoneyMultiplierModifier } from "./modifier"; +import { ModifierTier } from "./modifier-tier"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -2139,8 +2141,12 @@ function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], r allowLuckUpgrades = allowLuckUpgrades ?? true; let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades); let r = 0; - while (existingOptions.length && ++r < retryCount && existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length) { + let isValidForChallenge = new Utils.BooleanHolder(true); + applyChallenges(party[0].scene.gameMode, ChallengeType.RANDOM_ITEM_BLACKLIST, candidate!, isValidForChallenge); + while (existingOptions.length && ++r < retryCount && existingOptions.filter(o => o.type.name === candidate?.type.name || o.type.group === candidate?.type.group).length || !isValidForChallenge.value) { candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate?.type.tier ?? tier, candidate?.upgradeCount, 0, allowLuckUpgrades); + isValidForChallenge = new Utils.BooleanHolder(true); + applyChallenges(party[0].scene.gameMode, ChallengeType.RANDOM_ITEM_BLACKLIST, candidate!, isValidForChallenge); } return candidate!; } @@ -2170,7 +2176,7 @@ export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], } } -export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer): ModifierTypeOption[] { +export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, baseCost: integer, gameMode: GameMode): ModifierTypeOption[] { if (!(waveIndex % 10)) { return []; } @@ -2204,7 +2210,11 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base new ModifierTypeOption(modifierTypes.SACRED_ASH(), 0, baseCost * 10) ] ]; - return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat(); + return options.slice(0, Math.ceil(Math.max(waveIndex + 10, 0) / 30)).flat().filter(x => { + const isValidForChallenge = new Utils.BooleanHolder(true); + applyChallenges(gameMode, ChallengeType.SHOP_ITEM_BLACKLIST, x, isValidForChallenge); + return isValidForChallenge.value; + }); } export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier { diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 53723526c14..9b76ece6b7a 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -1,21 +1,23 @@ -import BattleScene from "#app/battle-scene"; import { BattlerIndex } from "#app/battle"; -import { getPokeballCatchMultiplier, getPokeballAtlasKey, getPokeballTintColor, doPokeballBounceAnim } from "#app/data/pokeball"; +import BattleScene from "#app/battle-scene"; +import { SubstituteTag } from "#app/data/battler-tags"; +import { ChallengeType, applyChallenges } from "#app/data/challenge"; +import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor } from "#app/data/pokeball"; import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect"; import { PokeballType } from "#app/enums/pokeball"; import { StatusEffect } from "#app/enums/status-effect"; -import { addPokeballOpenParticles, addPokeballCaptureStars } from "#app/field/anims"; +import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims"; import { EnemyPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonHeldItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; -import { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; +import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { Mode } from "#app/ui/ui"; +import { BooleanHolder } from "#app/utils"; import i18next from "i18next"; import { PokemonPhase } from "./pokemon-phase"; import { VictoryPhase } from "./victory-phase"; -import { SubstituteTag } from "#app/data/battler-tags"; export class AttemptCapturePhase extends PokemonPhase { private pokeballType: PokeballType; @@ -249,6 +251,13 @@ export class AttemptCapturePhase extends PokemonPhase { }); }; Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => { + const challengeCanAddToParty = new BooleanHolder(true); + applyChallenges(this.scene.gameMode, ChallengeType.ADD_POKEMON_TO_PARTY, pokemon, this.scene.currentBattle.waveIndex, challengeCanAddToParty); + if (!challengeCanAddToParty.value) { + removePokemon(); + end(); + return; + } if (this.scene.getParty().length === 6) { const promptRelease = () => { this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 86e42acb26b..ef2a1219124 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -1,7 +1,8 @@ +import { BattleType, TurnCommand } from "#app/battle"; import BattleScene from "#app/battle-scene"; -import { TurnCommand, BattleType } from "#app/battle"; -import { TrappedTag, EncoreTag } from "#app/data/battler-tags"; -import { MoveTargetSet, getMoveTargets } from "#app/data/move"; +import { EncoreTag, TrappedTag } from "#app/data/battler-tags"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { getMoveTargets, MoveTargetSet } from "#app/data/move"; import { speciesStarters } from "#app/data/pokemon-species"; import { Abilities } from "#app/enums/abilities"; import { BattlerTagType } from "#app/enums/battler-tag-type"; @@ -12,10 +13,11 @@ import { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { Command } from "#app/ui/command-ui-handler"; import { Mode } from "#app/ui/ui"; +import { BooleanHolder } from "#app/utils"; +import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import i18next from "i18next"; import { FieldPhase } from "./field-phase"; import { SelectTargetPhase } from "./select-target-phase"; -import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; export class CommandPhase extends FieldPhase { protected fieldIndex: integer; @@ -84,6 +86,18 @@ export class CommandPhase extends FieldPhase { switch (command) { case Command.FIGHT: + // Check if move can be used in challenge + const isValidForChallenge = new BooleanHolder(true); + applyChallenges(this.scene.gameMode, ChallengeType.MOVE_BLACKLIST, playerPokemon.getMoveset()[cursor]!, isValidForChallenge); + if (!isValidForChallenge.value) { + const moveName = playerPokemon.getMoveset()[cursor]?.getName(); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("challenges:illegalMove", { moveName: moveName }), null, () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + }, null, true); + break; + } let useStruggle = false; if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean) || diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index e6ee11202df..37afd4e2e8e 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -1,5 +1,7 @@ import BattleScene from "#app/battle-scene"; -import * as Utils from "#app/utils"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { BooleanHolder, fixedInt } from "#app/utils"; import { BattlePhase } from "./battle-phase"; export class PartyHealPhase extends BattlePhase { @@ -14,21 +16,36 @@ export class PartyHealPhase extends BattlePhase { start() { super.start(); + const isHealPhaseActive = new BooleanHolder(true); + applyChallenges(this.scene.gameMode, ChallengeType.NO_HEAL_PHASE, isHealPhaseActive); + if (!isHealPhaseActive.value) { + this.scene.unshiftPhase(new SelectModifierPhase(this.scene)); + this.end(); + return; + } + const bgmPlaying = this.scene.isBgmPlaying(); if (bgmPlaying) { this.scene.fadeOutBgm(1000, false); } + + const canBeRevived = new BooleanHolder(true); this.scene.ui.fadeOut(1000).then(() => { for (const pokemon of this.scene.getParty()) { - pokemon.hp = pokemon.getMaxHp(); - pokemon.resetStatus(); - for (const move of pokemon.moveset) { - move!.ppUsed = 0; // TODO: is this bang correct? + applyChallenges(this.scene.gameMode, ChallengeType.PREVENT_REVIVE, pokemon, canBeRevived); + if (canBeRevived.value || !pokemon.isFainted()) { + pokemon.hp = pokemon.getMaxHp(); + pokemon.resetStatus(); + for (const move of pokemon.moveset) { + if (move) { + move.ppUsed = 0; + } + } + pokemon.updateInfo(true); } - pokemon.updateInfo(true); } const healSong = this.scene.playSoundWithoutBgm("heal"); - this.scene.time.delayedCall(Utils.fixedInt(healSong.totalDuration * 1000), () => { + this.scene.time.delayedCall(fixedInt(healSong.totalDuration * 1000), () => { healSong.destroy(); if (this.resumeBgm && bgmPlaying) { this.scene.playBgm(); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 39a0da1167f..de27127ee3d 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,16 +1,15 @@ import BattleScene from "#app/battle-scene"; -import { ModifierTier } from "#app/modifier/modifier-tier"; -import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type"; import { ExtraModifierModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; -import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; -import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; -import { Mode } from "#app/ui/ui"; -import i18next from "i18next"; -import * as Utils from "#app/utils"; -import { BattlePhase } from "./battle-phase"; +import { ModifierTier } from "#app/modifier/modifier-tier"; +import { CustomModifierSettings, FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import Overrides from "#app/overrides"; -import { CustomModifierSettings } from "#app/modifier/modifier-type"; +import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; +import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler"; +import { Mode } from "#app/ui/ui"; +import * as Utils from "#app/utils"; import { isNullOrUndefined } from "#app/utils"; +import i18next from "i18next"; +import { BattlePhase } from "./battle-phase"; export class SelectModifierPhase extends BattlePhase { private rerollCount: integer; @@ -128,7 +127,7 @@ export class SelectModifierPhase extends BattlePhase { } break; default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); + const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1), this.scene.gameMode); const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; if (shopOption.type) { modifierType = shopOption.type; diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index c9d3f195720..31492d969f2 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -1,20 +1,20 @@ -import BattleScene from "../battle-scene"; -import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption, TmModifierType } from "../modifier/modifier-type"; -import { getPokeballAtlasKey, PokeballType } from "../data/pokeball"; -import { addTextObject, getTextStyleOptions, getModifierTierTextTint, getTextColor, TextStyle } from "./text"; -import AwaitableUiHandler from "./awaitable-ui-handler"; -import { Mode } from "./ui"; -import { LockModifierTiersModifier, PokemonHeldItemModifier, HealShopCostModifier } from "../modifier/modifier"; -import { handleTutorial, Tutorial } from "../tutorial"; -import { Button } from "#enums/buttons"; -import MoveInfoOverlay from "./move-info-overlay"; -import { allMoves } from "../data/move"; -import * as Utils from "./../utils"; +import BattleScene from "#app/battle-scene"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { allMoves } from "#app/data/move"; +import { getPokeballAtlasKey, PokeballType } from "#app/data/pokeball"; +import { HealShopCostModifier, LockModifierTiersModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { getPlayerShopModifierTypeOptionsForWave, ModifierTypeOption, TmModifierType } from "#app/modifier/modifier-type"; import Overrides from "#app/overrides"; +import { handleTutorial, Tutorial } from "#app/tutorial"; +import { BooleanHolder, formatMoney, NumberHolder } from "#app/utils"; +import { Button } from "#enums/buttons"; +import { ShopCursorTarget } from "#enums/shop-cursor-target"; import i18next from "i18next"; -import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; -import { IntegerHolder } from "./../utils"; import Phaser from "phaser"; +import AwaitableUiHandler from "./awaitable-ui-handler"; +import MoveInfoOverlay from "./move-info-overlay"; +import { addTextObject, getModifierTierTextTint, getTextColor, getTextStyleOptions, TextStyle } from "./text"; +import { Mode } from "./ui"; export const SHOP_OPTIONS_ROW_LIMIT = 6; @@ -186,11 +186,18 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const typeOptions = args[1] as ModifierTypeOption[]; const removeHealShop = this.scene.gameMode.hasNoShop; - const baseShopCost = new IntegerHolder(this.scene.getWaveMoneyAmount(1)); + const baseShopCost = new NumberHolder(this.scene.getWaveMoneyAmount(1)); + this.scene.applyModifier(HealShopCostModifier, true, baseShopCost); + const shopTypeOptions = !removeHealShop - ? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value) + ? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value, this.scene.gameMode).filter(shopItem => { + const isValidForChallenge = new BooleanHolder(true); + applyChallenges(this.scene.gameMode, ChallengeType.SHOP_ITEM_BLACKLIST, shopItem, isValidForChallenge); + return isValidForChallenge.value; + }) : []; + const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24; for (let m = 0; m < typeOptions.length; m++) { @@ -554,7 +561,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } const canReroll = this.scene.money >= this.rerollCost; - const formattedMoney = Utils.formatMoney(this.scene.moneyFormat, this.rerollCost); + const formattedMoney = formatMoney(this.scene.moneyFormat, this.rerollCost); this.rerollCostText.setText(i18next.t("modifierSelectUiHandler:rerollCost", { formattedMoney })); this.rerollCostText.setColor(this.getTextColor(canReroll ? TextStyle.MONEY : TextStyle.PARTY_RED)); @@ -819,7 +826,7 @@ class ModifierOption extends Phaser.GameObjects.Container { const cost = Overrides.WAIVE_ROLL_FEE_OVERRIDE ? 0 : this.modifierTypeOption.cost; const textStyle = cost <= scene.money ? TextStyle.MONEY : TextStyle.PARTY_RED; - const formattedMoney = Utils.formatMoney(scene.moneyFormat, cost); + const formattedMoney = formatMoney(scene.moneyFormat, cost); this.itemCostText.setText(i18next.t("modifierSelectUiHandler:itemCost", { formattedMoney })); this.itemCostText.setColor(getTextColor(textStyle, false, scene.uiTheme));