This commit is contained in:
Matilde Simões 2025-06-20 13:29:08 +00:00 committed by GitHub
commit feafb52ab0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 476 additions and 17 deletions

View File

@ -20,12 +20,14 @@ 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";
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 = [MoveId.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(),
);
}

View File

@ -37,6 +37,9 @@ import { CustomPokemonData } from "#app/data/custom-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 { 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;
@ -703,6 +706,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(

View File

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

View File

@ -6,4 +6,7 @@ export enum Challenges {
FRESH_START,
INVERSE_BATTLE,
FLIP_STAT,
NO_AUTO_HEAL,
HARDCORE,
LIMITED_CATCH,
}

View File

@ -3178,6 +3178,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;
}

View File

@ -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,8 @@ 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";
import { ChallengeType } from "#enums/challenge-type";
const outputModifierData = false;
const useMaxWeightForOutput = false;
@ -2613,10 +2616,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,
@ -2626,6 +2633,7 @@ function getModifierTypeOptionWithRetry(
0,
allowLuckUpgrades,
);
applyChallenges(ChallengeType.RANDOM_ITEM_BLACKLIST, candidate, isValidForChallenge);
}
return candidate!;
}
@ -2656,7 +2664,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 [];
}

View File

@ -45,6 +45,8 @@ import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters";
import { applyAbAttrs, applyPostItemLostAbAttrs } 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;
@ -2426,8 +2428,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
);
}

View File

@ -24,6 +24,9 @@ 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 { applyChallenges } from "#app/data/challenge";
import { ChallengeType } from "#enums/challenge-type";
export class AttemptCapturePhase extends PokemonPhase {
public readonly phaseName = "AttemptCapturePhase";
@ -285,6 +288,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(

View File

@ -2,6 +2,9 @@ import { globalScene } from "#app/global-scene";
import { applyPostBattleAbAttrs } 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 } from "#app/data/challenge";
import { ChallengeType } from "#enums/challenge-type";
export class BattleEndPhase extends BattlePhase {
public readonly phaseName = "BattleEndPhase";
@ -67,6 +70,16 @@ export class BattleEndPhase extends BattlePhase {
for (const pokemon of globalScene.getPokemonAllowedInBattle()) {
applyPostBattleAbAttrs("PostBattleAbAttr", pokemon, false, 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();

View File

@ -226,7 +226,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(

View File

@ -1,6 +1,8 @@
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 } from "#app/data/challenge";
import { ChallengeType } from "#enums/challenge-type";
export class PartyHealPhase extends BattlePhase {
public readonly phaseName = "PartyHealPhase";
@ -15,18 +17,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);
applyChallenges(ChallengeType.PREVENT_REVIVE, isReviveActive);
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;
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), () => {

View File

@ -8,6 +8,10 @@ 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";
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 +41,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 +110,15 @@ export class VictoryPhase extends PokemonPhase {
);
globalScene.phaseManager.pushNew("AddEnemyBuffModifierPhase");
}
if (!isHealPhaseActive.value) {
//Push shop instead of healing phase for NoHealChallenge
globalScene.phaseManager.pushNew(
"SelectModifierPhase",
undefined,
undefined,
this.getFixedBattleCustomModifiers(),
);
}
}
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {

View File

@ -16,6 +16,9 @@ import i18next from "i18next";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
import Phaser from "phaser";
import type { PokeballType } from "#enums/pokeball";
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;
const SINGLE_SHOP_ROW_YOFFSET = 12;
@ -211,9 +214,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;