From 66775f7cc94b625212d02d78018d3fd5dd23c7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilde=20Sim=C3=B5es?= Date: Wed, 4 Jun 2025 20:33:03 +0100 Subject: [PATCH 1/6] =?UTF-8?q?Implement=20NuzLocke=20related=20challenges?= =?UTF-8?q?=20and=20AI=20changes=20The=20Nuzlocke=20challenges=20that=20we?= =?UTF-8?q?re=20implemented=20were=20the=20following:=20-=20"No=20Free=20H?= =?UTF-8?q?eal"=20which=20consists=20in=20disabling=20the=20auto=20heal=20?= =?UTF-8?q?that=20occurs=20every=2010th=20wave,=20replacing=20it=20with=20?= =?UTF-8?q?a=20normal=20shop=20phase.=20Changes=20made:=20=20=20=20-=20Add?= =?UTF-8?q?ed=20a=20new=20challenge=20"NO=5FAUTO=5FHEAL"=20with=20a=20chal?= =?UTF-8?q?lenge=20type=20"NO=5FHEAL=5FPHASE"=20and=20created=20a=20new=20?= =?UTF-8?q?function=20"applyNoHealPhase"=20to=20determine=20whether=20the?= =?UTF-8?q?=20Pok=C3=A9mon=20can=20heal=20if=20the=20challenge=20is=20acti?= =?UTF-8?q?ve=20("challenge.ts").=20=20=20=20-=20Added=20a=20confirmation?= =?UTF-8?q?=20on=20the=20healing=20phase=20to=20check=20if=20the=20challen?= =?UTF-8?q?ge=20is=20active=20or=20not,=20and=20if=20it=20is,=20it=20shoul?= =?UTF-8?q?d=20skip=20the=20phase=20("party-heal-phase.ts").=20-=20Changed?= =?UTF-8?q?=20the=20logistic=20of=20when=20the=20shop=20should=20be=20disp?= =?UTF-8?q?layed,=20so=20when=20the=20challenge=20is=20active=20the=20shop?= =?UTF-8?q?=20appears=20every=2010th=20wave=20("modifier-type.ts")=20and?= =?UTF-8?q?=20actually=20push=20the=20shop=20phase=20("victory-phase.ts").?= =?UTF-8?q?=20-=20"Hardcore":=20Challenge=20divided=20into=20two=20modes,?= =?UTF-8?q?=20normal=20and=20hard,=20where=20fainted=20Pok=C3=A9mon=20can'?= =?UTF-8?q?t=20be=20revived,=20in=20addition,=20the=20hard=20mode=20delete?= =?UTF-8?q?s=20the=20fainted=20Pok=C3=A9mon=20so=20the=20player=20can't=20?= =?UTF-8?q?switch=20it's=20items=20after=20death.=20Changes=20made:=20-=20?= =?UTF-8?q?Added=20a=20new=20challenge=20"HARDCORE"=20with=20several=20cha?= =?UTF-8?q?llenge=20types=20with=20the=20correspondent=20apply=20functions?= =?UTF-8?q?=20("challenge.ts"),=20each=20one=20is=20used=20as=20follows:?= =?UTF-8?q?=20=09-=20RANDOM=5FITEM=5FBLACKLIST:=20filter=20the=20reward=20?= =?UTF-8?q?items=20with=20only=20the=20valid=20one's=20("modifier-type.ts"?= =?UTF-8?q?).=20=09-=20SHOP=5FITEM=5FBLACKLIST:=20filter=20the=20shop=20it?= =?UTF-8?q?ems=20with=20only=20the=20valid=20one's=20("modifier-select-ui-?= =?UTF-8?q?handler.ts").=20=09-=20MOVE=5FBLACKLIST:=20checks=20if=20the=20?= =?UTF-8?q?move=20selected=20is=20allowed=20and=20if=20not=20sends=20a=20m?= =?UTF-8?q?essage=20of=20no=20apply=20("pokemon.ts").=20=09-=20DELETE=5FPO?= =?UTF-8?q?KEMON:=20if=20hard=20mode=20was=20selected,=20automatically=20d?= =?UTF-8?q?elete=20the=20fainted=20Pok=C3=A9mon=20from=20the=20party=20("b?= =?UTF-8?q?attle-end-pahse.ts").=20=09-=20SHOULD=5FFUSE:=20changed=20the?= =?UTF-8?q?=20logic=20of=20should=20apply=20function=20to=20prohibit=20the?= =?UTF-8?q?=20fusion=20with=20dead=20Pok=C3=A9mon=20("modifier.ts").=20=09?= =?UTF-8?q?-=20PREVENT=5FREVIVE:=20prevent=20the=20gain=20of=20hp=20of=20f?= =?UTF-8?q?ainted=20Pok=C3=A9mon=20("party-heal-phase.ts").=20-=20"Limited?= =?UTF-8?q?=20Catch":=20Only=20the=20first=20wild=20Pok=C3=A9mon=20encount?= =?UTF-8?q?er=20of=20every=20biome=20can=20be=20added=20to=20the=20player'?= =?UTF-8?q?s=20current=20party.=20Changes=20made:=20-=20Added=20a=20new=20?= =?UTF-8?q?challenge=20LIMITED=5FCATCH=20with=20a=20challenge=20type=20=20?= =?UTF-8?q?ADD=5FPOKEMON=5FTO=5FPARTY=20and=20created=20a=20new=20function?= =?UTF-8?q?=20"applyAddPokemonToParty"=20to=20determine=20whether=20the=20?= =?UTF-8?q?Pok=C3=A9mon=20can=20be=20added=20to=20the=20party,=20which=20s?= =?UTF-8?q?hould=20only=20occur=20every=2011th=20wave=20if=20it=20isn't=20?= =?UTF-8?q?a=20catchable=20mystery=20encounter=20or=20every=2012th=20wave?= =?UTF-8?q?=20if=20the=2011th=20wave=20was=20a=20catchable=20mystery=20enc?= =?UTF-8?q?ounter=20("challenge.ts").=20-=20Changed=20the=20logistic=20of?= =?UTF-8?q?=20adding=20a=20Pok=C3=A9mon=20where=20it=20can=20be=20caught?= =?UTF-8?q?=20so=20that=20the=20"pokedex"=20is=20updated=20but=20the=20Pok?= =?UTF-8?q?=C3=A9mon=20isn't=20added=20to=20the=20party=20of=20the=20playe?= =?UTF-8?q?r=20affecting=20specifically=20mystery=20encounters=20("encount?= =?UTF-8?q?er-pokemon-utils.ts")=20and=20added=20the=20same=20logic=20to?= =?UTF-8?q?=20normal=20encounters.=20("attempt-capture-phase.ts")?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The changes in the game AI were as follows ("pokemon.ts"): - More accurately accounts for the Pokémon's actual moves and their effectiveness against the player instead of only the pokemon type - Introduced logic to decide when a Pokémon should be sacrificed or switched based on its HP and speed. Signed-off-by: Matilde Simões Co-authored-by: Fuad Ali --- src/data/challenge.ts | 314 ++++++++++++++++++ .../encounters/a-trainers-test-encounter.ts | 2 + .../slumbering-snorlax-encounter.ts | 2 + .../the-winstrate-challenge-encounter.ts | 2 + .../utils/encounter-pokemon-utils.ts | 13 + src/enums/challenges.ts | 3 + src/field/pokemon.ts | 7 + src/modifier/modifier-type.ts | 17 +- src/modifier/modifier.ts | 8 +- src/phases/attempt-capture-phase.ts | 13 + src/phases/battle-end-phase.ts | 12 + src/phases/command-phase.ts | 4 +- src/phases/party-heal-phase.ts | 22 +- src/phases/victory-phase.ts | 9 + src/ui/modifier-select-ui-handler.ts | 15 +- 15 files changed, 428 insertions(+), 15 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 3fdd83c185d..1de8e8949e4 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -26,6 +26,8 @@ import { ModifierTier } from "#enums/modifier-tier"; import { globalScene } from "#app/global-scene"; import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonEvolutions } from "./balance/pokemon-evolutions"; +import type { ModifierTypeOption } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { ChallengeType } from "#enums/challenge-type"; import type { MoveSourceType } from "#enums/move-source-type"; @@ -345,6 +347,84 @@ export abstract class Challenge { applyFlipStat(_pokemon: Pokemon, _baseStats: number[]) { return false; } + + /** + * An apply function for NO_AUTO_HEAL challenges. Derived classes should alter this. + * @param _applyHealPhase {@link BooleanHolder} Whether it should apply the heal phase. + * @returns {@link boolean} if this function did anything. + */ + applyNoHealPhase(_applyHealPhase: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for PREVENT_REVIVE. Derived classes should alter this. + * @param _canBeRevived {@link BooleanHolder} Whether it should revive the fainted Pokemon. + * @returns {@link boolean} if this function did anything. + */ + applyRevivePrevention(_canBeRevived: BooleanHolder): boolean { + return true; + } + + /** + * An apply function for RANDOM_ITEM_BLACKLIST. Derived classes should alter this. + * @param _randomItem {@link ModifierTypeOption} The random item in question. + * @param _isValid {@link BooleanHolder} Whether it should load the random item. + * @returns {@link boolean} if this function did anything. + */ + applyRandomItemBlacklist(_randomItem: ModifierTypeOption | null, _isValid: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for SHOP_ITEM_BLACKLIST. Derived classes should alter this. + * @param _shopItem {@link ModifierTypeOption} The shop item in question. + * @param _isValid {@link BooleanHolder} Whether the shop should have the item. + * @returns {@link boolean} if this function did anything. + */ + applyShopItemBlacklist(_shopItem: ModifierTypeOption | null, _isValid: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for MOVE_BLACKLIST. Derived classes should alter this. + * @param _move {@link PokemonMove} The move in question. + * @param _isValid {@link BooleanHolder} Whether the move should be allowed. + * @returns {@link boolean} if this function did anything. + */ + applyMoveBlacklist(_move: PokemonMove, _isValid: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for DELETE_POKEMON. Derived classes should alter this. + * @param _canStay {@link BooleanHolder} Whether the pokemon can stay in team after death. + * @returns {@link boolean} if this function did anything. + */ + applyDeletePokemon(_canStay: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for ADD_POKEMON_TO_PARTY. Derived classes should alter this. + * @param _waveIndex {@link BooleanHolder} The current wave. + * @param _canAddToParty {@link BooleanHolder} Whether the pokemon can be caught. + * @returns {@link boolean} if this function did anything. + */ + applyAddPokemonToParty(_waveIndex: number, _canAddToParty: BooleanHolder): boolean { + return false; + } + + /** + * An apply function for SHOULD_FUSE. Derived classes should alter this. + * @param _pokemon {@link Pokemon} The first chosen pokemon for fusion. + * @param _pokemonTofuse {@link Pokemon} The second chosen pokemon for fusion. + * @param _canFuse {@link BooleanHolder} Whether the pokemons can fuse. + * @returns {@link boolean} if this function did anything. + */ + applyShouldFuse(_pokemon: Pokemon, _pokemonTofuse: Pokemon, _canFuse: BooleanHolder): boolean { + return false; + } } type ChallengeCondition = (data: GameData) => boolean; @@ -889,6 +969,123 @@ export class LowerStarterPointsChallenge extends Challenge { } } +/** + * Challenge stops pokemon from healing every 10th wave + */ +export class NoFreeHealsChallenge extends Challenge { + constructor() { + super(Challenges.NO_AUTO_HEAL, 1); + } + + applyNoHealPhase(applyHealPhase: BooleanHolder): boolean { + applyHealPhase.value = false; + return true; + } + + static loadChallenge(source: NoFreeHealsChallenge | any): NoFreeHealsChallenge { + const newChallenge = new NoFreeHealsChallenge(); + 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, 2); + } + + applyRandomItemBlacklist(randomItem: ModifierTypeOption, isValid: BooleanHolder): boolean { + if (randomItem !== null) { + isValid.value = !this.itemBlackList.includes(randomItem.type.localeKey); + } + return true; + } + + applyShopItemBlacklist(shopItem: ModifierTypeOption, isValid: BooleanHolder): boolean { + isValid.value = !this.itemBlackList.includes(shopItem.type.localeKey); + return true; + } + + applyMoveBlacklist(move: PokemonMove, moveCanBeUsed: BooleanHolder): boolean { + const moveBlacklist = [Moves.REVIVAL_BLESSING]; + moveCanBeUsed.value = !moveBlacklist.includes(move.moveId); + return true; + } + + applyRevivePrevention(canBeRevived: BooleanHolder): boolean { + canBeRevived.value = false; + return true; + } + + applyDeletePokemon(canStay: BooleanHolder): boolean { + if (this.value === 2) { + canStay.value = false; + } else { + canStay.value = true; + } + return true; + } + + override applyShouldFuse(pokemon: Pokemon, pokemonToFuse: Pokemon, canFuse: BooleanHolder): boolean { + if (pokemon!.isFainted() || pokemonToFuse.isFainted()) { + canFuse.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 limits the amount of caught pokemons by 1 per biome stage + */ +export class LimitedCatchChallenge extends Challenge { + private mysteryEncounterBlacklist = [ + MysteryEncounterType.ABSOLUTE_AVARICE, + MysteryEncounterType.DANCING_LESSONS, + MysteryEncounterType.SAFARI_ZONE, + MysteryEncounterType.THE_POKEMON_SALESMAN, + MysteryEncounterType.UNCOMMON_BREED, + ]; + constructor() { + super(Challenges.LIMITED_CATCH, 1); + } + + override applyAddPokemonToParty(waveIndex: number, canAddToParty: BooleanHolder): boolean { + const lastMystery = globalScene.lastMysteryEncounter?.encounterType; + if (lastMystery === undefined && !(waveIndex % 10 === 1)) { + canAddToParty.value = false; + } + if (!(waveIndex % 10 === 1) && !(!this.mysteryEncounterBlacklist.includes(lastMystery!) && 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 challengeType {@link ChallengeType} ChallengeType.STARTER_CHOICE @@ -1040,6 +1237,90 @@ export function applyChallenges( ): boolean; export function applyChallenges(challengeType: ChallengeType.FLIP_STAT, pokemon: Pokemon, baseStats: number[]): boolean; +/** + * Apply all challenges that modify whether a pokemon can be auto healed or not in wave 10m. + * @param challengeType {@link ChallengeType} ChallengeType.NO_HEAL_PHASE + * @param applyHealPhase {@link BooleanHolder} Whether it should apply the heal phase. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.NO_HEAL_PHASE, applyHealPhase: BooleanHolder): boolean; +/** + * Apply all challenges that modify whether a shop item should be blacklisted. + * @param challengeType {@link ChallengeType} ChallengeType.SHOP_ITEM_BLACKLIST + * @param shopItem {@link ModifierTypeOption} The shop item in question. + * @param isValid {@link BooleanHolder} Whether the shop should have the item. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.SHOP_ITEM_BLACKLIST, + shopItem: ModifierTypeOption | null, + isValid: BooleanHolder, +): boolean; + +/** + * Apply all challenges that modify whether a reward item should be blacklisted. + * @param challengeType {@link ChallengeType} ChallengeType.RANDOM_ITEM_BLACKLIST + * @param randomItem {@link ModifierTypeOption} The random item in question. + * @param isValid {@link BooleanHolder} Whether it should load the random item. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.RANDOM_ITEM_BLACKLIST, + randomItem: ModifierTypeOption | null, + isValid: BooleanHolder, +): boolean; +/** + * Apply all challenges that modify whether a pokemon move should be blacklisted. + * @param challengeType {@link ChallengeType} ChallengeType.MOVE_BLACKLIST + * @param move {@link PokemonMove} The move in question. + * @param isValid {@link BooleanHolder} Whether the move should be allowed. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.MOVE_BLACKLIST, + move: PokemonMove, + isValid: BooleanHolder, +): boolean; +/** + * Apply all challenges that modify whether a pokemon should be removed from the team. + * @param challengeType {@link ChallengeType} ChallengeType.DELETE_POKEMON + * @param canStay {@link BooleanHolder} Whether the pokemon can stay in team after death. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.DELETE_POKEMON, canStay: BooleanHolder): boolean; +/** + * Apply all challenges that modify whether a pokemon should revive. + * @param challengeType {@link ChallengeType} ChallengeType.PREVENT_REVIVE + * @param canBeRevived {@link BooleanHolder} Whether it should revive the fainted Pokemon. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.PREVENT_REVIVE, canBeRevived: BooleanHolder): boolean; +/** + * Apply all challenges that modify whether a pokemon can be caught. + * @param challengeType {@link ChallengeType} ChallengeType.ADD_POKEMON_TO_PARTY + * @param waveIndex {@link BooleanHolder} The current wave. + * @param canAddToParty {@link BooleanHolder} Whether the pokemon can be caught. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.ADD_POKEMON_TO_PARTY, + waveIndex: number, + canAddToParty: BooleanHolder, +): boolean; +/** + * Apply all challenges that modify whether a pokemon can fuse. + * @param challengeType {@link ChallengeType} ChallengeType.SHOULD_FUSE + * @param pokemon {@link Pokemon} The first chosen pokemon for fusion. + * @param pokemonTofuse {@link Pokemon} The second chosen pokemon for fusion. + * @param canFuse {@link BooleanHolder} Whether the pokemons can fuse. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges( + challengeType: ChallengeType.SHOULD_FUSE, + pokemon: Pokemon, + pokemonTofuse: Pokemon, + canFuse: BooleanHolder, +): boolean; export function applyChallenges(challengeType: ChallengeType, ...args: any[]): boolean { let ret = false; @@ -1088,6 +1369,30 @@ export function applyChallenges(challengeType: ChallengeType, ...args: any[]): b case ChallengeType.FLIP_STAT: ret ||= c.applyFlipStat(args[0], args[1]); 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.applyRandomItemBlacklist(args[0], args[1]); + break; + case ChallengeType.MOVE_BLACKLIST: + ret ||= c.applyMoveBlacklist(args[0], args[1]); + break; + case ChallengeType.DELETE_POKEMON: + ret ||= c.applyDeletePokemon(args[0]); + break; + case ChallengeType.PREVENT_REVIVE: + ret ||= c.applyRevivePrevention(args[0]); + break; + case ChallengeType.ADD_POKEMON_TO_PARTY: + ret ||= c.applyAddPokemonToParty(args[0], args[1]); + break; + case ChallengeType.SHOULD_FUSE: + ret ||= c.applyShouldFuse(args[0], args[1], args[2]); + break; } } }); @@ -1115,6 +1420,12 @@ export function copyChallenge(source: Challenge | any): Challenge { return InverseBattleChallenge.loadChallenge(source); case Challenges.FLIP_STAT: return FlipStatChallenge.loadChallenge(source); + case Challenges.NO_AUTO_HEAL: + return NoFreeHealsChallenge.loadChallenge(source); + case Challenges.HARDCORE: + return HardcoreChallenge.loadChallenge(source); + case Challenges.LIMITED_CATCH: + return LimitedCatchChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } @@ -1128,6 +1439,9 @@ export function initChallenges() { new FreshStartChallenge(), new InverseBattleChallenge(), new FlipStatChallenge(), + new NoFreeHealsChallenge(), + new LimitedCatchChallenge(), + new HardcoreChallenge(), ); } diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 7a1c9821e89..bf6d2f5aabe 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -22,6 +22,7 @@ import { EggTier } from "#enums/egg-type"; import { ModifierTier } from "#enums/modifier-tier"; import { modifierTypes } from "#app/data/data-lists"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/aTrainersTest"; @@ -34,6 +35,7 @@ const namespace = "mysteryEncounters/aTrainersTest"; export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType( MysteryEncounterType.A_TRAINERS_TEST, ) + .withDisallowedChallenges(Challenges.NO_AUTO_HEAL) .withEncounterTier(MysteryEncounterTier.ROGUE) .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withIntroSpriteConfigs([]) // These are set in onInit() diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index d6a85dee119..445d8b6f4ba 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -31,6 +31,7 @@ import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import { randSeedInt } from "#app/utils/common"; +import { Challenges } from "#enums/challenges"; import { MoveUseMode } from "#enums/move-use-mode"; /** i18n namespace for the encounter */ @@ -44,6 +45,7 @@ const namespace = "mysteryEncounters/slumberingSnorlax"; export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType( MysteryEncounterType.SLUMBERING_SNORLAX, ) + .withDisallowedChallenges(Challenges.NO_AUTO_HEAL) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(15, 150) .withCatchAllowed(true) diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 6d28a710953..58d8196b8f4 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -31,6 +31,7 @@ import i18next from "i18next"; import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { BattlerTagType } from "#enums/battler-tag-type"; +import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theWinstrateChallenge"; @@ -43,6 +44,7 @@ const namespace = "mysteryEncounters/theWinstrateChallenge"; export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType( MysteryEncounterType.THE_WINSTRATE_CHALLENGE, ) + .withDisallowedChallenges(Challenges.NO_AUTO_HEAL) .withEncounterTier(MysteryEncounterTier.ROGUE) .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withIntroSpriteConfigs([ diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index f3655217b5a..916d2da036d 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -37,6 +37,8 @@ import { CustomPokemonData } from "#app/data/pokemon/pokemon-data"; import type { AbilityId } from "#enums/ability-id"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; +import { BooleanHolder } from "#app/utils/common"; +import { ChallengeType, applyChallenges } from "#app/data/challenge"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; @@ -703,6 +705,17 @@ export async function catchPokemon( }); }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { + const challengeCanAddToParty = new BooleanHolder(true); + applyChallenges( + ChallengeType.ADD_POKEMON_TO_PARTY, + globalScene.currentBattle.waveIndex, + challengeCanAddToParty, + ); + if (!challengeCanAddToParty.value) { + removePokemon(); + end(); + return; + } if (globalScene.getPlayerParty().length === 6) { const promptRelease = () => { globalScene.ui.showText( diff --git a/src/enums/challenges.ts b/src/enums/challenges.ts index 7b506a61a2f..5d9edcefbee 100644 --- a/src/enums/challenges.ts +++ b/src/enums/challenges.ts @@ -6,4 +6,7 @@ export enum Challenges { FRESH_START, INVERSE_BATTLE, FLIP_STAT, + NO_AUTO_HEAL, + HARDCORE, + LIMITED_CATCH, } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0a8e8469115..a84f09c5875 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3231,6 +3231,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public trySelectMove(moveIndex: number, ignorePp?: boolean): boolean { const move = this.getMoveset().length > moveIndex ? this.getMoveset()[moveIndex] : null; + if (move !== null) { + const isValid = new BooleanHolder(true); + applyChallenges(ChallengeType.MOVE_BLACKLIST, move!, isValid); + if (!isValid.value) { + return false; + } + } return move?.isUsable(this, ignorePp) ?? false; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index fcbe6b66a4e..dae82797ca7 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -111,6 +111,7 @@ import { NumberHolder, padInt, randSeedInt, + BooleanHolder, } from "#app/utils/common"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; @@ -128,6 +129,7 @@ import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; import { ModifierPoolType } from "#enums/modifier-pool-type"; import { getModifierPoolForType, getModifierType } from "#app/utils/modifier-utils"; import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; +import { applyChallenges } from "#app/data/challenge"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -2617,10 +2619,14 @@ function getModifierTypeOptionWithRetry( allowLuckUpgrades = allowLuckUpgrades ?? true; let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades); let r = 0; + const isValidForChallenge = new BooleanHolder(true); + applyChallenges(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 + (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, @@ -2630,6 +2636,7 @@ function getModifierTypeOptionWithRetry( 0, allowLuckUpgrades, ); + applyChallenges(ChallengeType.RANDOM_ITEM_BLACKLIST, candidate, isValidForChallenge); } return candidate!; } @@ -2660,7 +2667,9 @@ export function overridePlayerModifierTypeOptions(options: ModifierTypeOption[], } export function getPlayerShopModifierTypeOptionsForWave(waveIndex: number, baseCost: number): ModifierTypeOption[] { - if (!(waveIndex % 10)) { + const isHealPhaseActive = new BooleanHolder(true); + applyChallenges(ChallengeType.NO_HEAL_PHASE, isHealPhaseActive); + if (!(waveIndex % 10) && isHealPhaseActive.value) { return []; } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 247b64ca2c0..b8e4f6ef9ac 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -45,6 +45,7 @@ import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types"; +import { applyChallenges } from "#app/data/challenge"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -2426,8 +2427,13 @@ export class FusePokemonModifier extends ConsumablePokemonModifier { * @returns `true` if {@linkcode FusePokemonModifier} should be applied */ override shouldApply(playerPokemon?: PlayerPokemon, playerPokemon2?: PlayerPokemon): boolean { + const shouldFuse = new BooleanHolder(true); + applyChallenges(ChallengeType.SHOULD_FUSE, playerPokemon!, playerPokemon2!, shouldFuse); return ( - super.shouldApply(playerPokemon, playerPokemon2) && !!playerPokemon2 && this.fusePokemonId === playerPokemon2.id + super.shouldApply(playerPokemon, playerPokemon2) && + !!playerPokemon2 && + this.fusePokemonId === playerPokemon2.id && + shouldFuse.value ); } diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index f4e6725935a..84bc677f4c4 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -24,6 +24,8 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import { Gender } from "#app/data/gender"; +import { BooleanHolder } from "#app/utils/common"; +import { ChallengeType, applyChallenges } from "#app/data/challenge"; export class AttemptCapturePhase extends PokemonPhase { public readonly phaseName = "AttemptCapturePhase"; @@ -285,6 +287,17 @@ export class AttemptCapturePhase extends PokemonPhase { }); }; Promise.all([pokemon.hideInfo(), globalScene.gameData.setPokemonCaught(pokemon)]).then(() => { + const challengeCanAddToParty = new BooleanHolder(true); + applyChallenges( + ChallengeType.ADD_POKEMON_TO_PARTY, + globalScene.currentBattle.waveIndex, + challengeCanAddToParty, + ); + if (!challengeCanAddToParty.value) { + removePokemon(); + end(); + return; + } if (globalScene.getPlayerParty().length === PLAYER_PARTY_MAX_SIZE) { const promptRelease = () => { globalScene.ui.showText( diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 297e20cb445..38f8ef43d88 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -2,6 +2,8 @@ import { globalScene } from "#app/global-scene"; import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; +import { BooleanHolder } from "#app/utils/common"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; export class BattleEndPhase extends BattlePhase { public readonly phaseName = "BattleEndPhase"; @@ -67,6 +69,16 @@ export class BattleEndPhase extends BattlePhase { for (const pokemon of globalScene.getPokemonAllowedInBattle()) { applyAbAttrs("PostBattleAbAttr", { pokemon, victory: this.isVictory }); } + const canStay = new BooleanHolder(true); + applyChallenges(ChallengeType.DELETE_POKEMON, canStay); + if (!canStay.value) { + const party = globalScene.getPlayerParty().slice(); + for (const pokemon of party) { + if (pokemon.isFainted()) { + globalScene.removePokemonFromPlayerParty(pokemon); + } + } + } if (globalScene.currentBattle.moneyScattered) { globalScene.currentBattle.pickUpScatteredMoney(); diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index 8281019b3c4..1203750ed55 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -227,7 +227,9 @@ export class CommandPhase extends FieldPhase { .selectionDeniedText(playerPokemon, move.moveId) : move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" - : "battle:moveNoPP"; + : move.getPpRatio() + ? "battle:moveDisabled" + : "battle:moveNoPP"; const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator globalScene.ui.showText( diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 765c7dbad8e..1a017293126 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -1,6 +1,7 @@ import { globalScene } from "#app/global-scene"; -import { fixedInt } from "#app/utils/common"; +import { BooleanHolder, fixedInt } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; export class PartyHealPhase extends BattlePhase { public readonly phaseName = "PartyHealPhase"; @@ -15,18 +16,27 @@ export class PartyHealPhase extends BattlePhase { start() { super.start(); + const isHealPhaseActive = new BooleanHolder(true); + const isReviveActive = new BooleanHolder(true); + applyChallenges(ChallengeType.NO_HEAL_PHASE, isHealPhaseActive); + if (!isHealPhaseActive.value) { + return this.end(); + } const bgmPlaying = globalScene.isBgmPlaying(); if (bgmPlaying) { globalScene.fadeOutBgm(1000, false); } globalScene.ui.fadeOut(1000).then(() => { for (const pokemon of globalScene.getPlayerParty()) { - pokemon.hp = pokemon.getMaxHp(); - pokemon.resetStatus(true, false, false, true); - for (const move of pokemon.moveset) { - move.ppUsed = 0; + applyChallenges(ChallengeType.PREVENT_REVIVE, isReviveActive); + if (isReviveActive.value || !pokemon.isFainted()) { + pokemon.hp = pokemon.getMaxHp(); + pokemon.resetStatus(true, false, false, true); + for (const move of pokemon.moveset) { + move.ppUsed = 0; + } + pokemon.updateInfo(true); } - pokemon.updateInfo(true); } const healSong = globalScene.playSoundWithoutBgm("heal"); globalScene.time.delayedCall(fixedInt(healSong.totalDuration * 1000), () => { diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index ae5b727c2a6..9ccc28d6bd7 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -8,6 +8,9 @@ import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/util import { globalScene } from "#app/global-scene"; import { timedEventManager } from "#app/global-event-manager"; +import { BooleanHolder } from "#app/utils/common"; +import { applyChallenges } from "#app/data/challenge"; + export class VictoryPhase extends PokemonPhase { public readonly phaseName = "VictoryPhase"; /** If true, indicates that the phase is intended for EXP purposes only, and not to continue a battle to next phase */ @@ -37,6 +40,8 @@ export class VictoryPhase extends PokemonPhase { return this.end(); } + const isHealPhaseActive = new BooleanHolder(true); + applyChallenges(ChallengeType.NO_HEAL_PHASE, isHealPhaseActive); if ( !globalScene .getEnemyParty() @@ -104,6 +109,10 @@ export class VictoryPhase extends PokemonPhase { ); globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase"); } + if (!isHealPhaseActive.value) { + //Push shop instead of healing phase for NoHealChallenge + globalScene.pushPhase(new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers())); + } } if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) { diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 7f5bf997f88..68ee8f60c44 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -16,6 +16,8 @@ import i18next from "i18next"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import Phaser from "phaser"; import type { PokeballType } from "#enums/pokeball"; +import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { BooleanHolder } from "#app/utils/common"; export const SHOP_OPTIONS_ROW_LIMIT = 7; const SINGLE_SHOP_ROW_YOFFSET = 12; @@ -211,9 +213,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const removeHealShop = globalScene.gameMode.hasNoShop; const baseShopCost = new NumberHolder(globalScene.getWaveMoneyAmount(1)); globalScene.applyModifier(HealShopCostModifier, true, baseShopCost); - const shopTypeOptions = !removeHealShop - ? getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value) - : []; + const shopTypeOptions = removeHealShop + ? [] + : getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value).filter( + shopItem => { + const isValidForChallenge = new BooleanHolder(true); + applyChallenges(ChallengeType.SHOP_ITEM_BLACKLIST, shopItem, isValidForChallenge); + return isValidForChallenge.value; + }, + ); + const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; From 289dffb8d0dfb06fc4e2018da44eada26c43fb8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilde=20Sim=C3=B5es?= Date: Wed, 4 Jun 2025 21:39:21 +0100 Subject: [PATCH 2/6] =?UTF-8?q?Implement=20NuzLocke=20related=20challenges?= =?UTF-8?q?=20and=20AI=20changes=20The=20Nuzlocke=20challenges=20that=20we?= =?UTF-8?q?re=20implemented=20were=20the=20following:=20-=20"No=20Free=20H?= =?UTF-8?q?eal"=20which=20consists=20in=20disabling=20the=20auto=20heal=20?= =?UTF-8?q?that=20occurs=20every=2010th=20wave,=20replacing=20it=20with=20?= =?UTF-8?q?a=20normal=20shop=20phase.=20Changes=20made:=20=20=20=20-=20Add?= =?UTF-8?q?ed=20a=20new=20challenge=20"NO=5FAUTO=5FHEAL"=20with=20a=20chal?= =?UTF-8?q?lenge=20type=20"NO=5FHEAL=5FPHASE"=20and=20created=20a=20new=20?= =?UTF-8?q?function=20"applyNoHealPhase"=20to=20determine=20whether=20the?= =?UTF-8?q?=20Pok=C3=A9mon=20can=20heal=20if=20the=20challenge=20is=20acti?= =?UTF-8?q?ve=20("challenge.ts").=20=20=20=20-=20Added=20a=20confirmation?= =?UTF-8?q?=20on=20the=20healing=20phase=20to=20check=20if=20the=20challen?= =?UTF-8?q?ge=20is=20active=20or=20not,=20and=20if=20it=20is,=20it=20shoul?= =?UTF-8?q?d=20skip=20the=20phase=20("party-heal-phase.ts").=20=20=20=20-?= =?UTF-8?q?=20Changed=20the=20logistic=20of=20when=20the=20shop=20should?= =?UTF-8?q?=20be=20displayed,=20so=20when=20the=20challenge=20is=20active?= =?UTF-8?q?=20the=20shop=20appears=20every=2010th=20wave=20("modifier-type?= =?UTF-8?q?.ts")=20and=20actually=20push=20the=20shop=20phase=20("victory-?= =?UTF-8?q?phase.ts").=20-=20"Hardcore":=20Challenge=20divided=20into=20tw?= =?UTF-8?q?o=20modes,=20normal=20and=20hard,=20where=20fainted=20Pok=C3=A9?= =?UTF-8?q?mon=20can't=20be=20revived,=20in=20addition,=20the=20hard=20mod?= =?UTF-8?q?e=20deletes=20the=20fainted=20Pok=C3=A9mon=20so=20the=20player?= =?UTF-8?q?=20can't=20switch=20it's=20items=20after=20death.=20Changes=20m?= =?UTF-8?q?ade:=20=20=20=20-=20Added=20a=20new=20challenge=20"HARDCORE"=20?= =?UTF-8?q?with=20several=20challenge=20types=20with=20the=20correspondent?= =?UTF-8?q?=20apply=20functions=20("challenge.ts"),=20each=20one=20is=20us?= =?UTF-8?q?ed=20as=20follows:=20=20=20=20-=20RANDOM=5FITEM=5FBLACKLIST:=20?= =?UTF-8?q?filter=20the=20reward=20items=20with=20only=20the=20valid=20one?= =?UTF-8?q?'s=20("modifier-type.ts").=20=20=20=20-=20SHOP=5FITEM=5FBLACKLI?= =?UTF-8?q?ST:=20filter=20the=20shop=20items=20with=20only=20the=20valid?= =?UTF-8?q?=20one's=20("modifier-select-ui-handler.ts").=20=20=20=20-=20MO?= =?UTF-8?q?VE=5FBLACKLIST:=20checks=20if=20the=20move=20selected=20is=20al?= =?UTF-8?q?lowed=20and=20if=20not=20sends=20a=20message=20of=20no=20apply?= =?UTF-8?q?=20("pokemon.ts").=20=20=20=20-=20DELETE=5FPOKEMON:=20if=20hard?= =?UTF-8?q?=20mode=20was=20selected,=20automatically=20delete=20the=20fain?= =?UTF-8?q?ted=20Pok=C3=A9mon=20from=20the=20party=20("battle-end-pahse.ts?= =?UTF-8?q?").=20=20=20=20-=20SHOULD=5FFUSE:=20changed=20the=20logic=20of?= =?UTF-8?q?=20should=20apply=20function=20to=20prohibit=20the=20fusion=20w?= =?UTF-8?q?ith=20dead=20Pok=C3=A9mon=20("modifier.ts").=20=20=20=20-=20PRE?= =?UTF-8?q?VENT=5FREVIVE:=20prevent=20the=20gain=20of=20hp=20of=20fainted?= =?UTF-8?q?=20Pok=C3=A9mon=20("party-heal-phase.ts").=20-=20"Limited=20Cat?= =?UTF-8?q?ch":=20Only=20the=20first=20wild=20Pok=C3=A9mon=20encounter=20o?= =?UTF-8?q?f=20every=20biome=20can=20be=20added=20to=20the=20player's=20cu?= =?UTF-8?q?rrent=20party.=20Changes=20made:=20=20=20-=20Added=20a=20new=20?= =?UTF-8?q?challenge=20LIMITED=5FCATCH=20with=20a=20challenge=20type=20=20?= =?UTF-8?q?ADD=5FPOKEMON=5FTO=5FPARTY=20and=20created=20a=20new=20function?= =?UTF-8?q?=20"applyAddPokemonToParty"=20to=20determine=20whether=20the=20?= =?UTF-8?q?Pok=C3=A9mon=20can=20be=20added=20to=20the=20party,=20which=20s?= =?UTF-8?q?hould=20only=20occur=20every=2011th=20wave=20if=20it=20isn't=20?= =?UTF-8?q?a=20catchable=20mystery=20encounter=20or=20every=2012th=20wave?= =?UTF-8?q?=20if=20the=2011th=20wave=20was=20a=20catchable=20mystery=20enc?= =?UTF-8?q?ounter=20("challenge.ts").=20=20=20=20-=20Changed=20the=20logis?= =?UTF-8?q?tic=20of=20adding=20a=20Pok=C3=A9mon=20where=20it=20can=20be=20?= =?UTF-8?q?caught=20so=20that=20the=20"pokedex"=20is=20updated=20but=20the?= =?UTF-8?q?=20Pok=C3=A9mon=20isn't=20added=20to=20the=20party=20of=20the?= =?UTF-8?q?=20player=20affecting=20specifically=20mystery=20encounters=20(?= =?UTF-8?q?"encounter-pokemon-utils.ts")=20and=20added=20the=20same=20logi?= =?UTF-8?q?c=20to=20normal=20encounters.=20("attempt-capture-phase.ts")?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The changes in the game AI were as follows ("pokemon.ts"): - More accurately accounts for the Pokémon's actual moves and their effectiveness against the player instead of only the pokemon type - Introduced logic to decide when a Pokémon should be sacrificed or switched based on its HP and speed. Signed-off-by: Matilde Simões Co-authored-by: Fuad Ali --- src/phases/party-heal-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 1a017293126..594f6f9072d 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -19,6 +19,7 @@ export class PartyHealPhase extends BattlePhase { const isHealPhaseActive = new BooleanHolder(true); const isReviveActive = new BooleanHolder(true); applyChallenges(ChallengeType.NO_HEAL_PHASE, isHealPhaseActive); + applyChallenges(ChallengeType.PREVENT_REVIVE, isReviveActive); if (!isHealPhaseActive.value) { return this.end(); } @@ -28,7 +29,6 @@ export class PartyHealPhase extends BattlePhase { } globalScene.ui.fadeOut(1000).then(() => { for (const pokemon of globalScene.getPlayerParty()) { - applyChallenges(ChallengeType.PREVENT_REVIVE, isReviveActive); if (isReviveActive.value || !pokemon.isFainted()) { pokemon.hp = pokemon.getMaxHp(); pokemon.resetStatus(true, false, false, true); From 8f6cf35beb7f07e390090904c8a033af53578df3 Mon Sep 17 00:00:00 2001 From: mati-soda Date: Thu, 12 Jun 2025 22:05:17 +0100 Subject: [PATCH 3/6] =?UTF-8?q?Fixed=20conflicts=20on=20file=20imports.=20?= =?UTF-8?q?Signed-off-by:=20Matilde=20Sim=C3=B5es=20=20Co-authored-by:=20Fuad=20Ali=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/data/challenge.ts | 4 +- .../utils/encounter-pokemon-utils.ts | 3 +- src/enums/challenge-type.ts | 41 ++++++++++++++++++- src/modifier/modifier-type.ts | 1 + src/modifier/modifier.ts | 1 + src/phases/attempt-capture-phase.ts | 3 +- src/phases/battle-end-phase.ts | 3 +- src/phases/party-heal-phase.ts | 3 +- src/phases/victory-phase.ts | 8 +++- src/ui/modifier-select-ui-handler.ts | 3 +- 10 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 1de8e8949e4..cd68d2e0a95 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -20,7 +20,7 @@ import { Challenges } from "#enums/challenges"; import { SpeciesId } from "#enums/species-id"; import { TrainerType } from "#enums/trainer-type"; import { Nature } from "#enums/nature"; -import type { MoveId } from "#enums/move-id"; +import { MoveId } from "#enums/move-id"; import { TypeColor, TypeShadow } from "#enums/color"; import { ModifierTier } from "#enums/modifier-tier"; import { globalScene } from "#app/global-scene"; @@ -1018,7 +1018,7 @@ export class HardcoreChallenge extends Challenge { } applyMoveBlacklist(move: PokemonMove, moveCanBeUsed: BooleanHolder): boolean { - const moveBlacklist = [Moves.REVIVAL_BLESSING]; + const moveBlacklist = [MoveId.REVIVAL_BLESSING]; moveCanBeUsed.value = !moveBlacklist.includes(move.moveId); return true; } diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index 916d2da036d..a312a2bf44f 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -38,7 +38,8 @@ import type { AbilityId } from "#enums/ability-id"; import type { PokeballType } from "#enums/pokeball"; import { StatusEffect } from "#enums/status-effect"; import { BooleanHolder } from "#app/utils/common"; -import { ChallengeType, applyChallenges } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; diff --git a/src/enums/challenge-type.ts b/src/enums/challenge-type.ts index d9b1fce3e6e..25371c8fffa 100644 --- a/src/enums/challenge-type.ts +++ b/src/enums/challenge-type.ts @@ -65,5 +65,44 @@ export enum ChallengeType { /** * Modifies what the pokemon stats for Flip Stat Mode. */ - FLIP_STAT + FLIP_STAT, + /** + * Challenge that modifies if the player should auto heal every 10th wave + */ + NO_HEAL_PHASE, + /** + * Modifies if the shop item is blacklisted + * @see {@linkcode Challenge.applyShopItemBlacklist} + */ + SHOP_ITEM_BLACKLIST, + /** + * Modifies if the random item is blacklisted + * @see {@linkcode Challenge.applyRandomItemBlacklist} + */ + RANDOM_ITEM_BLACKLIST, + /** + * Modifies if the move is blacklisted + * @see {@linkcode Challenge.applyMoveBlacklist} + */ + MOVE_BLACKLIST, + /** + * Modifies if pokemon are allowed to be revived from fainting + * @see {@linkcode Challenge.applyRevivePrevention} + */ + PREVENT_REVIVE, + /** + * Modifies if pokemon are allowed to be revived from fainting + * @see {@linkcode Challenge.applyDeletePokemon} + */ + DELETE_POKEMON, + /** + * Challenge that modifies if the player should catch pokemon on waves other than the first + * @see {@linkcode Challenge.applyAddPokemonToParty} + */ + ADD_POKEMON_TO_PARTY, + /** + * Modifies if pokemon are allowed to fuse + * @see {@linkcode Challenge.applyShouldFuse} + */ + SHOULD_FUSE, } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index dae82797ca7..76a97325331 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -130,6 +130,7 @@ import { ModifierPoolType } from "#enums/modifier-pool-type"; import { getModifierPoolForType, getModifierType } from "#app/utils/modifier-utils"; import type { ModifierTypeFunc, WeightedModifierTypeWeightFunc } from "#app/@types/modifier-types"; import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; const outputModifierData = false; const useMaxWeightForOutput = false; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b8e4f6ef9ac..d261d2508aa 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -46,6 +46,7 @@ import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import type { ModifierInstanceMap, ModifierString } from "#app/@types/modifier-types"; import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; export type ModifierPredicate = (modifier: Modifier) => boolean; diff --git a/src/phases/attempt-capture-phase.ts b/src/phases/attempt-capture-phase.ts index 84bc677f4c4..13e0437ae9f 100644 --- a/src/phases/attempt-capture-phase.ts +++ b/src/phases/attempt-capture-phase.ts @@ -25,7 +25,8 @@ import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import { Gender } from "#app/data/gender"; import { BooleanHolder } from "#app/utils/common"; -import { ChallengeType, applyChallenges } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; export class AttemptCapturePhase extends PokemonPhase { public readonly phaseName = "AttemptCapturePhase"; diff --git a/src/phases/battle-end-phase.ts b/src/phases/battle-end-phase.ts index 38f8ef43d88..622800cc1d9 100644 --- a/src/phases/battle-end-phase.ts +++ b/src/phases/battle-end-phase.ts @@ -3,7 +3,8 @@ import { applyAbAttrs } from "#app/data/abilities/apply-ab-attrs"; import { LapsingPersistentModifier, LapsingPokemonHeldItemModifier } from "#app/modifier/modifier"; import { BattlePhase } from "./battle-phase"; import { BooleanHolder } from "#app/utils/common"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; export class BattleEndPhase extends BattlePhase { public readonly phaseName = "BattleEndPhase"; diff --git a/src/phases/party-heal-phase.ts b/src/phases/party-heal-phase.ts index 594f6f9072d..e93a5b642b9 100644 --- a/src/phases/party-heal-phase.ts +++ b/src/phases/party-heal-phase.ts @@ -1,7 +1,8 @@ import { globalScene } from "#app/global-scene"; import { BooleanHolder, fixedInt } from "#app/utils/common"; import { BattlePhase } from "./battle-phase"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; export class PartyHealPhase extends BattlePhase { public readonly phaseName = "PartyHealPhase"; diff --git a/src/phases/victory-phase.ts b/src/phases/victory-phase.ts index 9ccc28d6bd7..fa54b80558e 100644 --- a/src/phases/victory-phase.ts +++ b/src/phases/victory-phase.ts @@ -8,6 +8,7 @@ import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/util import { globalScene } from "#app/global-scene"; import { timedEventManager } from "#app/global-event-manager"; +import { ChallengeType } from "#enums/challenge-type"; import { BooleanHolder } from "#app/utils/common"; import { applyChallenges } from "#app/data/challenge"; @@ -111,7 +112,12 @@ export class VictoryPhase extends PokemonPhase { } if (!isHealPhaseActive.value) { //Push shop instead of healing phase for NoHealChallenge - globalScene.pushPhase(new SelectModifierPhase(undefined, undefined, this.getFixedBattleCustomModifiers())); + globalScene.phaseManager.pushNew( + "SelectModifierPhase", + undefined, + undefined, + this.getFixedBattleCustomModifiers(), + ); } } diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 68ee8f60c44..e9b3357ead5 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -16,7 +16,8 @@ import i18next from "i18next"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import Phaser from "phaser"; import type { PokeballType } from "#enums/pokeball"; -import { applyChallenges, ChallengeType } from "#app/data/challenge"; +import { applyChallenges } from "#app/data/challenge"; +import { ChallengeType } from "#enums/challenge-type"; import { BooleanHolder } from "#app/utils/common"; export const SHOP_OPTIONS_ROW_LIMIT = 7; From 68e1ee84f9549e6dbd93520da550982047e08fae Mon Sep 17 00:00:00 2001 From: mati-soda Date: Fri, 20 Jun 2025 14:28:58 +0100 Subject: [PATCH 4/6] =?UTF-8?q?Deleted=20the=20constraints=20of=20the=20au?= =?UTF-8?q?to=20heal=20challenge=20from=20the=20Mystery=20Ecounters=20Sign?= =?UTF-8?q?ed-off-by:=20Matilde=20Sim=C3=B5es=20=20Co-authored-by:=20Fuad=20Ali=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mystery-encounters/encounters/a-trainers-test-encounter.ts | 2 -- .../encounters/slumbering-snorlax-encounter.ts | 1 - .../encounters/the-winstrate-challenge-encounter.ts | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index bf6d2f5aabe..7a1c9821e89 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -22,7 +22,6 @@ import { EggTier } from "#enums/egg-type"; import { ModifierTier } from "#enums/modifier-tier"; import { modifierTypes } from "#app/data/data-lists"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; -import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/aTrainersTest"; @@ -35,7 +34,6 @@ const namespace = "mysteryEncounters/aTrainersTest"; export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType( MysteryEncounterType.A_TRAINERS_TEST, ) - .withDisallowedChallenges(Challenges.NO_AUTO_HEAL) .withEncounterTier(MysteryEncounterTier.ROGUE) .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withIntroSpriteConfigs([]) // These are set in onInit() diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 445d8b6f4ba..e4771ebe8b8 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -45,7 +45,6 @@ const namespace = "mysteryEncounters/slumberingSnorlax"; export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType( MysteryEncounterType.SLUMBERING_SNORLAX, ) - .withDisallowedChallenges(Challenges.NO_AUTO_HEAL) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(15, 150) .withCatchAllowed(true) diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 58d8196b8f4..6d28a710953 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -31,7 +31,6 @@ import i18next from "i18next"; import { ModifierTier } from "#enums/modifier-tier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { BattlerTagType } from "#enums/battler-tag-type"; -import { Challenges } from "#enums/challenges"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theWinstrateChallenge"; @@ -44,7 +43,6 @@ const namespace = "mysteryEncounters/theWinstrateChallenge"; export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType( MysteryEncounterType.THE_WINSTRATE_CHALLENGE, ) - .withDisallowedChallenges(Challenges.NO_AUTO_HEAL) .withEncounterTier(MysteryEncounterTier.ROGUE) .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withIntroSpriteConfigs([ From c4c4fdfd219df1aa77dbf405e02725accca9a2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilde=20Sim=C3=B5es?= Date: Wed, 2 Jul 2025 20:20:41 +0100 Subject: [PATCH 5/6] Updated No Free heal challenge to implement two other modes and applied minor adjustments on challenge.ts file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matilde Simões Co-authored-by: Fuad Ali --- src/data/challenge.ts | 74 +++++++++++++++++++++------- src/enums/challenge-type.ts | 4 ++ src/ui/modifier-select-ui-handler.ts | 21 ++++---- 3 files changed, 73 insertions(+), 26 deletions(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index cd68d2e0a95..bf491554725 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1,4 +1,4 @@ -import { BooleanHolder, type NumberHolder, randSeedItem } from "#app/utils/common"; +import { BooleanHolder, type NumberHolder, randSeedItem, isNullOrUndefined } from "#app/utils/common"; import { deepCopy } from "#app/utils/data"; import i18next from "i18next"; import type { DexAttrProps, GameData } from "#app/system/game-data"; @@ -357,13 +357,22 @@ export abstract class Challenge { return false; } + /** + * An apply function for NO_SHOP_PHASE. Derived classes should alter this. + * @param _applyShopPhase {@link BooleanHolder} Whether it should apply the shop phase. + * @returns {@link boolean} if this function did anything. + */ + applyNoShopPhase(_applyShopPhase: BooleanHolder): boolean { + return false; + } + /** * An apply function for PREVENT_REVIVE. Derived classes should alter this. * @param _canBeRevived {@link BooleanHolder} Whether it should revive the fainted Pokemon. * @returns {@link boolean} if this function did anything. */ applyRevivePrevention(_canBeRevived: BooleanHolder): boolean { - return true; + return false; } /** @@ -974,12 +983,23 @@ export class LowerStarterPointsChallenge extends Challenge { */ export class NoFreeHealsChallenge extends Challenge { constructor() { - super(Challenges.NO_AUTO_HEAL, 1); + super(Challenges.NO_AUTO_HEAL, 3); } applyNoHealPhase(applyHealPhase: BooleanHolder): boolean { - applyHealPhase.value = false; - return true; + if (this.value !== 1) { + applyHealPhase.value = false; + return true; + } + return false; + } + + applyNoShopPhase(applyShopPhase: BooleanHolder): boolean { + if (this.value !== 2) { + applyShopPhase.value = false; + return true; + } + return false; } static loadChallenge(source: NoFreeHealsChallenge | any): NoFreeHealsChallenge { @@ -1001,6 +1021,8 @@ export class HardcoreChallenge extends Challenge { "modifierType:ModifierType.REVIVER_SEED", ]; + private moveBlacklist = [MoveId.REVIVAL_BLESSING]; + constructor() { super(Challenges.HARDCORE, 2); } @@ -1018,9 +1040,11 @@ export class HardcoreChallenge extends Challenge { } applyMoveBlacklist(move: PokemonMove, moveCanBeUsed: BooleanHolder): boolean { - const moveBlacklist = [MoveId.REVIVAL_BLESSING]; - moveCanBeUsed.value = !moveBlacklist.includes(move.moveId); - return true; + if (this.moveBlacklist.includes(move.moveId)) { + moveCanBeUsed.value = false; + return true; + } + return false; } applyRevivePrevention(canBeRevived: BooleanHolder): boolean { @@ -1033,15 +1057,17 @@ export class HardcoreChallenge extends Challenge { canStay.value = false; } else { canStay.value = true; + return true; } - return true; + return false; } override applyShouldFuse(pokemon: Pokemon, pokemonToFuse: Pokemon, canFuse: BooleanHolder): boolean { if (pokemon!.isFainted() || pokemonToFuse.isFainted()) { canFuse.value = false; + return true; } - return true; + return false; } static override loadChallenge(source: HardcoreChallenge | any): HardcoreChallenge { @@ -1068,14 +1094,17 @@ export class LimitedCatchChallenge extends Challenge { } override applyAddPokemonToParty(waveIndex: number, canAddToParty: BooleanHolder): boolean { - const lastMystery = globalScene.lastMysteryEncounter?.encounterType; - if (lastMystery === undefined && !(waveIndex % 10 === 1)) { - canAddToParty.value = false; + if (waveIndex % 10 !== 1) { + const lastMystery = globalScene.lastMysteryEncounter?.encounterType; + if ( + isNullOrUndefined(lastMystery) || + !(waveIndex % 10 === 2 && !this.mysteryEncounterBlacklist.includes(lastMystery!)) + ) { + canAddToParty.value = false; + return true; + } + return false; } - if (!(waveIndex % 10 === 1) && !(!this.mysteryEncounterBlacklist.includes(lastMystery!) && waveIndex % 10 === 2)) { - canAddToParty.value = false; - } - return true; } static override loadChallenge(source: LimitedCatchChallenge | any): LimitedCatchChallenge { @@ -1244,6 +1273,14 @@ export function applyChallenges(challengeType: ChallengeType.FLIP_STAT, pokemon: * @returns True if any challenge was successfully applied. */ export function applyChallenges(challengeType: ChallengeType.NO_HEAL_PHASE, applyHealPhase: BooleanHolder): boolean; + +/** + * Apply all challenges that modify whether the shop will appear. + * @param challengeType {@link ChallengeType} ChallengeType.NO_SHOP_PHASE + * @param applyShopPhase {@link BooleanHolder} Whether it should apply the shop phase. + * @returns True if any challenge was successfully applied. + */ +export function applyChallenges(challengeType: ChallengeType.NO_SHOP_PHASE, applyShopPhase: BooleanHolder): boolean; /** * Apply all challenges that modify whether a shop item should be blacklisted. * @param challengeType {@link ChallengeType} ChallengeType.SHOP_ITEM_BLACKLIST @@ -1372,6 +1409,9 @@ export function applyChallenges(challengeType: ChallengeType, ...args: any[]): b case ChallengeType.NO_HEAL_PHASE: ret ||= c.applyNoHealPhase(args[0]); break; + case ChallengeType.NO_SHOP_PHASE: + ret ||= c.applyNoShopPhase(args[0]); + break; case ChallengeType.SHOP_ITEM_BLACKLIST: ret ||= c.applyShopItemBlacklist(args[0], args[1]); break; diff --git a/src/enums/challenge-type.ts b/src/enums/challenge-type.ts index 25371c8fffa..19bcd1fb172 100644 --- a/src/enums/challenge-type.ts +++ b/src/enums/challenge-type.ts @@ -70,6 +70,10 @@ export enum ChallengeType { * Challenge that modifies if the player should auto heal every 10th wave */ NO_HEAL_PHASE, + /** + * Challenge that modifies if the shop should appear + */ + NO_SHOP_PHASE, /** * Modifies if the shop item is blacklisted * @see {@linkcode Challenge.applyShopItemBlacklist} diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index e9b3357ead5..fe4921155eb 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -214,15 +214,18 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const removeHealShop = globalScene.gameMode.hasNoShop; const baseShopCost = new NumberHolder(globalScene.getWaveMoneyAmount(1)); globalScene.applyModifier(HealShopCostModifier, true, baseShopCost); - const shopTypeOptions = removeHealShop - ? [] - : getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value).filter( - shopItem => { - const isValidForChallenge = new BooleanHolder(true); - applyChallenges(ChallengeType.SHOP_ITEM_BLACKLIST, shopItem, isValidForChallenge); - return isValidForChallenge.value; - }, - ); + const isShopActive = new BooleanHolder(true); + applyChallenges(ChallengeType.NO_SHOP_PHASE, isShopActive); + const shopTypeOptions = + removeHealShop || !isShopActive.value + ? [] + : getPlayerShopModifierTypeOptionsForWave(globalScene.currentBattle.waveIndex, baseShopCost.value).filter( + shopItem => { + const isValidForChallenge = new BooleanHolder(true); + applyChallenges(ChallengeType.SHOP_ITEM_BLACKLIST, shopItem, isValidForChallenge); + return isValidForChallenge.value; + }, + ); const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; From 9dbe02de8bb896e23d5af04dfd370f3bd017b1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilde=20Sim=C3=B5es?= Date: Wed, 2 Jul 2025 20:47:24 +0100 Subject: [PATCH 6/6] Fixed minor issue on applyAddPokemonToParty. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matilde Simões Co-authored-by: Fuad Ali --- src/data/challenge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/challenge.ts b/src/data/challenge.ts index bf491554725..3b1d5ad33ca 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -1103,8 +1103,8 @@ export class LimitedCatchChallenge extends Challenge { canAddToParty.value = false; return true; } - return false; } + return false; } static override loadChallenge(source: LimitedCatchChallenge | any): LimitedCatchChallenge {