Compare commits

..

17 Commits

Author SHA1 Message Date
ImperialSympathizer
bb21de335f
Merge pull request #4237 from ben-lear/mystery-encounters
update ME tsdocs with links
2024-09-13 21:52:04 -04:00
ImperialSympathizer
1533af5b5d update ME tsdocs with links 2024-09-13 21:50:50 -04:00
ImperialSympathizer
6159d938b6
Merge pull request #4236 from ben-lear/mystery-encounters
update ME tsdocs with links
2024-09-13 21:45:18 -04:00
ImperialSympathizer
9ccb15ee8b update ME tsdocs with links 2024-09-13 21:44:43 -04:00
ImperialSympathizer
ae88966a11
Merge pull request #4235 from ben-lear/mystery-encounters
more ME nits
2024-09-13 21:12:21 -04:00
ImperialSympathizer
cc0220e30a more nits 2024-09-13 21:11:19 -04:00
ImperialSympathizer
2970ca530b
Merge pull request #4233 from ben-lear/mystery-encounters
MEs nits and money formatting
2024-09-13 21:05:28 -04:00
ImperialSympathizer
894d71631d more nits 2024-09-13 21:04:47 -04:00
ImperialSympathizer
550fe76eb1 fix money formatting and some nits 2024-09-13 21:03:26 -04:00
ImperialSympathizer
393b733cc9
Update src/ui/modifier-select-ui-handler.ts
Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com>
2024-09-13 19:34:51 -04:00
ImperialSympathizer
f8f157a319
Merge pull request #4229 from ben-lear/mystery-encounters
Change how reroll button gets disabled in Modifier Shop Phase
2024-09-13 19:29:09 -04:00
ImperialSympathizer
d4565158b1 update continue button text logic 2024-09-13 19:28:05 -04:00
ImperialSympathizer
28dd929da2 Change how reroll button gets disabled in Modifier Shop Phase 2024-09-13 19:15:31 -04:00
ImperialSympathizer
d51f294c03
Merge pull request #4222 from ben-lear/mystery-encounters
fix unit test
2024-09-13 16:50:54 -04:00
ImperialSympathizer
e232fc0f52 fix unit test 2024-09-13 16:50:09 -04:00
ImperialSympathizer
36e3a0d2a9
Merge pull request #4221 from ben-lear/mystery-encounters
more PR feedback and update field trip with Rarer Candy
2024-09-13 16:45:48 -04:00
ImperialSympathizer
0e7f9dc723 more PR feedback and update field trip with Rarer Candy 2024-09-13 16:44:42 -04:00
58 changed files with 435 additions and 355 deletions

View File

@ -265,7 +265,9 @@ export default class BattleScene extends SceneBase {
public money: integer;
public pokemonInfoContainer: PokemonInfoContainer;
private party: PlayerPokemon[];
/** Session save data that pertains to Mystery Encounters */
public mysteryEncounterSaveData: MysteryEncounterSaveData = new MysteryEncounterSaveData();
/** If the previous wave was a MysteryEncounter, tracks the object with this variable. Mostly used for visual object cleanup */
public lastMysteryEncounter?: MysteryEncounter;
/** Combined Biome and Wave count text */
private biomeWaveText: Phaser.GameObjects.Text;

View File

@ -80,6 +80,7 @@ export default class Battle {
public playerFaintsHistory: FaintLogEntry[] = [];
public enemyFaintsHistory: FaintLogEntry[] = [];
/** If the current battle is a Mystery Encounter, this will always be defined */
public mysteryEncounter?: MysteryEncounter;
private rngCounter: number = 0;

View File

@ -9,6 +9,7 @@ import { Moves } from "#enums/moves";
import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils";
import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims";
//import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget {
@ -105,18 +106,6 @@ export enum CommonAnim {
LOCK_ON = 2120
}
/**
* Animations used for Mystery Encounters
* These are custom animations that may or may not work in any other circumstance
* Use at your own risk
*/
export enum EncounterAnim {
MAGMA_BG,
MAGMA_SPOUT,
SMOKESCREEN,
DANCE
}
export class AnimConfig {
public id: integer;
public graphic: string;

View File

@ -2315,10 +2315,12 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
super(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON, BattlerTagLapseType.CUSTOM, 1);
}
/** Event when tag is added */
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
}
/** Performs post-summon effects through {@linkcode Pokemon.mysteryEncounterBattleEffects} */
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
@ -2335,6 +2337,7 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
return ret;
}
/** Event when tag is removed */
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
}

View File

@ -63,8 +63,8 @@ export interface IEggOptions {
*/
overrideHiddenAbility?: boolean,
/** If Egg is of {@link EggSourceType.EVENT}, can customize the message displayed for where the egg was obtained */
eventEggTypeDescriptor?: string;
/** Can customize the message displayed for where the egg was obtained */
eggDescriptor?: string;
}
export class Egg {
@ -86,7 +86,7 @@ export class Egg {
private _overrideHiddenAbility: boolean;
private _eventEggTypeDescriptor?: string;
private _eggDescriptor?: string;
////
// #endregion
@ -197,7 +197,7 @@ export class Egg {
generateEggProperties(eggOptions);
}
this._eventEggTypeDescriptor = eggOptions?.eventEggTypeDescriptor;
this._eggDescriptor = eggOptions?.eggDescriptor;
}
////
@ -299,15 +299,15 @@ export class Egg {
public getEggTypeDescriptor(scene: BattleScene): string {
switch (this.sourceType) {
case EggSourceType.SAME_SPECIES_EGG:
return i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName()});
return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName()});
case EggSourceType.GACHA_LEGENDARY:
return `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`;
return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`;
case EggSourceType.GACHA_SHINY:
return i18next.t("egg:gachaTypeShiny");
return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny");
case EggSourceType.GACHA_MOVE:
return i18next.t("egg:gachaTypeMove");
return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove");
case EggSourceType.EVENT:
return this._eventEggTypeDescriptor ?? i18next.t("egg:eventType");
return this._eggDescriptor ?? i18next.t("egg:eventType");
default:
console.warn("getEggTypeDescriptor case not defined. Returning default empty string");
return "";

View File

@ -96,7 +96,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
encounter.misc = { trainerType, trainerNameKey, trainerEggDescription: eggDescription };
// Trainer config
const trainerConfig = trainerConfigs[trainerType].copy();
const trainerConfig = trainerConfigs[trainerType].clone();
const trainerSpriteKey = trainerConfig.getSpriteKey();
encounter.enemyPartyConfigs.push({
levelAdditiveMultiplier: 1,
@ -147,7 +147,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eventEggTypeDescriptor: encounter.misc.trainerEggDescription,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.ULTRA
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`));
@ -170,11 +170,11 @@ export const ATrainersTestEncounter: MysteryEncounter =
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eventEggTypeDescriptor: encounter.misc.trainerEggDescription,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.GREAT
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.rare`));
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: 0 }, [eggOptions]);
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [eggOptions]);
leaveEncounterWithoutBattle(scene);
}
)

View File

@ -23,6 +23,7 @@ import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:absoluteAvarice";
@ -35,7 +36,7 @@ const namespace = "mysteryEncounter:absoluteAvarice";
export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Must have at least 4 berries to spawn
.withIntroSpriteConfigs([
{

View File

@ -12,6 +12,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse";
@ -24,7 +25,7 @@ const namespace = "mysteryEncounter:offerYouCantRefuse";
export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
.withIntroSpriteConfigs([
{

View File

@ -30,6 +30,7 @@ import i18next from "#app/plugins/i18n";
import { BerryType } from "#enums/berry-type";
import { PERMANENT_STATS, Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:berriesAbound";
@ -42,7 +43,7 @@ const namespace = "mysteryEncounter:berriesAbound";
export const BerriesAboundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([]) // Set in onInit()

View File

@ -49,6 +49,7 @@ import i18next from "i18next";
import MoveInfoOverlay from "#app/ui/move-info-overlay";
import { allMoves } from "#app/data/move";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:bugTypeSuperfan";
@ -191,7 +192,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1),
new TypeRequirement(Type.BUG, false, 1)
))
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit()
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
@ -439,7 +440,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
function getTrainerConfigForWave(waveIndex: number) {
// Bug type superfan trainer config
const config = trainerConfigs[TrainerType.BUG_TYPE_SUPERFAN].copy();
const config = trainerConfigs[TrainerType.BUG_TYPE_SUPERFAN].clone();
config.name = i18next.t("trainerNames:bug_type_superfan");
const pool3Copy = POOL_3_POKEMON.slice(0);

View File

@ -26,10 +26,11 @@ import { BerryModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#app/battle";
import { Moves } from "#enums/moves";
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#app/data/move";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { GameModes } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, GameModes } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:clowningAround";
@ -61,7 +62,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withDisabledGameModes(GameModes.CHALLENGE)
.withSceneWaveRangeRequirement(80, 180)
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withAnimations(EncounterAnim.SMOKESCREEN)
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
@ -107,7 +108,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!;
const clownTrainerType = TrainerType.HARLEQUIN;
const clownConfig = trainerConfigs[clownTrainerType].copy();
const clownConfig = trainerConfigs[clownTrainerType].clone();
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));

View File

@ -12,7 +12,7 @@ import { Moves } from "#enums/moves";
import { TrainerSlot } from "#app/data/trainer-config";
import PokemonData from "#app/system/pokemon-data";
import { Biome } from "#enums/biome";
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { BattlerTagType } from "#enums/battler-tag-type";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
@ -25,6 +25,8 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:dancingLessons";
@ -81,7 +83,7 @@ const SENSU_STYLE_BIOMES = [
export const DancingLessonsEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // Uses a real Pokemon sprite instead of ME Intro Visuals
.withAnimations(EncounterAnim.DANCE)
.withHideWildIntroMessage(true)

View File

@ -13,6 +13,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:darkDeal";
@ -100,7 +101,7 @@ export const DarkDealEncounter: MysteryEncounter =
text: `${namespace}.intro_dialogue`,
},
])
.withSceneWaveRangeRequirement(30, 180) // waves 30 to 180
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
.withCatchAllowed(true)
.withTitle(`${namespace}.title`)

View File

@ -16,6 +16,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u
import i18next from "#app/plugins/i18n";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:delibirdy";
@ -40,7 +41,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
export const DelibirdyEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, 2)) // Must have enough money for it to spawn at the very least
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // Must also have either option 2 or 3 available to spawn
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),

View File

@ -11,6 +11,7 @@ import MysteryEncounter, {
MysteryEncounterBuilder,
} from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:departmentStoreSale";
@ -23,7 +24,7 @@ const namespace = "mysteryEncounter:departmentStoreSale";
export const DepartmentStoreSaleEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 100)
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
.withIntroSpriteConfigs([
{
spriteKey: "b2w2_lady",

View File

@ -11,6 +11,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Stat } from "#enums/stat";
import i18next from "i18next";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieldTrip";
@ -23,7 +24,7 @@ const namespace = "mysteryEncounter:fieldTrip";
export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([
{
spriteKey: "preschooler_m",
@ -90,6 +91,7 @@ export const FieldTripEncounter: MysteryEncounter =
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.DEF])!,
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!,
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
@ -135,6 +137,7 @@ export const FieldTripEncounter: MysteryEncounter =
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPDEF])!,
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
generateModifierTypeOption(scene, modifierTypes.DIRE_HIT)!,
generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!,
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });
@ -180,6 +183,7 @@ export const FieldTripEncounter: MysteryEncounter =
generateModifierTypeOption(scene, modifierTypes.TEMP_STAT_STAGE_BOOSTER, [Stat.SPD])!,
generateModifierTypeOption(scene, modifierTypes.GREAT_BALL)!,
generateModifierTypeOption(scene, modifierTypes.IV_SCANNER)!,
generateModifierTypeOption(scene, modifierTypes.RARER_CANDY)!,
];
setEncounterRewards(scene, { guaranteedModifierTypeOptions: modifiers, fillRemaining: false });

View File

@ -12,7 +12,7 @@ import { Type } from "#app/data/type";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { Moves } from "#enums/moves";
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#app/data/weather";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { StatusEffect } from "#app/data/status-effect";
@ -20,6 +20,8 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fieryFallout";
@ -39,7 +41,7 @@ const DAMAGE_PERCENTAGE: number = 20;
export const FieryFalloutEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(40, 180)
.withSceneWaveRangeRequirement(40, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withCatchAllowed(true)
.withIntroSpriteConfigs([]) // Set in onInit()
.withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT)

View File

@ -28,6 +28,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { randSeedInt } from "#app/utils";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:fightOrFlight";
@ -40,7 +41,7 @@ const namespace = "mysteryEncounter:fightOrFlight";
export const FightOrFlightEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([]) // Set in onInit()

View File

@ -21,6 +21,7 @@ import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { modifierTypes } from "#app/modifier/modifier-type";
import { Nature } from "#enums/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:funAndGames";
@ -33,7 +34,7 @@ const namespace = "mysteryEncounter:funAndGames";
export const FunAndGamesEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FUN_AND_GAMES)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Cost equal to 1 Max Potion to play
.withAutoHideIntroVisuals(false)
// Allows using move without a visible enemy pokemon

View File

@ -22,6 +22,7 @@ import { getNatureName } from "#app/data/nature";
import { getPokeballAtlasKey, getPokeballTintColor, PokeballType } from "#app/data/pokeball";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { trainerNamePools } from "#app/data/trainer-names";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:globalTradeSystem";
@ -70,7 +71,7 @@ const EXCLUDED_TRADE_SPECIES = [
export const GlobalTradeSystemEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.GLOBAL_TRADE_SYSTEM)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
{

View File

@ -9,6 +9,7 @@ import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
const OPTION_1_REQUIRED_MOVE = Moves.SURF;
const OPTION_2_REQUIRED_MOVE = Moves.FLY;
@ -28,7 +29,7 @@ const namespace = "mysteryEncounter:lostAtSea";
*/
export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.LOST_AT_SEA)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(11, 179)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([
{
spriteKey: "buoy",

View File

@ -17,6 +17,7 @@ import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:mysteriousChallengers";
@ -29,7 +30,7 @@ const namespace = "mysteryEncounter:mysteriousChallengers";
export const MysteriousChallengersEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([
{
@ -42,7 +43,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
// Normal difficulty trainer is randomly pulled from biome
const normalTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex);
const normalConfig = trainerConfigs[normalTrainerType].copy();
const normalConfig = trainerConfigs[normalTrainerType].clone();
let female = false;
if (normalConfig.hasGenders) {
female = !!Utils.randSeedInt(2);
@ -65,7 +66,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
true
)
);
const hardConfig = trainerConfigs[hardTrainerType].copy();
const hardConfig = trainerConfigs[hardTrainerType].clone();
hardConfig.setPartyTemplates(hardTemplate);
female = false;
if (hardConfig.hasGenders) {
@ -85,7 +86,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
true
);
const e4Template = trainerPartyTemplates.ELITE_FOUR;
const brutalConfig = trainerConfigs[brutalTrainerType].copy();
const brutalConfig = trainerConfigs[brutalTrainerType].clone();
brutalConfig.setPartyTemplates(e4Template);
// @ts-ignore
brutalConfig.partyTemplateFunc = null; // Overrides gym leader party template func

View File

@ -13,6 +13,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { Moves } from "#enums/moves";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:mysteriousChest";
@ -31,7 +32,7 @@ const MASTER_REWARDS_WEIGHT = 65; // 5%
export const MysteriousChestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withAutoHideIntroVisuals(false)
.withCatchAllowed(true)
.withIntroSpriteConfigs([

View File

@ -11,6 +11,7 @@ import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requir
import { getEncounterText, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "i18next";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:partTimer";
@ -23,7 +24,7 @@ const namespace = "mysteryEncounter:partTimer";
export const PartTimerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([
{
spriteKey: "warehouse_crate",

View File

@ -18,6 +18,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:safariZone";
@ -34,7 +35,7 @@ const SAFARI_MONEY_MULTIPLIER = 2.75;
export const SafariZoneEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([

View File

@ -14,6 +14,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Nature } from "#enums/nature";
import { getNatureName } from "#app/data/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:shadyVitaminDealer";
@ -26,7 +27,7 @@ const namespace = "mysteryEncounter:shadyVitaminDealer";
export const ShadyVitaminDealerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Must have the money for at least the cheap deal
.withPrimaryPokemonHealthRatioRequirement([0.5, 1]) // At least 1 Pokemon must have above half HP
.withIntroSpriteConfigs([

View File

@ -16,6 +16,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounter:slumberingSnorlax";
@ -28,7 +29,7 @@ const namespace = "mysteryEncounter:slumberingSnorlax";
export const SlumberingSnorlaxEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([

View File

@ -19,6 +19,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:teleportingHijinks";
@ -35,7 +36,7 @@ const MACHINE_INTERFACING_TYPES = [Type.ELECTRIC, Type.STEEL];
export const TeleportingHijinksEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TELEPORTING_HIJINKS)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
.withSceneRequirement(new MoneyRequirement(undefined, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
.withAutoHideIntroVisuals(false)

View File

@ -14,6 +14,7 @@ import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encoun
import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:pokemonSalesman";
@ -28,7 +29,7 @@ const MAX_POKEMON_PRICE_MULTIPLIER = 6;
export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([

View File

@ -17,6 +17,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theStrongStuff";
@ -33,7 +34,7 @@ const BST_INCREASE_VALUE = 10;
export const TheStrongStuffEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
.withHideWildIntroMessage(true)
.withAutoHideIntroVisuals(false)

View File

@ -22,6 +22,7 @@ import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import i18next from "i18next";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theWinstrateChallenge";
@ -34,7 +35,7 @@ const namespace = "mysteryEncounter:theWinstrateChallenge";
export const TheWinstrateChallengeEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(100, 180)
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withIntroSpriteConfigs([
{
spriteKey: "vito",

View File

@ -19,6 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
import i18next from "i18next";
import { getStatKey } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** The i18n namespace for the encounter */
const namespace = "mysteryEncounter:trainingSession";
@ -31,7 +32,7 @@ const namespace = "mysteryEncounter:trainingSession";
export const TrainingSessionEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([

View File

@ -17,6 +17,7 @@ import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:trashToTreasure";
@ -31,7 +32,7 @@ const SOUND_EFFECT_WAIT_TIME = 700;
export const TrashToTreasureEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(60, 180)
.withSceneWaveRangeRequirement(60, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withMaxAllowedEncounters(1)
.withIntroSpriteConfigs([
{

View File

@ -23,6 +23,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { BerryModifier } from "#app/modifier/modifier";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:uncommonBreed";
@ -35,7 +36,7 @@ const namespace = "mysteryEncounter:uncommonBreed";
export const UncommonBreedEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.UNCOMMON_BREED)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withCatchAllowed(true)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([]) // Set in onInit()

View File

@ -20,7 +20,7 @@ import i18next from "#app/plugins/i18n";
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
import { getLevelTotalExp } from "#app/data/exp";
import { Stat } from "#enums/stat";
import { GameModes } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, GameModes } from "#app/game-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:weirdDream";
@ -82,10 +82,16 @@ const SUPER_LEGENDARY_BST_THRESHOLD = 600;
const NON_LEGENDARY_BST_THRESHOLD = 570;
const GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD = 450;
/** Value ranges of the resulting species BST transformations after adding values to original species */
const HIGH_BST_TRANSFORM_BASE_VALUES = [90, 110];
const STANDARD_BST_TRANSFORM_BASE_VALUES = [40, 50];
/**
* Value ranges of the resulting species BST transformations after adding values to original species
* 2 Pokemon in the party use this range
*/
const HIGH_BST_TRANSFORM_BASE_VALUES: [number, number] = [90, 110];
/**
* Value ranges of the resulting species BST transformations after adding values to original species
* All remaining Pokemon in the party use this range
*/
const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [40, 50];
/**
* Weird Dream encounter.
@ -96,7 +102,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withDisabledGameModes(GameModes.CHALLENGE)
.withSceneWaveRangeRequirement(10, 180)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([
{
spriteKey: "weird_dream_woman",
@ -252,7 +258,7 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
scene.removePokemonFromPlayerParty(removed, false);
const bst = removed.calculateBaseStats().reduce((a, b) => a + b, 0);
let newBstRange;
let newBstRange: [number, number];
if (i < 2) {
newBstRange = HIGH_BST_TRANSFORM_BASE_VALUES;
} else {

View File

@ -20,7 +20,8 @@ export class EncounterOptionsDialogue {
title?: string;
description?: string;
query?: string;
options?: [...OptionTextDisplay[]]; // Options array with minimum 2 options
/** Options array with minimum 2 options */
options?: [...OptionTextDisplay[]];
}
/**

View File

@ -44,7 +44,7 @@ export default class MysteryEncounterOption implements IMysteryEncounterOption {
/**
* Dialogue object containing all the dialogue, messages, tooltips, etc. for this option
* Will be populated on MysteryEncounter initialization
* Will be populated on {@linkcode MysteryEncounter} initialization
*/
dialogue?: OptionTextDisplay;

View File

@ -2,6 +2,10 @@ import { Abilities } from "#enums/abilities";
import { Type } from "#app/data/type";
import { isNullOrUndefined } from "#app/utils";
/**
* Data that can customize a Pokemon in non-standard ways from its Species
* Currently only used by Mystery Encounters, may need to be renamed if it becomes more widely used
*/
export class MysteryEncounterPokemonData {
public spriteScale: number;
public ability: Abilities | -1;

View File

@ -53,7 +53,7 @@ export class CombinationSceneRequirement extends EncounterSceneRequirement {
return false;
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
@ -99,7 +99,7 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
this.orRequirements = orRequirements;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return true;
@ -108,7 +108,7 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
return false;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
for (const req of this.orRequirements) {
const result = req.queryParty(partyPokemon);
if (result?.length > 0) {
@ -119,7 +119,7 @@ export class CombinationPokemonRequirement extends EncounterPokemonRequirement {
return [];
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
for (const req of this.orRequirements) {
if (req.meetsRequirement(scene)) {
return req.getDialogueToken(scene, pokemon);
@ -142,11 +142,11 @@ export class PreviousEncounterRequirement extends EncounterSceneRequirement {
this.previousEncounterRequirement = previousEncounterRequirement;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
return scene.mysteryEncounterSaveData.encounteredEvents.some(e => e.type === this.previousEncounterRequirement);
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["previousEncounter", scene.mysteryEncounterSaveData.encounteredEvents.find(e => e.type === this.previousEncounterRequirement)?.[0].toString() ?? ""];
}
}
@ -164,7 +164,7 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
this.waveRange = waveRange;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
if (!isNullOrUndefined(this.waveRange) && this.waveRange?.[0] <= this.waveRange?.[1]) {
const waveIndex = scene.currentBattle.waveIndex;
if (waveIndex >= 0 && (this.waveRange?.[0] >= 0 && this.waveRange?.[0] > waveIndex) || (this.waveRange?.[1] >= 0 && this.waveRange?.[1] < waveIndex)) {
@ -174,7 +174,7 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
return true;
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["waveIndex", scene.currentBattle.waveIndex.toString()];
}
}
@ -199,11 +199,11 @@ export class WaveModulusRequirement extends EncounterSceneRequirement {
this.modulusValue = modulusValue;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
return this.waveModuli.includes(scene.currentBattle.waveIndex % this.modulusValue);
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["waveIndex", scene.currentBattle.waveIndex.toString()];
}
}
@ -216,7 +216,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
this.requiredTimeOfDay = Array.isArray(timeOfDay) ? timeOfDay : [timeOfDay];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const timeOfDay = scene.arena?.getTimeOfDay();
if (!isNullOrUndefined(timeOfDay) && this.requiredTimeOfDay?.length > 0 && !this.requiredTimeOfDay.includes(timeOfDay)) {
return false;
@ -225,7 +225,7 @@ export class TimeOfDayRequirement extends EncounterSceneRequirement {
return true;
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["timeOfDay", TimeOfDay[scene.arena.getTimeOfDay()].toLocaleLowerCase()];
}
}
@ -238,7 +238,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
this.requiredWeather = Array.isArray(weather) ? weather : [weather];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const currentWeather = scene.arena.weather?.weatherType;
if (!isNullOrUndefined(currentWeather) && this.requiredWeather?.length > 0 && !this.requiredWeather.includes(currentWeather!)) {
return false;
@ -247,7 +247,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
return true;
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const currentWeather = scene.arena.weather?.weatherType;
let token = "";
if (!isNullOrUndefined(currentWeather)) {
@ -273,7 +273,7 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
this.excludeFainted = excludeFainted;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
const partySize = this.excludeFainted ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
if (partySize >= 0 && (this.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
@ -284,7 +284,7 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
return true;
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["partySize", scene.getParty().length.toString()];
}
}
@ -299,7 +299,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) {
return false;
@ -317,7 +317,7 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement {
return modifierCount >= this.minNumberOfItems;
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["requiredItem", this.requiredHeldItemModifiers[0]];
}
}
@ -332,7 +332,7 @@ export class MoneyRequirement extends EncounterSceneRequirement {
this.scalingMultiplier = scalingMultiplier ?? 0;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const money = scene.money;
if (isNullOrUndefined(money)) {
return false;
@ -344,7 +344,7 @@ export class MoneyRequirement extends EncounterSceneRequirement {
return !(this.requiredMoney > 0 && this.requiredMoney > money);
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const value = this.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString();
return ["money", value];
}
@ -362,7 +362,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
this.requiredSpecies = Array.isArray(species) ? species : [species];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredSpecies?.length < 0) {
return false;
@ -370,7 +370,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredSpecies.filter((species) => pokemon.species.speciesId === species).length > 0);
} else {
@ -379,7 +379,7 @@ export class SpeciesRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (pokemon?.species.speciesId && this.requiredSpecies.includes(pokemon.species.speciesId)) {
return ["species", Species[pokemon.species.speciesId]];
}
@ -400,7 +400,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
this.requiredNature = Array.isArray(nature) ? nature : [nature];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredNature?.length < 0) {
return false;
@ -408,7 +408,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredNature.filter((nature) => pokemon.nature === nature).length > 0);
} else {
@ -417,7 +417,7 @@ export class NatureRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon!.nature)) {
return ["nature", Nature[pokemon!.nature]];
}
@ -439,7 +439,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
this.requiredType = Array.isArray(type) ? type : [type];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
let partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon)) {
@ -453,7 +453,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredType.filter((type) => pokemon.getTypes().includes(type)).length > 0);
} else {
@ -462,7 +462,7 @@ export class TypeRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const includedTypes = this.requiredType.filter((ty) => pokemon?.getTypes().includes(ty));
if (includedTypes.length > 0) {
return ["type", Type[includedTypes[0]]];
@ -484,7 +484,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
this.requiredMoves = Array.isArray(moves) ? moves : [moves];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
return false;
@ -492,7 +492,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0);
} else {
@ -501,7 +501,7 @@ export class MoveRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const includedMoves = pokemon?.moveset.filter((move) => move?.moveId && this.requiredMoves.includes(move.moveId));
if (includedMoves && includedMoves.length > 0 && includedMoves[0]) {
return ["move", includedMoves[0].getName()];
@ -528,7 +528,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
this.requiredMoves = Array.isArray(learnableMove) ? learnableMove : [learnableMove];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredMoves?.length < 0) {
return false;
@ -536,7 +536,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredMoves.filter((learnableMove) => pokemon.compatibleTms.filter(tm => !pokemon.moveset.find(m => m?.moveId === tm)).includes(learnableMove)).length > 0);
} else {
@ -545,7 +545,7 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const includedCompatMoves = this.requiredMoves.filter((reqMove) => pokemon?.compatibleTms.filter((tm) => !pokemon.moveset.find(m => m?.moveId === tm)).includes(reqMove));
if (includedCompatMoves.length > 0) {
return ["compatibleMove", Moves[includedCompatMoves[0]]];
@ -555,46 +555,6 @@ export class CompatibleMoveRequirement extends EncounterPokemonRequirement {
}
/*
export class EvolutionTargetSpeciesRequirement extends EncounterPokemonRequirement {
requiredEvolutionTargetSpecies: Species[];
minNumberOfPokemon:number;
invertQuery:boolean;
constructor(evolutionTargetSpecies: Species | Species[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
super();
this.minNumberOfPokemon = minNumberOfPokemon;
this.invertQuery = invertQuery;
this.requiredEvolutionTargetSpecies = Array.isArray(evolutionTargetSpecies) ? evolutionTargetSpecies : [evolutionTargetSpecies];
}
meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionTargetSpecies?.length < 0) {
return false;
}
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredEvolutionTargetSpecies.filter((evolutionTargetSpecies) => pokemon.getEvolution()?.speciesId === evolutionTargetSpecies).length > 0);
} else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed evolutionTargetSpeciess
return partyPokemon.filter((pokemon) => this.requiredEvolutionTargetSpecies.filter((evolutionTargetSpecies) => pokemon.getEvolution()?.speciesId === evolutionTargetSpecies).length === 0);
}
}
getMatchingDialogueToken(str:string, pokemon: PlayerPokemon): [RegExp, string] {
const evos = this.requiredEvolutionTargetSpecies.filter((evolutionTargetSpecies) => pokemon.getEvolution().speciesId === evolutionTargetSpecies);
if (evos.length > 0) {
return ["evolution", Species[evos[0]]];
}
return ["evolution", ""];
}
}*/
export class AbilityRequirement extends EncounterPokemonRequirement {
requiredAbilities: Abilities[];
minNumberOfPokemon: number;
@ -607,7 +567,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
this.requiredAbilities = Array.isArray(abilities) ? abilities : [abilities];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredAbilities?.length < 0) {
return false;
@ -615,7 +575,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability));
} else {
@ -624,7 +584,7 @@ export class AbilityRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) {
return ["ability", pokemon.getAbility().name];
}
@ -644,7 +604,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
this.requiredStatusEffect = Array.isArray(statusEffect) ? statusEffect : [statusEffect];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredStatusEffect?.length < 0) {
return false;
@ -654,7 +614,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
return x;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => {
return this.requiredStatusEffect.some((statusEffect) => {
@ -682,7 +642,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const reqStatus = this.requiredStatusEffect.filter((a) => {
if (a === StatusEffect.NONE) {
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon!.status!.effect) || pokemon!.status!.effect === a;
@ -714,7 +674,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
this.requiredFormChangeItem = Array.isArray(formChangeItem) ? formChangeItem : [formChangeItem];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredFormChangeItem?.length < 0) {
return false;
@ -735,7 +695,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
}
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem)).length > 0);
} else {
@ -744,7 +704,7 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredFormChangeItem.filter((formChangeItem) => this.filterByForm(pokemon, formChangeItem));
if (requiredItems.length > 0) {
return ["formChangeItem", FormChangeItem[requiredItems[0]]];
@ -766,7 +726,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
this.requiredEvolutionItem = Array.isArray(evolutionItems) ? evolutionItems : [evolutionItems];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon) || this.requiredEvolutionItem?.length < 0) {
return false;
@ -785,7 +745,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
return false;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredEvolutionItem.filter((evolutionItem) => this.filterByEvo(pokemon, evolutionItem)).length > 0);
} else {
@ -794,7 +754,7 @@ export class CanEvolveWithItemRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const requiredItems = this.requiredEvolutionItem.filter((evoItem) => this.filterByEvo(pokemon, evoItem));
if (requiredItems.length > 0) {
return ["evolutionItem", EvolutionItem[requiredItems[0]]];
@ -815,7 +775,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon)) {
return false;
@ -823,7 +783,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
return pokemon.getHeldItems().some((it) => {
@ -839,7 +799,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => {
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
});
@ -862,7 +822,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
const partyPokemon = scene.getParty();
if (isNullOrUndefined(partyPokemon)) {
return false;
@ -870,7 +830,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
return this.queryParty(partyPokemon).length >= this.minNumberOfPokemon;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => this.requiredHeldItemTypes.some((heldItemType) => {
return pokemon.getHeldItems().some((it) => {
@ -886,7 +846,7 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const requiredItems = pokemon?.getHeldItems().filter((it) => {
return this.requiredHeldItemTypes.some(heldItemType => it instanceof AttackTypeBoosterModifier && (it.type as AttackTypeBoosterModifierType).moveType === heldItemType);
});
@ -909,7 +869,7 @@ export class LevelRequirement extends EncounterPokemonRequirement {
this.requiredLevelRange = requiredLevelRange;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required level range
if (!isNullOrUndefined(this.requiredLevelRange) && this.requiredLevelRange[0] <= this.requiredLevelRange[1]) {
const partyPokemon = scene.getParty();
@ -921,7 +881,7 @@ export class LevelRequirement extends EncounterPokemonRequirement {
return true;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.level >= this.requiredLevelRange[0] && pokemon.level <= this.requiredLevelRange[1]);
} else {
@ -930,7 +890,7 @@ export class LevelRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["level", pokemon?.level.toString() ?? ""];
}
}
@ -947,7 +907,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
this.requiredFriendshipRange = requiredFriendshipRange;
}
meetsRequirement(scene: BattleScene): boolean {
override meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required friendship range
if (!isNullOrUndefined(this.requiredFriendshipRange) && this.requiredFriendshipRange[0] <= this.requiredFriendshipRange[1]) {
const partyPokemon = scene.getParty();
@ -959,7 +919,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
return true;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.friendship >= this.requiredFriendshipRange[0] && pokemon.friendship <= this.requiredFriendshipRange[1]);
} else {
@ -968,7 +928,7 @@ export class FriendshipRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["friendship", pokemon?.friendship.toString() ?? ""];
}
}
@ -990,8 +950,8 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
this.requiredHealthRange = requiredHealthRange;
}
meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required level range
override meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon's health inside required health range
if (!isNullOrUndefined(this.requiredHealthRange) && this.requiredHealthRange[0] <= this.requiredHealthRange[1]) {
const partyPokemon = scene.getParty();
const pokemonInRange = this.queryParty(partyPokemon);
@ -1002,7 +962,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
return true;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => {
return pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1];
@ -1013,7 +973,7 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (!isNullOrUndefined(pokemon?.getHpRatio())) {
return ["healthRatio", Math.floor(pokemon!.getHpRatio() * 100).toString() + "%"];
}
@ -1033,8 +993,8 @@ export class WeightRequirement extends EncounterPokemonRequirement {
this.requiredWeightRange = requiredWeightRange;
}
meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon inside required friendship range
override meetsRequirement(scene: BattleScene): boolean {
// Party Pokemon's weight inside required weight range
if (!isNullOrUndefined(this.requiredWeightRange) && this.requiredWeightRange[0] <= this.requiredWeightRange[1]) {
const partyPokemon = scene.getParty();
const pokemonInRange = this.queryParty(partyPokemon);
@ -1045,7 +1005,7 @@ export class WeightRequirement extends EncounterPokemonRequirement {
return true;
}
queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] {
if (!this.invertQuery) {
return partyPokemon.filter((pokemon) => pokemon.getWeight() >= this.requiredWeightRange[0] && pokemon.getWeight() <= this.requiredWeightRange[1]);
} else {
@ -1054,7 +1014,7 @@ export class WeightRequirement extends EncounterPokemonRequirement {
}
}
getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
return ["weight", pokemon?.getWeight().toString() ?? ""];
}
}

View File

@ -10,11 +10,11 @@ import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter
import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements";
import { BattlerIndex } from "#app/battle";
import { EncounterAnim } from "#app/data/battle-anims";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { GameModes } from "#app/game-mode";
import { EncounterAnim } from "#enums/encounter-anims";
export interface EncounterStartOfBattleEffect {
sourcePokemon?: Pokemon;
@ -72,15 +72,14 @@ export interface IMysteryEncounter {
* Unless you know what you're doing, you should use MysteryEncounterBuilder to create an instance for this class
*/
export default class MysteryEncounter implements IMysteryEncounter {
/**
* Required params
*/
// #region Required params
encounterType: MysteryEncounterType;
options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]];
spriteConfigs: MysteryEncounterSpriteConfig[];
/**
* Optional params
*/
// #region Optional params
encounterTier: MysteryEncounterTier;
/**
* Custom battle animations that are configured for encounter effects and visuals
@ -88,7 +87,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
*/
encounterAnimations?: EncounterAnim[];
/**
* If specified, defines any game modes where the MysteryEncounter should *NOT* spawn
* If specified, defines any game modes where the {@linkcode MysteryEncounter} should *NOT* spawn
*/
disabledGameModes?: GameModes[];
/**
@ -137,9 +136,8 @@ export default class MysteryEncounter implements IMysteryEncounter {
*/
skipToFightInput: boolean;
/**
* Event callback functions
*/
// #region Event callback functions
/** Event when Encounter is first loaded, use it for data conditioning */
onInit?: (scene: BattleScene) => boolean;
/** Event when battlefield visuals have finished sliding in and the encounter dialogue begins */
@ -171,9 +169,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
primaryPokemon?: PlayerPokemon;
secondaryPokemon?: PlayerPokemon[];
/**
* Post-construct / Auto-populated params
*/
// #region Post-construct / Auto-populated params
/**
* Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter
@ -192,9 +188,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
*/
introVisuals?: MysteryEncounterIntroVisuals;
/**
* Flags
*/
// #region Flags
/**
* Can be set for uses programatic dialogue during an encounter (storing the name of one of the party's pokemon, etc.)
@ -274,7 +268,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
}
/**
* Checks if the current scene state meets the requirements for the MysteryEncounter to spawn
* Checks if the current scene state meets the requirements for the {@linkcode MysteryEncounter} to spawn
* This is used to filter the pool of encounters down to only the ones with all requirements met
* @param scene
* @returns
@ -497,7 +491,7 @@ export default class MysteryEncounter implements IMysteryEncounter {
/**
* Maintains seed offset for RNG consistency
* Increments if the same MysteryEncounter has multiple option select cycles
* Increments if the same {@linkcode MysteryEncounter} has multiple option select cycles
* @param scene
*/
updateSeedOffset(scene: BattleScene) {
@ -540,7 +534,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* @statif Defines the type of encounter which is used as an identifier, should be tied to a unique MysteryEncounterType
* NOTE: if new functions are added to MysteryEncounter class
* NOTE: if new functions are added to {@linkcode MysteryEncounter} class
* @param encounterType
* @returns this
*/

View File

@ -1,6 +1,9 @@
import { Moves } from "#enums/moves";
import { Abilities } from "#enums/abilities";
/**
* Moves that "steal" things
*/
export const STEALING_MOVES = [
Moves.PLUCK,
Moves.COVET,
@ -10,6 +13,9 @@ export const STEALING_MOVES = [
Moves.SWITCHEROO
];
/**
* Moves that "charm" someone
*/
export const CHARMING_MOVES = [
Moves.CHARM,
Moves.FLATTER,
@ -39,6 +45,9 @@ export const DANCING_MOVES = [
Moves.VICTORY_DANCE
];
/**
* Moves that can distract someone/something
*/
export const DISTRACTION_MOVES = [
Moves.FAKE_OUT,
Moves.FOLLOW_ME,
@ -54,6 +63,9 @@ export const DISTRACTION_MOVES = [
Moves.SHED_TAIL
];
/**
* Moves that protect in some way
*/
export const PROTECTING_MOVES = [
Moves.PROTECT,
Moves.WIDE_GUARD,
@ -70,6 +82,9 @@ export const PROTECTING_MOVES = [
Moves.DETECT
];
/**
* Moves that (loosely) can be used to trap/rob someone
*/
export const EXTORTION_MOVES = [
Moves.BIND,
Moves.CLAMP,
@ -93,6 +108,9 @@ export const EXTORTION_MOVES = [
Moves.STRING_SHOT,
];
/**
* Abilities that (loosely) can be used to trap/rob someone
*/
export const EXTORTION_ABILITIES = [
Abilities.INTIMIDATE,
Abilities.ARENA_TRAP,

View File

@ -88,13 +88,18 @@ export interface EnemyPokemonConfig {
}
export interface EnemyPartyConfig {
levelAdditiveMultiplier?: number; // Formula for enemy: level += waveIndex / 10 * levelAdditive
/** Formula for enemy: level += waveIndex / 10 * levelAdditive */
levelAdditiveMultiplier?: number;
doubleBattle?: boolean;
trainerType?: TrainerType; // Generates trainer battle solely off trainer type
trainerConfig?: TrainerConfig; // More customizable option for configuring trainer battle
/** Generates trainer battle solely off trainer type */
trainerType?: TrainerType;
/** More customizable option for configuring trainer battle */
trainerConfig?: TrainerConfig;
pokemonConfigs?: EnemyPokemonConfig[];
female?: boolean; // True for female trainer, false for male
disableSwitch?: boolean; // True will prevent player from switching
/** True for female trainer, false for male */
female?: boolean;
/** True will prevent player from switching */
disableSwitch?: boolean;
}
/**
@ -589,7 +594,7 @@ export function selectOptionThenPokemon(scene: BattleScene, options: OptionSelec
* @param scene - Battle Scene
* @param customShopRewards - adds a shop phase with the specified rewards / reward tiers
* @param eggRewards
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before MysteryEncounterRewardsPhase)
* @param preRewardsCallback - can execute an arbitrary callback before the new phases if necessary (useful for updating items/party/injecting new phases before {@linkcode MysteryEncounterRewardsPhase})
*/
export function setEncounterRewards(scene: BattleScene, customShopRewards?: CustomModifierSettings, eggRewards?: IEggOptions[], preRewardsCallback?: Function) {
scene.currentBattle.mysteryEncounter!.doEncounterRewards = (scene: BattleScene) => {
@ -766,7 +771,7 @@ export function transitionMysteryEncounterIntroVisuals(scene: BattleScene, hide:
/**
* Will queue moves for any pokemon to use before the first CommandPhase of a battle
* Mostly useful for allowing MysteryEncounter enemies to "cheat" and use moves before the first turn
* Mostly useful for allowing {@linkcode MysteryEncounter} enemies to "cheat" and use moves before the first turn
* @param scene
*/
export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {

View File

@ -355,9 +355,9 @@ export class TrainerConfig {
/**
* Sets the configuration for trainers with genders, including the female name and encounter background music (BGM).
* @param {string} [nameFemale] - The name of the female trainer. If 'Ivy', a localized name will be assigned.
* @param {TrainerType | string} [femaleEncounterBgm] - The encounter BGM for the female trainer, which can be a TrainerType or a string.
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param {string} [nameFemale] The name of the female trainer. If 'Ivy', a localized name will be assigned.
* @param {TrainerType | string} [femaleEncounterBgm] The encounter BGM for the female trainer, which can be a TrainerType or a string.
* @returns {TrainerConfig} The updated TrainerConfig instance.
**/
setHasGenders(nameFemale?: string, femaleEncounterBgm?: TrainerType | string): TrainerConfig {
// If the female name is 'Ivy' (the rival), assign a localized name.
@ -392,9 +392,9 @@ export class TrainerConfig {
/**
* Sets the configuration for trainers with double battles, including the name of the double trainer and the encounter BGM.
* @param nameDouble - The name of the double trainer (e.g., "Ace Duo" for Trainer Class Doubles or "red_blue_double" for NAMED trainer doubles).
* @param doubleEncounterBgm - The encounter BGM for the double trainer, which can be a TrainerType or a string.
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param nameDouble The name of the double trainer (e.g., "Ace Duo" for Trainer Class Doubles or "red_blue_double" for NAMED trainer doubles).
* @param doubleEncounterBgm The encounter BGM for the double trainer, which can be a TrainerType or a string.
* @returns {TrainerConfig} The updated TrainerConfig instance.
*/
setHasDouble(nameDouble: string, doubleEncounterBgm?: TrainerType | string): TrainerConfig {
this.hasDouble = true;
@ -407,8 +407,8 @@ export class TrainerConfig {
/**
* Sets the trainer type for double battles.
* @param trainerTypeDouble - The TrainerType of the partner in a double battle.
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param trainerTypeDouble The TrainerType of the partner in a double battle.
* @returns {TrainerConfig} The updated TrainerConfig instance.
*/
setDoubleTrainerType(trainerTypeDouble: TrainerType): TrainerConfig {
this.trainerTypeDouble = trainerTypeDouble;
@ -432,8 +432,8 @@ export class TrainerConfig {
/**
* Sets the title for double trainers
* @param titleDouble - the key for the title in the i18n file. (e.g., "champion_double").
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param titleDouble The key for the title in the i18n file. (e.g., "champion_double").
* @returns {TrainerConfig} The updated TrainerConfig instance.
*/
setDoubleTitle(titleDouble: string): TrainerConfig {
// First check if i18n is initialized
@ -625,10 +625,10 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for an evil team admin.
* @param title - The title of the evil team admin.
* @param poolName - The evil team the admin belongs to.
* @param {Species | Species[]} signatureSpecies - The signature species for the evil team leader.
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param title The title of the evil team admin.
* @param poolName The evil team the admin belongs to.
* @param {Species | Species[]} signatureSpecies The signature species for the evil team leader.
* @returns {TrainerConfig} The updated TrainerConfig instance.
* **/
initForEvilTeamAdmin(title: string, poolName: string, signatureSpecies: (Species | Species[])[],): TrainerConfig {
if (!getIsInitialized()) {
@ -661,10 +661,10 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for a Stat Trainer, as part of the Trainer's Test Mystery Encounter.
* @param {Species | Species[]} signatureSpecies - The signature species for the Elite Four member.
* @param {Type[]} specialtyTypes - The specialty types for the Stat Trainer.
* @param isMale - Whether the Elite Four Member is Male or Female (for localization of the title).
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param {Species | Species[]} signatureSpecies The signature species for the Elite Four member.
* @param {Type[]} specialtyTypes The specialty types for the Stat Trainer.
* @param isMale Whether the Elite Four Member is Male or Female (for localization of the title).
* @returns {TrainerConfig} The updated TrainerConfig instance.
**/
initForStatTrainer(signatureSpecies: (Species | Species[])[], isMale: boolean, ...specialtyTypes: Type[]): TrainerConfig {
if (!getIsInitialized()) {
@ -698,10 +698,10 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for an evil team leader. Temporarily hardcoding evil leader teams though.
* @param {Species | Species[]} signatureSpecies - The signature species for the evil team leader.
* @param {Type[]} specialtyTypes - The specialty types for the evil team Leader.
* @param boolean whether or not this is the rematch fight
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param {Species | Species[]} signatureSpecies The signature species for the evil team leader.
* @param {Type[]} specialtyTypes The specialty types for the evil team Leader.
* @param boolean Whether or not this is the rematch fight
* @returns {TrainerConfig} The updated TrainerConfig instance.
* **/
initForEvilTeamLeader(title: string, signatureSpecies: (Species | Species[])[], rematch: boolean = false, ...specialtyTypes: Type[]): TrainerConfig {
if (!getIsInitialized()) {
@ -737,10 +737,10 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for a Gym Leader.
* @param {Species | Species[]} signatureSpecies - The signature species for the Gym Leader.
* @param {Type[]} specialtyTypes - The specialty types for the Gym Leader.
* @param isMale - Whether the Gym Leader is Male or Not (for localization of the title).
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param {Species | Species[]} signatureSpecies The signature species for the Gym Leader.
* @param {Type[]} specialtyTypes The specialty types for the Gym Leader.
* @param isMale Whether the Gym Leader is Male or Not (for localization of the title).
* @returns {TrainerConfig} The updated TrainerConfig instance.
* **/
initForGymLeader(signatureSpecies: (Species | Species[])[], isMale: boolean, ...specialtyTypes: Type[]): TrainerConfig {
// Check if the internationalization (i18n) system is initialized.
@ -794,10 +794,10 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for an Elite Four member.
* @param {Species | Species[]} signatureSpecies - The signature species for the Elite Four member.
* @param {Type[]} specialtyTypes - The specialty types for the Elite Four member.
* @param isMale - Whether the Elite Four Member is Male or Female (for localization of the title).
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param {Species | Species[]} signatureSpecies The signature species for the Elite Four member.
* @param {Type[]} specialtyTypes The specialty types for the Elite Four member.
* @param isMale Whether the Elite Four Member is Male or Female (for localization of the title).
* @returns {TrainerConfig} The updated TrainerConfig instance.
**/
initForEliteFour(signatureSpecies: (Species | Species[])[], isMale: boolean, ...specialtyTypes: Type[]): TrainerConfig {
// Check if the internationalization (i18n) system is initialized.
@ -850,9 +850,9 @@ export class TrainerConfig {
/**
* Initializes the trainer configuration for a Champion.
* @param {Species | Species[]} signatureSpecies - The signature species for the Champion.
* @param isMale - Whether the Champion is Male or Female (for localization of the title).
* @returns {TrainerConfig} - The updated TrainerConfig instance.
* @param {Species | Species[]} signatureSpecies The signature species for the Champion.
* @param isMale Whether the Champion is Male or Female (for localization of the title).
* @returns {TrainerConfig} The updated TrainerConfig instance.
**/
initForChampion(signatureSpecies: (Species | Species[])[], isMale: boolean): TrainerConfig {
// Check if the internationalization (i18n) system is initialized.
@ -995,63 +995,63 @@ export class TrainerConfig {
}
/**
* Creates a copy of a trainer config so that it can be modified without affecting the {@link trainerConfigs} source map
* Creates a shallow copy of a trainer config so that it can be modified without affecting the {@link trainerConfigs} source map
*/
copy(): TrainerConfig {
let copy = new TrainerConfig(this.trainerType);
copy = this.trainerTypeDouble ? copy.setDoubleTrainerType(this.trainerTypeDouble) : copy;
copy = this.name ? copy.setName(this.name) : copy;
copy = this.hasGenders ? copy.setHasGenders(this.nameFemale, this.femaleEncounterBgm) : copy;
copy = this.hasDouble ? copy.setHasDouble(this.nameDouble, this.doubleEncounterBgm) : copy;
copy = this.title ? copy.setTitle(this.title) : copy;
copy = this.titleDouble ? copy.setDoubleTitle(this.titleDouble) : copy;
copy = this.hasCharSprite ? copy.setHasCharSprite() : copy;
copy = this.doubleOnly ? copy.setDoubleOnly() : copy;
copy = this.moneyMultiplier ? copy.setMoneyMultiplier(this.moneyMultiplier) : copy;
copy = this.isBoss ? copy.setBoss() : copy;
copy = this.hasStaticParty ? copy.setStaticParty() : copy;
copy = this.useSameSeedForAllMembers ? copy.setUseSameSeedForAllMembers() : copy;
copy = this.battleBgm ? copy.setBattleBgm(this.battleBgm) : copy;
copy = this.encounterBgm ? copy.setEncounterBgm(this.encounterBgm) : copy;
copy = this.victoryBgm ? copy.setVictoryBgm(this.victoryBgm) : copy;
copy = this.genModifiersFunc ? copy.setGenModifiersFunc(this.genModifiersFunc) : copy;
clone(): TrainerConfig {
let clone = new TrainerConfig(this.trainerType);
clone = this.trainerTypeDouble ? clone.setDoubleTrainerType(this.trainerTypeDouble) : clone;
clone = this.name ? clone.setName(this.name) : clone;
clone = this.hasGenders ? clone.setHasGenders(this.nameFemale, this.femaleEncounterBgm) : clone;
clone = this.hasDouble ? clone.setHasDouble(this.nameDouble, this.doubleEncounterBgm) : clone;
clone = this.title ? clone.setTitle(this.title) : clone;
clone = this.titleDouble ? clone.setDoubleTitle(this.titleDouble) : clone;
clone = this.hasCharSprite ? clone.setHasCharSprite() : clone;
clone = this.doubleOnly ? clone.setDoubleOnly() : clone;
clone = this.moneyMultiplier ? clone.setMoneyMultiplier(this.moneyMultiplier) : clone;
clone = this.isBoss ? clone.setBoss() : clone;
clone = this.hasStaticParty ? clone.setStaticParty() : clone;
clone = this.useSameSeedForAllMembers ? clone.setUseSameSeedForAllMembers() : clone;
clone = this.battleBgm ? clone.setBattleBgm(this.battleBgm) : clone;
clone = this.encounterBgm ? clone.setEncounterBgm(this.encounterBgm) : clone;
clone = this.victoryBgm ? clone.setVictoryBgm(this.victoryBgm) : clone;
clone = this.genModifiersFunc ? clone.setGenModifiersFunc(this.genModifiersFunc) : clone;
if (this.modifierRewardFuncs) {
// Clones array instead of passing ref
copy.modifierRewardFuncs = this.modifierRewardFuncs.slice(0);
clone.modifierRewardFuncs = this.modifierRewardFuncs.slice(0);
}
if (this.partyTemplates) {
copy.partyTemplates = this.partyTemplates.slice(0);
clone.partyTemplates = this.partyTemplates.slice(0);
}
copy = this.partyTemplateFunc ? copy.setPartyTemplateFunc(this.partyTemplateFunc) : copy;
clone = this.partyTemplateFunc ? clone.setPartyTemplateFunc(this.partyTemplateFunc) : clone;
if (this.partyMemberFuncs) {
Object.keys(this.partyMemberFuncs).forEach((index) => {
copy = copy.setPartyMemberFunc(parseInt(index, 10), this.partyMemberFuncs[index]);
clone = clone.setPartyMemberFunc(parseInt(index, 10), this.partyMemberFuncs[index]);
});
}
copy = this.speciesPools ? copy.setSpeciesPools(this.speciesPools) : copy;
copy = this.speciesFilter ? copy.setSpeciesFilter(this.speciesFilter) : copy;
clone = this.speciesPools ? clone.setSpeciesPools(this.speciesPools) : clone;
clone = this.speciesFilter ? clone.setSpeciesFilter(this.speciesFilter) : clone;
if (this.specialtyTypes) {
copy.specialtyTypes = this.specialtyTypes.slice(0);
clone.specialtyTypes = this.specialtyTypes.slice(0);
}
copy.encounterMessages = this.encounterMessages?.slice(0);
copy.victoryMessages = this.victoryMessages?.slice(0);
copy.defeatMessages = this.defeatMessages?.slice(0);
clone.encounterMessages = this.encounterMessages?.slice(0);
clone.victoryMessages = this.victoryMessages?.slice(0);
clone.defeatMessages = this.defeatMessages?.slice(0);
copy.femaleEncounterMessages = this.femaleEncounterMessages?.slice(0);
copy.femaleVictoryMessages = this.femaleVictoryMessages?.slice(0);
copy.femaleDefeatMessages = this.femaleDefeatMessages?.slice(0);
clone.femaleEncounterMessages = this.femaleEncounterMessages?.slice(0);
clone.femaleVictoryMessages = this.femaleVictoryMessages?.slice(0);
clone.femaleDefeatMessages = this.femaleDefeatMessages?.slice(0);
copy.doubleEncounterMessages = this.doubleEncounterMessages?.slice(0);
copy.doubleVictoryMessages = this.doubleVictoryMessages?.slice(0);
copy.doubleDefeatMessages = this.doubleDefeatMessages?.slice(0);
clone.doubleEncounterMessages = this.doubleEncounterMessages?.slice(0);
clone.doubleVictoryMessages = this.doubleVictoryMessages?.slice(0);
clone.doubleDefeatMessages = this.doubleDefeatMessages?.slice(0);
return copy;
return clone;
}
}

View File

@ -0,0 +1,11 @@
/**
* Animations used for Mystery Encounters
* These are custom animations that may or may not work in any other circumstance
* Use at your own risk
*/
export enum EncounterAnim {
MAGMA_BG,
MAGMA_SPOUT,
SMOKESCREEN,
DANCE
}

View File

@ -1,12 +1,12 @@
export enum MysteryEncounterMode {
/** MysteryEncounter will always begin in this mode, but will always swap modes when an option is selected */
/** {@linkcode MysteryEncounter} will always begin in this mode, but will always swap modes when an option is selected */
DEFAULT,
/** If the MysteryEncounter battle is a trainer type battle */
/** If the {@linkcode MysteryEncounter} battle is a trainer type battle */
TRAINER_BATTLE,
/** If the MysteryEncounter battle is a wild type battle */
/** If the {@linkcode MysteryEncounter} battle is a wild type battle */
WILD_BATTLE,
/** Enables special boss music during encounter */
BOSS_BATTLE,
/** If there is no battle in the MysteryEncounter or option selected */
/** If there is no battle in the {@linkcode MysteryEncounter} or option selected */
NO_BATTLE
}

View File

@ -33,8 +33,8 @@ interface GameModeConfig {
}
// Describes min and max waves for MEs in specific game modes
const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
export const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
export class GameMode implements GameModeConfig {
public modeId: GameModes;
@ -325,6 +325,9 @@ export class GameMode implements GameModeConfig {
}
}
/**
* Returns the wave range where MEs can spawn for the game mode [min, max]
*/
getMysteryEncounterLegalWaves(): [number, number] {
switch (this.modeId) {
default:

View File

@ -122,7 +122,7 @@ export class ModifierType {
* Populates item tier for ModifierType instance
* Tier is a necessary field for items that appear in player shop (determines the Pokeball visual they use)
* To find the tier, this function performs a reverse lookup of the item type in modifier pools
* @param poolType - Default 'ModifierPoolType.PLAYER'. Which pool to lookup item tier from
* @param poolType Default 'ModifierPoolType.PLAYER'. Which pool to lookup item tier from
*/
withTierFromPool(poolType: ModifierPoolType = ModifierPoolType.PLAYER): ModifierType {
for (const tier of Object.values(getModifierPoolForType(poolType))) {
@ -2048,6 +2048,7 @@ export interface CustomModifierSettings {
guaranteedModifierTypeOptions?: ModifierTypeOption[];
guaranteedModifierTypeFuncs?: ModifierTypeFunc[];
fillRemaining?: boolean;
/** Set to negative value to disable rerolls completely in shop */
rerollMultiplier?: number;
allowLuckUpgrades?: boolean;
}
@ -2057,20 +2058,20 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc {
}
/**
* Generates modifier options for a SelectModifierPhase
* @param count - Determines the number of items to generate
* @param party - Party is required for generating proper modifier pools
* @param modifierTiers - (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule.
* @param customModifierSettings - (Optional) If specified, can customize the item shop rewards further.
* - `guaranteedModifierTypeOptions?: ModifierTypeOption[]` - If specified, will override the first X items to be specific modifier options (these should be pre-genned).
* - `guaranteedModifierTypeFuncs?: ModifierTypeFunc[]` - If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned).
* - `guaranteedModifierTiers?: ModifierTier[]` - If specified, will override the next X items to be the specified tier. These can upgrade with luck.
* - `fillRemaining?: boolean` - Default 'false'. If set to true, will fill the remainder of shop items that were not overridden by the 3 options above, up to the 'count' param value.
* Generates modifier options for a {@linkcode SelectModifierPhase}
* @param count Determines the number of items to generate
* @param party Party is required for generating proper modifier pools
* @param modifierTiers (Optional) If specified, rolls items in the specified tiers. Commonly used for tier-locking with Lock Capsule.
* @param customModifierSettings (Optional) If specified, can customize the item shop rewards further.
* - `guaranteedModifierTypeOptions?: ModifierTypeOption[]` If specified, will override the first X items to be specific modifier options (these should be pre-genned).
* - `guaranteedModifierTypeFuncs?: ModifierTypeFunc[]` If specified, will override the next X items to be auto-generated from specific modifier functions (these don't have to be pre-genned).
* - `guaranteedModifierTiers?: ModifierTier[]` If specified, will override the next X items to be the specified tier. These can upgrade with luck.
* - `fillRemaining?: boolean` Default 'false'. If set to true, will fill the remainder of shop items that were not overridden by the 3 options above, up to the 'count' param value.
* - Example: `count = 4`, `customModifierSettings = { guaranteedModifierTiers: [ModifierTier.GREAT], fillRemaining: true }`,
* - The first item in the shop will be `GREAT` tier, and the remaining 3 items will be generated normally.
* - If `fillRemaining = false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of `count` value).
* - `rerollMultiplier?: number` - If specified, can adjust the amount of money required for a shop reroll. If set to 0, the shop will not allow rerolls at all.
* - `allowLuckUpgrades?: boolean` - Default true, if false will prevent set item tiers from upgrading via luck
* - `rerollMultiplier?: number` If specified, can adjust the amount of money required for a shop reroll. If set to a negative value, the shop will not allow rerolls at all.
* - `allowLuckUpgrades?: boolean` Default `true`, if `false` will prevent set item tiers from upgrading via luck
*/
export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings): ModifierTypeOption[] {
const options: ModifierTypeOption[] = [];
@ -2126,12 +2127,12 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
}
/**
* Will generate a ModifierType from the ModifierPoolType.PLAYER pool, attempting to retry duplicated items up to retryCount
* @param existingOptions - currently generated options
* @param retryCount - how many times to retry before allowing a dupe item
* @param party - current player party, used to calculate items in the pool
* @param tier - If specified will generate item of tier
* @param allowLuckUpgrades - allow items to upgrade tiers (the little animation that plays and is affected by luck)
* Will generate a {@linkcode ModifierType} from the {@linkcode ModifierPoolType.PLAYER} pool, attempting to retry duplicated items up to retryCount
* @param existingOptions Currently generated options
* @param retryCount How many times to retry before allowing a dupe item
* @param party Current player party, used to calculate items in the pool
* @param tier If specified will generate item of tier
* @param allowLuckUpgrades `true` to allow items to upgrade tiers (the little animation that plays and is affected by luck)
*/
function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], retryCount: integer, party: PlayerPokemon[], tier?: ModifierTier, allowLuckUpgrades?: boolean): ModifierTypeOption {
allowLuckUpgrades = allowLuckUpgrades ?? true;
@ -2270,12 +2271,12 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P
/**
* Generates a ModifierType from the specified pool
* @param party - party of the trainer using the item
* @param poolType - PLAYER/WILD/TRAINER
* @param tier - If specified, will override the initial tier of an item (can still upgrade with luck)
* @param upgradeCount - If defined, means that this is a new ModifierType being generated to override another via luck upgrade. Used for recursive logic
* @param retryCount - Max allowed tries before the next tier down is checked for a valid ModifierType
* @param allowLuckUpgrades - Default true. If false, will not allow ModifierType to randomly upgrade to next tier
* @param party party of the trainer using the item
* @param poolType PLAYER/WILD/TRAINER
* @param tier If specified, will override the initial tier of an item (can still upgrade with luck)
* @param upgradeCount If defined, means that this is a new ModifierType being generated to override another via luck upgrade. Used for recursive logic
* @param retryCount Max allowed tries before the next tier down is checked for a valid ModifierType
* @param allowLuckUpgrades Default true. If false, will not allow ModifierType to randomly upgrade to next tier
*/
function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0, allowLuckUpgrades: boolean = true): ModifierTypeOption | null {
const player = !poolType;

View File

@ -98,7 +98,7 @@ export class EncounterPhase extends BattlePhase {
} else {
let enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true);
// If player has golden bug net, rolls 10% chance to replace with species from the golden bug net bug pool
if (!!this.scene.findModifier(m => m instanceof BoostBugSpawnModifier) && randSeedInt(10) === 0) {
if (this.scene.findModifier(m => m instanceof BoostBugSpawnModifier) && randSeedInt(10) === 0) {
enemySpecies = getGoldenBugNetSpecies();
}
battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies));

View File

@ -30,19 +30,18 @@ import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounte
* - Clearing of phase queues to enter the Mystery Encounter game state
* - Management of session data related to MEs
* - Initialization of ME option select menu and UI
* - Execute onPreOptionPhase() logic if it exists for the selected option
* - Display any OptionTextDisplay.selected type dialogue that is set in the MysteryEncounterDialogue dialogue tree for selected option
* - Queuing of the MysteryEncounterOptionSelectedPhase
* - Execute {@linkcode MysteryEncounter.onPreOptionPhase} logic if it exists for the selected option
* - Display any `OptionTextDisplay.selected` type dialogue that is set in the {@linkcode MysteryEncounterDialogue} dialogue tree for selected option
* - Queuing of the {@linkcode MysteryEncounterOptionSelectedPhase}
*/
export class MysteryEncounterPhase extends Phase {
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300;
optionSelectSettings?: OptionSelectSettings;
/**
*
* @param scene
* @param optionSelectSettings - allows overriding the typical options of an encounter with new ones
* Mostly useful for having repeated queries during a single encounter, where the queries and options may differ each time
* @param scene
* @param optionSelectSettings allows overriding the typical options of an encounter with new ones
*/
constructor(scene: BattleScene, optionSelectSettings?: OptionSelectSettings) {
super(scene);
@ -114,7 +113,7 @@ export class MysteryEncounterPhase extends Phase {
}
/**
* Queues MysteryEncounterOptionSelectedPhase, displays option.selected dialogue and ends phase
* Queues {@linkcode MysteryEncounterOptionSelectedPhase}, displays option.selected dialogue and ends phase
*/
continueEncounter() {
const endDialogueAndContinueEncounter = () => {
@ -161,7 +160,7 @@ export class MysteryEncounterPhase extends Phase {
/**
* Will handle (in order):
* - Execute onOptionSelect() logic if it exists for the selected option
* - Execute {@linkcode MysteryEncounter.onOptionSelect} logic if it exists for the selected option
*
* It is important to point out that no phases are directly queued by any logic within this phase
* Any phase that is meant to follow this one MUST be queued via the onOptionSelect() logic of the selected option
@ -174,6 +173,13 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
this.onOptionSelect = this.scene.currentBattle.mysteryEncounter!.selectedOption!.onOptionPhase;
}
/**
* Will handle (in order):
* - Execute {@linkcode MysteryEncounter.onOptionSelect} logic if it exists for the selected option
*
* It is important to point out that no phases are directly queued by any logic within this phase.
* Any phase that is meant to follow this one MUST be queued via the {@linkcode MysteryEncounter.onOptionSelect} logic of the selected option.
*/
start() {
super.start();
if (this.scene.currentBattle.mysteryEncounter?.autoHideIntroVisuals) {
@ -196,9 +202,9 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
/**
* Runs at the beginning of an Encounter's battle
* Will clean up any residual flinches, Endure, etc. that are left over from startOfBattleEffects
* Will also handle Game Overs, switches, etc. that could happen from handleMysteryEncounterBattleStartEffects
* See [TurnEndPhase](../phases.ts) for more details
* Will clean up any residual flinches, Endure, etc. that are left over from {@linkcode MysteryEncounter.startOfBattleEffects}
* Will also handle Game Overs, switches, etc. that could happen from {@linkcode handleMysteryEncounterBattleStartEffects}
* See {@linkcode TurnEndPhase} for more details
*/
export class MysteryEncounterBattleStartCleanupPhase extends Phase {
constructor(scene: BattleScene) {
@ -206,7 +212,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
}
/**
* Cleans up TURN_END tags, any PostTurnEffectPhases, checks for Pokemon switches, then continues
* Cleans up `TURN_END` tags, any {@linkcode PostTurnStatusEffectPhase}s, checks for Pokemon switches, then continues
*/
start() {
super.start();
@ -253,7 +259,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
* - Setting BGM
* - Showing intro dialogue for an enemy trainer or wild Pokemon
* - Sliding in the visuals for enemy trainer or wild Pokemon, as well as handling summoning animations
* - Queue the SummonPhases, PostSummonPhases, etc., required to initialize the phase queue for a battle
* - Queue the {@linkcode SummonPhase}s, {@linkcode PostSummonPhase}s, etc., required to initialize the phase queue for a battle
*/
export class MysteryEncounterBattlePhase extends Phase {
disableSwitch: boolean;
@ -300,7 +306,7 @@ export class MysteryEncounterBattlePhase extends Phase {
}
/**
* Queues SummonPhases for the new battle, and handles trainer animations/dialogue if Trainer battle
* Queues {@linkcode SummonPhase}s for the new battle, and handles trainer animations/dialogue if it's a Trainer battle
* @param scene
* @private
*/
@ -369,7 +375,7 @@ export class MysteryEncounterBattlePhase extends Phase {
}
/**
* Initiate SummonPhases, scanner phases, PostSummon phases, etc.
* Initiate {@linkcode SummonPhase}s, {@linkcode ScanIvsPhase}, {@linkcode PostSummonPhase}s, etc.
* @param scene
* @private
*/
@ -465,10 +471,10 @@ export class MysteryEncounterBattlePhase extends Phase {
*
* OR
*
* - Any encounter reward logic that is set within MysteryEncounter doEncounterExp
* - Any encounter reward logic that is set within MysteryEncounter doEncounterRewards
* - Any encounter reward logic that is set within {@linkcode MysteryEncounter.doEncounterExp}
* - Any encounter reward logic that is set within {@linkcode MysteryEncounter.doEncounterRewards}
* - Otherwise, can add a no-reward-item shop with only Potions, etc. if addHealPhase is true
* - Queuing of the PostMysteryEncounterPhase
* - Queuing of the {@linkcode PostMysteryEncounterPhase}
*/
export class MysteryEncounterRewardsPhase extends Phase {
addHealPhase: boolean;
@ -479,7 +485,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
}
/**
* Runs {@link MysteryEncounter.doContinueEncounter} and ends phase, OR {@link MysteryEncounter.onRewards} then continues encounter
* Runs {@linkcode MysteryEncounter.doContinueEncounter} and ends phase, OR {@linkcode MysteryEncounter.onRewards} then continues encounter
*/
start() {
super.start();
@ -504,7 +510,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
}
/**
* Queues encounter EXP and rewards phases, PostMysteryEncounterPhase, and ends phase
* Queues encounter EXP and rewards phases, {@linkcode PostMysteryEncounterPhase}, and ends phase
*/
doEncounterRewardsAndContinue() {
const encounter = this.scene.currentBattle.mysteryEncounter!;
@ -517,7 +523,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
encounter.doEncounterRewards(this.scene);
} else if (this.addHealPhase) {
this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, undefined, { fillRemaining: false, rerollMultiplier: 0 }));
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, undefined, { fillRemaining: false, rerollMultiplier: -1 }));
}
this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene));
@ -527,7 +533,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
/**
* Will handle (in order):
* - onPostOptionSelect logic (based on an option that was selected)
* - {@linkcode MysteryEncounter.onPostOptionSelect} logic (based on an option that was selected)
* - Showing any outro dialogue messages
* - Cleanup of any leftover intro visuals
* - Queuing of the next wave
@ -542,7 +548,7 @@ export class PostMysteryEncounterPhase extends Phase {
}
/**
* Runs {@link MysteryEncounter.onPostOptionSelect} then continues encounter
* Runs {@linkcode MysteryEncounter.onPostOptionSelect} then continues encounter
*/
start() {
super.start();
@ -562,7 +568,7 @@ export class PostMysteryEncounterPhase extends Phase {
}
/**
* Queues NewBattlePhase, plays outro dialogue and ends phase
* Queues {@linkcode NewBattlePhase}, plays outro dialogue and ends phase
*/
continueEncounter() {
const endPhase = () => {

View File

@ -18,6 +18,9 @@ export class PartyExpPhase extends Phase {
this.pokemonParticipantIds = pokemonParticipantIds;
}
/**
* Gives EXP to the party
*/
start() {
super.start();

View File

@ -14,14 +14,14 @@ import { isNullOrUndefined } from "#app/utils";
export class SelectModifierPhase extends BattlePhase {
private rerollCount: integer;
private modifierTiers: ModifierTier[];
private modifierTiers?: ModifierTier[];
private customModifierSettings?: CustomModifierSettings;
constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings) {
super(scene);
this.rerollCount = rerollCount;
this.modifierTiers = modifierTiers!; // TODO: is this bang correct?
this.modifierTiers = modifierTiers;
this.customModifierSettings = customModifierSettings;
}
@ -74,7 +74,7 @@ export class SelectModifierPhase extends BattlePhase {
switch (cursor) {
case 0:
const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers);
if (rerollCost === 0 || this.scene.money < rerollCost) {
if (rerollCost < 0 || this.scene.money < rerollCost) {
this.scene.ui.playError();
return false;
} else {
@ -241,7 +241,17 @@ export class SelectModifierPhase extends BattlePhase {
} else {
baseValue = 250;
}
const multiplier = !isNullOrUndefined(this.customModifierSettings?.rerollMultiplier) ? this.customModifierSettings!.rerollMultiplier! : 1;
let multiplier = 1;
if (!isNullOrUndefined(this.customModifierSettings?.rerollMultiplier)) {
if (this.customModifierSettings!.rerollMultiplier! < 0) {
// Completely overrides reroll cost to -1 and early exits
return -1;
}
// Otherwise, continue with custom multiplier
multiplier = this.customModifierSettings!.rerollMultiplier!;
}
return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER);
}

View File

@ -172,7 +172,18 @@ export async function initI18n(): Promise<void> {
// If you don't want the BBCode tag applied, just use 'number' formatter
i18next.services.formatter?.add("money", (value, lng, options) => {
const numberFormattedString = Intl.NumberFormat(lng, options).format(value);
switch (lng) {
case "ja":
return `@[MONEY]{${numberFormattedString}}円`;
case "de":
case "es":
case "fr":
case "it":
return `@[MONEY]{${numberFormattedString} ₽}`;
default:
// English and other languages that use same format
return `@[MONEY]{₽${numberFormattedString}}`;
}
});
await initFonts(localStorage.getItem("prLang") ?? undefined);

View File

@ -1561,7 +1561,7 @@ export class GameData {
* @param incrementCount
* @param fromEgg
* @param showMessage
* @returns - true if Pokemon catch unlocked a new starter, false if Pokemon catch did not unlock a starter
* @returns `true` if Pokemon catch unlocked a new starter, `false` if Pokemon catch did not unlock a starter
*/
setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false, showMessage: boolean = true): Promise<boolean> {
return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg, showMessage);
@ -1573,7 +1573,7 @@ export class GameData {
* @param incrementCount
* @param fromEgg
* @param showMessage
* @returns - true if Pokemon catch unlocked a new starter, false if Pokemon catch did not unlock a starter
* @returns `true` if Pokemon catch unlocked a new starter, `false` if Pokemon catch did not unlock a starter
*/
setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false, showMessage: boolean = true): Promise<boolean> {
return new Promise<boolean>(resolve => {
@ -1694,8 +1694,8 @@ export class GameData {
*
* @param species
* @param eggMoveIndex
* @param showMessage - Default true. If true, will display message for unlocked egg move
* @param prependSpeciesToMessage - Default false. If true, will change message from "X Egg Move Unlocked!" to "Bulbasaur X Egg Move Unlocked!"
* @param showMessage Default true. If true, will display message for unlocked egg move
* @param prependSpeciesToMessage Default false. If true, will change message from "X Egg Move Unlocked!" to "Bulbasaur X Egg Move Unlocked!"
*/
setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer, showMessage: boolean = true, prependSpeciesToMessage: boolean = false): Promise<boolean> {
return new Promise<boolean>(resolve => {

View File

@ -57,6 +57,7 @@ export default class PokemonData {
public bossSegments?: integer;
public summonData: PokemonSummonData;
/** Data that can customize a Pokemon in non-standard ways from its Species */
public mysteryEncounterPokemonData: MysteryEncounterPokemonData;
constructor(source: Pokemon | any, forHistory: boolean = false) {

View File

@ -15,11 +15,11 @@ import { VictoryPhase } from "#app/phases/victory-phase";
import { MessagePhase } from "#app/phases/message-phase";
/**
* Runs a MysteryEncounter to either the start of a battle, or to the MysteryEncounterRewardsPhase, depending on the option selected
* Runs a {@linkcode MysteryEncounter} to either the start of a battle, or to the {@linkcode MysteryEncounterRewardsPhase}, depending on the option selected
* @param game
* @param optionNo - human number, not index
* @param secondaryOptionSelect -
* @param isBattle - if selecting option should lead to battle, set to true
* @param optionNo Human number, not index
* @param secondaryOptionSelect
* @param isBattle If selecting option should lead to battle, set to `true`
*/
export async function runMysteryEncounterToEnd(game: GameManager, optionNo: number, secondaryOptionSelect?: { pokemonNo: number, optionNo?: number }, isBattle: boolean = false) {
vi.spyOn(EncounterPhaseUtils, "selectPokemonForOption");
@ -157,7 +157,7 @@ async function handleSecondaryOptionSelect(game: GameManager, pokemonNo: number,
}
/**
* For any MysteryEncounter that has a battle, can call this to skip battle and proceed to MysteryEncounterRewardsPhase
* For any {@linkcode MysteryEncounter} that has a battle, can call this to skip battle and proceed to {@linkcode MysteryEncounterRewardsPhase}
* @param game
* @param runRewardsPhase
*/

View File

@ -117,11 +117,12 @@ describe("Field Trip - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(4);
expect(modifierSelectHandler.options.length).toEqual(5);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("X Attack");
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("X Defense");
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("X Speed");
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("Dire Hit");
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("Rarer Candy");
});
it("should leave encounter without battle", async () => {
@ -163,11 +164,12 @@ describe("Field Trip - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(4);
expect(modifierSelectHandler.options.length).toEqual(5);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("X Sp. Atk");
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("X Sp. Def");
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("X Speed");
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("Dire Hit");
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("Rarer Candy");
});
it("should leave encounter without battle", async () => {
@ -209,11 +211,12 @@ describe("Field Trip - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(4);
expect(modifierSelectHandler.options.length).toEqual(5);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("X Accuracy");
expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("X Speed");
expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("5x Great Ball");
expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("IV Scanner");
expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("Rarer Candy");
});
it("should leave encounter without battle", async () => {

View File

@ -363,7 +363,7 @@ describe("The Winstrate Challenge - Mystery Encounter", () => {
});
/**
* For any MysteryEncounter that has a battle, can call this to skip battle and proceed to MysteryEncounterRewardsPhase
* For any {@linkcode MysteryEncounter} that has a battle, can call this to skip battle and proceed to MysteryEncounterRewardsPhase
* @param game
* @param isFinalBattle
*/

View File

@ -14,6 +14,7 @@ import Overrides from "#app/overrides";
import i18next from "i18next";
import { ShopCursorTarget } from "#app/enums/shop-cursor-target";
import { IntegerHolder } from "./../utils";
import Phaser from "phaser";
export const SHOP_OPTIONS_ROW_LIMIT = 6;
@ -31,6 +32,10 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
private rowCursor: integer = 0;
private player: boolean;
/**
* If reroll cost is negative, it is assumed there are 0 items in the shop.
* It will cause reroll button to be disabled, and a "Continue" button to show in the place of shop items
*/
private rerollCost: integer;
private transferButtonWidth: integer;
private checkButtonWidth: integer;
@ -111,6 +116,11 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.continueButtonContainer.setVisible(false);
ui.add(this.continueButtonContainer);
// Create continue button
const continueButtonText = addTextObject(this.scene, -24, 5, i18next.t("modifierSelectUiHandler:continueNextWaveButton"), TextStyle.MESSAGE);
continueButtonText.setName("text-continue-btn");
this.continueButtonContainer.add(continueButtonText);
// prepare move overlay
const overlayScale = 1;
this.moveInfoOverlay = new MoveInfoOverlay(this.scene, {
@ -192,12 +202,10 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.options.push(option);
}
// Add continue button
if (this.options.length === 0) {
const continueButtonText = addTextObject(this.scene, -24, optionsYOffset - 5, i18next.t("modifierSelectUiHandler:continueNextWaveButton"), TextStyle.MESSAGE);
continueButtonText.setName("text-continue-btn");
this.continueButtonContainer.add(continueButtonText);
}
// Set "Continue" button height based on number of rows in healing items shop
const continueButton = this.continueButtonContainer.getAt<Phaser.GameObjects.Text>(0);
continueButton.y = optionsYOffset - 5;
continueButton.setVisible(this.options.length === 0);
for (let m = 0; m < shopTypeOptions.length; m++) {
const row = m < SHOP_OPTIONS_ROW_LIMIT ? 0 : 1;
@ -265,7 +273,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.continueButtonContainer.setAlpha(0);
this.rerollButtonContainer.setVisible(true);
this.checkButtonContainer.setVisible(true);
this.continueButtonContainer.setVisible(this.rerollCost === 0);
this.continueButtonContainer.setVisible(this.rerollCost < 0);
this.lockRarityButtonContainer.setVisible(canLockRarities);
this.scene.tweens.add({
@ -276,7 +284,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
this.scene.tweens.add({
targets: [this.rerollButtonContainer],
alpha: this.rerollCost === 0 ? 0.5 : 1,
alpha: this.rerollCost < 0 ? 0.5 : 1,
duration: 250
});
@ -537,6 +545,13 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler {
}
updateRerollCostText(): void {
const rerollDisabled = this.rerollCost < 0;
if (rerollDisabled) {
this.rerollCostText.setVisible(false);
return;
} else {
this.rerollCostText.setVisible(true);
}
const canReroll = this.scene.money >= this.rerollCost;
const formattedMoney = Utils.formatMoney(this.scene.moneyFormat, this.rerollCost);