Merge pull request #150 from AsdarDevelops/winstrate-challenge

adds Winstrate Challenge mystery encounter
This commit is contained in:
ImperialSympathizer 2024-08-19 13:02:40 -04:00 committed by GitHub
commit 549a1d95d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 3559 additions and 2175 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "vicky.png",
"format": "RGBA8888",
"size": {
"w": 52,
"h": 53
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 27,
"w": 52,
"h": 53
},
"frame": {
"x": 0,
"y": 0,
"w": 52,
"h": 53
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:bf9d2d417a1982282dd711456ac71206:101e07828e3d6e2a2a7a80aebfa802ad:cabe44a4410c334298b1984a219f8160$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "victor.png",
"format": "RGBA8888",
"size": {
"w": 55,
"h": 53
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 12,
"y": 27,
"w": 55,
"h": 53
},
"frame": {
"x": 0,
"y": 0,
"w": 55,
"h": 53
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:64eff0f697754cdf9552b46342c9292a:611e0e2cacbd90c1229ce5443b2414f0:0cc0f5a2c1b2eedb46dd8318e8feb1d8$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "victoria.png",
"format": "RGBA8888",
"size": {
"w": 52,
"h": 54
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 14,
"y": 26,
"w": 52,
"h": 54
},
"frame": {
"x": 0,
"y": 0,
"w": 52,
"h": 54
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4dafeae3674d63b12cc4d8044f67b5a3:7834687d784c31169256927f419c7958:cf0eb39e0a3f2e42f23ca29747d73c40$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "vito.png",
"format": "RGBA8888",
"size": {
"w": 41,
"h": 78
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 20,
"y": 2,
"w": 41,
"h": 78
},
"frame": {
"x": 0,
"y": 0,
"w": 41,
"h": 78
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:cb988be58fcd5381174e9d120b051e38:4d4723dbbcd9713ee0ed3c2d84ef4bfb:1c7723b536b218346e3138016d865ce9$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "vivi.png",
"format": "RGBA8888",
"size": {
"w": 48,
"h": 69
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 13,
"y": 11,
"w": 48,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 48,
"h": 69
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:0a51b4df0b2ed0fed7e3bdb5dffd9e28:af1f3b1480023b3e3761c49e49faf5f1:4fc6bf2bec74c4bb8809df38231deb01$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

View File

@ -14,7 +14,7 @@ import { Arena, ArenaBase } from "./field/arena";
import { GameData } from "./system/game-data";
import { addTextObject, getTextColor, TextStyle } from "./ui/text";
import { allMoves } from "./data/move";
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue, ModifierPoolType, PokemonHeldItemModifierType } from "./modifier/modifier-type";
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue, ModifierPoolType } from "./modifier/modifier-type";
import AbilityBar from "./ui/ability-bar";
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr } from "./data/ability";
import Battle, { BattleType, FixedBattleConfig } from "./battle";
@ -67,12 +67,13 @@ import { UiTheme } from "#enums/ui-theme";
import { TimedEventManager } from "#app/timed-event-manager.js";
import i18next from "i18next";
import { TrainerType } from "#enums/trainer-type";
import IMysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters";
import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -223,7 +224,7 @@ export default class BattleScene extends SceneBase {
public pokemonInfoContainer: PokemonInfoContainer;
private party: PlayerPokemon[];
public mysteryEncounterData: MysteryEncounterData = new MysteryEncounterData(null);
public lastMysteryEncounter: IMysteryEncounter;
public lastMysteryEncounter: MysteryEncounter;
/** Combined Biome and Wave count text */
private biomeWaveText: Phaser.GameObjects.Text;
private moneyText: Phaser.GameObjects.Text;
@ -1036,7 +1037,7 @@ export default class BattleScene extends SceneBase {
}
}
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: IMysteryEncounter): Battle {
newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: MysteryEncounter): Battle {
const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave;
const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1);
let newDouble: boolean;
@ -2423,7 +2424,7 @@ export default class BattleScene extends SceneBase {
});
}
generateEnemyModifiers(customHeldModifiers?: PokemonHeldItemModifierType[][]): Promise<void> {
generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise<void> {
return new Promise(resolve => {
if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
return resolve();
@ -2445,8 +2446,16 @@ export default class BattleScene extends SceneBase {
}
party.every((enemyPokemon: EnemyPokemon, i: integer) => {
if (customHeldModifiers && i < customHeldModifiers.length && customHeldModifiers[i] && customHeldModifiers[i].length > 0) {
customHeldModifiers[i].forEach(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this));
if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i] && heldModifiersConfigs[i].length > 0) {
heldModifiersConfigs[i].forEach(mt => {
const stackCount = mt.stackCount ?? 1;
// const isTransferable = mt.isTransferable ?? true;
const modifier = mt.modifierType.newModifier(enemyPokemon);
modifier.stackCount = stackCount;
// TODO: set isTransferable
// modifier.setIsTransferable(isTransferable);
this.addEnemyModifier(modifier, false, true);
});
return true;
}
@ -2702,9 +2711,9 @@ export default class BattleScene extends SceneBase {
* @param override - used to load session encounter when restarting game, etc.
* @returns
*/
getMysteryEncounter(override: IMysteryEncounter): IMysteryEncounter {
getMysteryEncounter(override: MysteryEncounter): MysteryEncounter {
// Loading override or session encounter
let encounter: IMysteryEncounter;
let encounter: MysteryEncounter;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
} else {
@ -2726,7 +2735,7 @@ export default class BattleScene extends SceneBase {
}
if (encounter) {
encounter = new IMysteryEncounter(encounter);
encounter = new MysteryEncounter(encounter);
encounter.populateDialogueTokensFromRequirements(this);
return encounter;
}
@ -2755,7 +2764,7 @@ export default class BattleScene extends SceneBase {
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE;
}
let availableEncounters: IMysteryEncounter[] = [];
let availableEncounters: MysteryEncounter[] = [];
// New encounter will never be the same as the most recent encounter
const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1][0] : null;
const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? [];
@ -2802,7 +2811,7 @@ export default class BattleScene extends SceneBase {
}
encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)];
// New encounter object to not dirty flags
encounter = new IMysteryEncounter(encounter);
encounter = new MysteryEncounter(encounter);
encounter.populateDialogueTokensFromRequirements(this);
return encounter;
}

View File

@ -14,7 +14,7 @@ import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import i18next from "#app/plugins/i18n";
import IMysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
export enum BattleType {
@ -70,7 +70,7 @@ export default class Battle {
public lastUsedPokeball: PokeballType;
public playerFaints: number; // The amount of times pokemon on the players side have fainted
public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted
public mysteryEncounter: IMysteryEncounter;
public mysteryEncounter: MysteryEncounter;
private rngCounter: integer = 0;

View File

@ -735,6 +735,56 @@ export const trainerTypeDialogue: TrainerTypeDialogue = {
]
}
],
[TrainerType.VICTOR]: [
{
encounter: [
"dialogue:winstrates_victor.encounter.1",
],
victory: [
"dialogue:winstrates_victor.victory.1"
],
}
],
[TrainerType.VICTORIA]: [
{
encounter: [
"dialogue:winstrates_victoria.encounter.1",
],
victory: [
"dialogue:winstrates_victoria.victory.1"
],
}
],
[TrainerType.VIVI]: [
{
encounter: [
"dialogue:winstrates_vivi.encounter.1",
],
victory: [
"dialogue:winstrates_vivi.victory.1"
],
}
],
[TrainerType.VICKY]: [
{
encounter: [
"dialogue:winstrates_vicky.encounter.1",
],
victory: [
"dialogue:winstrates_vicky.victory.1"
],
}
],
[TrainerType.VITO]: [
{
encounter: [
"dialogue:winstrates_vito.encounter.1",
],
victory: [
"dialogue:winstrates_vito.victory.1"
],
}
],
[TrainerType.BROCK]: {
encounter: [
"dialogue:brock.encounter.1",

View File

@ -2,7 +2,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattl
import { trainerConfigs, } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
@ -22,10 +22,10 @@ const namespace = "mysteryEncounter:aTrainersTest";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/115 | GitHub Issue #115}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ATrainersTestEncounter: IMysteryEncounter =
export const ATrainersTestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.withSceneWaveRangeRequirement(100, 180)
.withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([
{
@ -139,13 +139,6 @@ export const ATrainersTestEncounter: IMysteryEncounter =
// Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
let eggTier;
if (randSeedInt(64) >= 54) {
eggTier = EggTier.MASTER;
} else {
eggTier = EggTier.ULTRA;
}
await transitionMysteryEncounterIntroVisuals(scene);
const eggOptions: IEggOptions = {
@ -153,9 +146,9 @@ export const ATrainersTestEncounter: IMysteryEncounter =
pulled: false,
sourceType: EggSourceType.EVENT,
eventEggTypeDescriptor: encounter.misc.trainerEggDescription,
tier: eggTier
tier: EggTier.ULTRA
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.${eggTier === EggTier.ULTRA ? "epic" : "legendary"}`));
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`));
setEncounterRewards(scene, { fillRemaining: true }, [eggOptions]);
return initBattleWithEnemyConfig(scene, config);
@ -185,7 +178,7 @@ export const ATrainersTestEncounter: IMysteryEncounter =
)
.withOutroDialogue([
{
text: `${namespace}:outro`,
text: `${namespace}.outro`,
},
])
.build();

View File

@ -1,10 +1,10 @@
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { PersistentModifierRequirement } from "../mystery-encounter-requirements";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -21,6 +21,7 @@ import { BattlerIndex } from "#app/battle";
import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#app/data/pokeball";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:absoluteAvarice";
@ -30,7 +31,7 @@ const namespace = "mysteryEncounter:absoluteAvarice";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/58 | GitHub Issue #58}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const AbsoluteAvariceEncounter: IMysteryEncounter =
export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
@ -191,13 +192,13 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
encounter.misc = { berryItemsMap };
// Generates copies of the stolen berries to put on the Greedent
const bossModifierTypes: PokemonHeldItemModifierType[] = [];
const bossModifierConfigs: HeldModifierConfig[] = [];
berryItems.forEach(berryMod => {
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
for (let i = 0; i < berryMod.stackCount; i++) {
const modifierType = generateModifierTypeOption(scene, modifierTypes.BERRY, [berryMod.berryType]).type as PokemonHeldItemModifierType;
bossModifierTypes.push(modifierType);
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
bossModifierConfigs.push({ modifierType });
}
scene.removeModifier(berryMod);
@ -212,7 +213,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
isBoss: true,
bossSegments: 3,
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF],
modifierTypes: bossModifierTypes,
modifierConfigs: bossModifierConfigs,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
@ -243,7 +244,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter;
// Provides 1x Reviver Seed to each party member at end of battle
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const givePartyPokemonReviverSeeds = () => {
const party = scene.getParty();
party.forEach(p => {
@ -296,7 +297,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
Phaser.Math.RND.shuffle(berryTypesAsArray);
const randBerryType = berryTypesAsArray.pop();
const berryModType = generateModifierTypeOption(scene, modifierTypes.BERRY, [randBerryType]).type as BerryModifierType;
const berryModType = generateModifierType(scene, modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
applyModifierTypeToPlayerPokemon(scene, pokemon, berryModType);
}
}

View File

@ -3,7 +3,7 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
@ -21,7 +21,7 @@ const namespace = "mysteryEncounter:offerYouCantRefuse";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/72 | GitHub Issue #72}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const AnOfferYouCantRefuseEncounter: IMysteryEncounter =
export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)

View File

@ -1,7 +1,7 @@
import { BattleStat } from "#app/data/battle-stat";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import {
EnemyPartyConfig, generateModifierTypeOption,
EnemyPartyConfig, generateModifierType, generateModifierTypeOption,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, setEncounterExp,
setEncounterRewards
@ -19,7 +19,7 @@ import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -40,7 +40,7 @@ const namespace = "mysteryEncounter:berriesAbound";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const BerriesAboundEncounter: IMysteryEncounter =
export const BerriesAboundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
@ -315,7 +315,7 @@ export const BerriesAboundEncounter: IMysteryEncounter =
async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
const berry = generateModifierTypeOption(scene, modifierTypes.BERRY, [berryType]).type as BerryModifierType;
const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType;
const party = scene.getParty();

View File

@ -1,11 +1,11 @@
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
@ -56,7 +56,7 @@ const RANDOM_ABILITY_POOL = [
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ClowningAroundEncounter: IMysteryEncounter =
export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(80, 180)
@ -128,8 +128,7 @@ export const ClowningAroundEncounter: IMysteryEncounter =
},
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
species: getPokemonSpecies(Species.BLACEPHALON),
ability: ability,
mysteryEncounterData: new MysteryEncounterPokemonData(null, null, null, [randSeedInt(18), randSeedInt(18)]),
mysteryEncounterData: new MysteryEncounterPokemonData(null, ability, null, [randSeedInt(18), randSeedInt(18)]),
isBoss: true,
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
},
@ -483,9 +482,9 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem
const newItemType = pool[randIndex];
let newMod;
if (tier === "Berries") {
newMod = generateModifierTypeOption(scene, modifierTypes.BERRY, [newItemType[0]]).type as PokemonHeldItemModifierType;
newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
} else {
newMod = generateModifierTypeOption(scene, newItemType[0]).type as PokemonHeldItemModifierType;
newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType;
}
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
// Decrement max stacks and remove from pool if at max

View File

@ -3,7 +3,7 @@ import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -77,7 +77,7 @@ const SENSU_STYLE_BIOMES = [
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/130 | GitHub Issue #130}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DancingLessonsEncounter: IMysteryEncounter =
export const DancingLessonsEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)

View File

@ -6,7 +6,7 @@ import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { modifierTypes } from "#app/modifier/modifier-type";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
@ -74,7 +74,7 @@ const excludedBosses = [
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/61 | GitHub Issue #61}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DarkDealEncounter: IMysteryEncounter =
export const DarkDealEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([

View File

@ -1,10 +1,10 @@
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -36,7 +36,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/57 | GitHub Issue #57}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DelibirdyEncounter: IMysteryEncounter =
export const DelibirdyEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
@ -113,7 +113,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
@ -169,7 +169,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`);
}
return null;
@ -188,7 +188,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
@ -201,7 +201,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);
@ -281,7 +281,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true);

View File

@ -7,7 +7,7 @@ import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, {
import MysteryEncounter, {
MysteryEncounterBuilder,
} from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -20,7 +20,7 @@ const namespace = "mysteryEncounter:departmentStoreSale";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/33 | GitHub Issue #33}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const DepartmentStoreSaleEncounter: IMysteryEncounter =
export const DepartmentStoreSaleEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 100)

View File

@ -7,7 +7,7 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -19,7 +19,7 @@ const namespace = "mysteryEncounter:fieldTrip";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/17 | GitHub Issue #17}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FieldTripEncounter: IMysteryEncounter =
export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)

View File

@ -3,7 +3,7 @@ import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig
import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { TypeRequirement } from "../mystery-encounter-requirements";
import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
@ -36,7 +36,7 @@ const DAMAGE_PERCENTAGE: number = 20;
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/88 | GitHub Issue #88}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FieryFalloutEncounter: IMysteryEncounter =
export const FieryFalloutEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(40, 180)

View File

@ -17,7 +17,7 @@ import {
} from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -33,7 +33,7 @@ const namespace = "mysteryEncounter:fightOrFlight";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FightOrFlightEncounter: IMysteryEncounter =
export const FightOrFlightEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180

View File

@ -127,7 +127,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
* @param scene Battle scene
* @param guidePokemon pokemon choosen as a guide
*/
function handlePokemonGuidingYouPhase(scene: BattleScene) {
async function handlePokemonGuidingYouPhase(scene: BattleScene) {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
const { mysteryEncounter } = scene.currentBattle;

View File

@ -15,7 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** the i18n namespace for the encounter */
@ -26,7 +26,7 @@ const namespace = "mysteryEncounter:mysteriousChallengers";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/41 | GitHub Issue #41}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const MysteriousChallengersEncounter: IMysteryEncounter =
export const MysteriousChallengersEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180

View File

@ -5,7 +5,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { randSeedInt } from "#app/utils.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -18,7 +18,7 @@ const namespace = "mysteryEncounter:mysteriousChest";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/32 | GitHub Issue #32}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const MysteriousChestEncounter: IMysteryEncounter =
export const MysteriousChestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
@ -116,9 +116,8 @@ export const MysteriousChestEncounter: IMysteryEncounter =
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
// Show which Pokemon was KOed, then leave encounter with no rewards
// Does this synchronously so that game over doesn't happen over result message
await showEncounterText(scene, `${namespace}.option.1.bad`).then(() => {
await showEncounterText(scene, `${namespace}.option.1.bad`);
leaveEncounterWithoutBattle(scene);
});
}
})
.build()

View File

@ -2,7 +2,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -20,7 +20,7 @@ const namespace = "mysteryEncounter:partTimer";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/82 | GitHub Issue #82}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const PartTimerEncounter: IMysteryEncounter =
export const PartTimerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
@ -115,7 +115,7 @@ export const PartTimerEncounter: IMysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}:invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`);
}
return null;
@ -194,7 +194,7 @@ export const PartTimerEncounter: IMysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}:invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`);
}
return null;

View File

@ -1,7 +1,7 @@
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import { ScanIvsPhase, SummonPhase } from "#app/phases";
@ -17,7 +17,6 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter
import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:safariZone";
@ -29,7 +28,7 @@ const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/39 | GitHub Issue #39}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const SafariZoneEncounter: IMysteryEncounter =
export const SafariZoneEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180)
@ -66,7 +65,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter;
encounter.encounterMode = MysteryEncounterMode.CONTINUOUS_ENCOUNTER;
encounter.continuousEncounter = true;
encounter.misc = {
safariPokemonRemaining: 3
};
@ -104,7 +103,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
/**
* SAFARI ZONE MINIGAME OPTIONS
*
* Catch and flee rate **stages** are calculated in the same way stat changes are (they range from -6/+6)
* Catch and flee rate stages are calculated in the same way stat changes are (they range from -6/+6)
* https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone
*
* Catch Rate calculation:
@ -130,21 +129,23 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.withOptionPhase(async (scene: BattleScene) => {
// Throw a ball option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = encounter.misc.pokemon;
const catchResult = await throwPokeball(scene, pokemon);
if (catchResult) {
// You caught pokemon
// Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
}
} else {
// Pokemon failed to catch, end turn
// Pokemon catch failed, end turn
await doEndTurn(scene, 0);
}
return true;
@ -217,14 +218,16 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
})
.withOptionPhase(async (scene: BattleScene) => {
// Flee option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon;
const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = encounter.misc.pokemon;
await doPlayerFlee(scene, pokemon);
// Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) {
if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
}
return true;
@ -487,6 +490,7 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) {
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
} else {
// End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true);
}
} else {

View File

@ -1,4 +1,4 @@
import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { StatusEffect } from "#app/data/status-effect";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
@ -6,7 +6,7 @@ import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -22,7 +22,7 @@ const namespace = "mysteryEncounter:shadyVitaminDealer";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/34 | GitHub Issue #34}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ShadyVitaminDealerEncounter: IMysteryEncounter =
export const ShadyVitaminDealerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180)
@ -79,8 +79,8 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type,
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type,
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
];
encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].name);
@ -95,7 +95,7 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
// If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:invalid_selection`);
return getEncounterText(scene, `${namespace}.invalid_selection`);
}
return null;
@ -162,8 +162,8 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens
const modifiers = [
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type,
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type,
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
];
encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].name);

View File

@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
@ -25,7 +25,7 @@ const namespace = "mysteryEncounter:slumberingSnorlax";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/103 | GitHub Issue #103}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const SlumberingSnorlaxEncounter: IMysteryEncounter =
export const SlumberingSnorlaxEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180

View File

@ -2,7 +2,7 @@ import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, up
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
@ -25,7 +25,7 @@ const MAX_POKEMON_PRICE_MULTIPLIER = 6;
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/36 | GitHub Issue #36}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const ThePokemonSalesmanEncounter: IMysteryEncounter =
export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180)

View File

@ -1,8 +1,8 @@
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { Nature } from "#app/data/nature";
@ -26,7 +26,7 @@ const namespace = "mysteryEncounter:theStrongStuff";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/54 | GitHub Issue #54}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheStrongStuffEncounter: IMysteryEncounter =
export const TheStrongStuffEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
@ -74,12 +74,20 @@ export const TheStrongStuffEncounter: IMysteryEncounter =
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierTypes: [
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType,
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.APICOT]).type as PokemonHeldItemModifierType,
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.GANLON]).type as PokemonHeldItemModifierType,
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.LUM]).type as PokemonHeldItemModifierType,
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.LUM]).type as PokemonHeldItemModifierType
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2
}
],
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {

View File

@ -0,0 +1,489 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { Nature } from "#enums/nature";
import { Type } from "#app/data/type";
import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat";
import { PartyHealPhase, ReturnPhase, ShowTrainerPhase } from "#app/phases";
import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/ability";
import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theWinstrateChallenge";
/**
* The Winstrate Challenge encounter.
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/136 | GitHub Issue #136}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheWinstrateChallengeEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(80, 180)
.withIntroSpriteConfigs([
{
spriteKey: "vito",
fileRoot: "trainer",
hasShadow: false,
x: 16,
y: -4
},
{
spriteKey: "vivi",
fileRoot: "trainer",
hasShadow: false,
x: -14,
y: -4
},
{
spriteKey: "victor",
fileRoot: "trainer",
hasShadow: true,
x: -32
},
{
spriteKey: "victoria",
fileRoot: "trainer",
hasShadow: true,
x: 40,
},
{
spriteKey: "vicky",
fileRoot: "trainer",
hasShadow: true,
x: 3,
y: 5,
yShadow: 5
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
},
])
.withAutoHideIntroVisuals(false)
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Loaded back to front for pop() operations
encounter.enemyPartyConfigs.push(getVitoTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVickyTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getViviTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig(scene));
encounter.enemyPartyConfigs.push(getVictorTrainerConfig(scene));
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
speaker: "trainerNames:victor",
text: `${namespace}.option.1.selected`,
},
],
},
async (scene: BattleScene) => {
// Spawn 5 trainer battles back to back with Macho Brace in rewards
// scene.currentBattle.mysteryEncounter.continuousEncounter = true;
scene.currentBattle.mysteryEncounter.doContinueEncounter = (scene: BattleScene) => {
return endTrainerBattleAndShowDialogue(scene);
};
await transitionMysteryEncounterIntroVisuals(scene, true, false);
await spawnNextTrainerOrEndEncounter(scene);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.2.selected`,
},
],
},
async (scene: BattleScene) => {
// Refuse the challenge, they full heal the party and give the player a Rarer Candy
scene.unshiftPhase(new PartyHealPhase(scene, true));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], fillRemaining: false });
leaveEncounterWithoutBattle(scene);
}
)
.build();
async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter;
const nextConfig = encounter.enemyPartyConfigs.pop();
if (!nextConfig) {
await transitionMysteryEncounterIntroVisuals(scene, false, false);
await showEncounterDialogue(scene, `${namespace}.victory`, `${namespace}.speaker`);
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE], fillRemaining: false });
encounter.doContinueEncounter = null;
leaveEncounterWithoutBattle(scene, false, MysteryEncounterMode.TRAINER_BATTLE);
} else {
await initBattleWithEnemyConfig(scene, nextConfig);
}
}
function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise<void> {
return new Promise(async resolve => {
if (scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length === 0) {
// Battle is over
scene.tweens.add({
targets: scene.currentBattle.trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(scene.currentBattle.trainer, true);
}
});
await spawnNextTrainerOrEndEncounter(scene);
resolve(); // Wait for all dialogue/post battle stuff to complete before resolving
} else {
scene.arena.resetArenaEffects();
const playerField = scene.getPlayerField();
playerField.forEach((_, p) => scene.unshiftPhase(new ReturnPhase(scene, p)));
for (const pokemon of scene.getParty()) {
// Only trigger form change when Eiscue is in Noice form
// Hardcoded Eiscue for now in case it is fused with another pokemon
if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) {
scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
}
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
scene.unshiftPhase(new ShowTrainerPhase(scene));
// Hide the trainer and init next battle
const trainer = scene.currentBattle.trainer;
// Unassign previous trainer from battle so it isn't destroyed before animation completes
scene.currentBattle.trainer = null;
await spawnNextTrainerOrEndEncounter(scene);
scene.tweens.add({
targets: trainer,
x: "+=16",
y: "-=16",
alpha: 0,
ease: "Sine.easeInOut",
duration: 750,
onComplete: () => {
scene.field.remove(trainer, true);
resolve();
}
});
}
});
}
function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VICTOR,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SWELLOW),
isBoss: false,
abilityIndex: 0, // Guts
nature: Nature.ADAMANT,
moveSet: [Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
]
},
{
species: getPokemonSpecies(Species.OBSTAGOON),
isBoss: false,
abilityIndex: 1, // Guts
nature: Nature.ADAMANT,
moveSet: [Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
}
]
};
}
function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VICTORIA,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.ROSERADE),
isBoss: false,
abilityIndex: 0, // Natural Cure
nature: Nature.CALM,
moveSet: [Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.GARDEVOIR),
isBoss: false,
formIndex: 1,
nature: Nature.TIMID,
moveSet: [Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.PSYCHIC]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FAIRY]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false
}
]
}
]
};
}
function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VIVI,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SEAKING),
isBoss: false,
abilityIndex: 3, // Lightning Rod
nature: Nature.ADAMANT,
moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.BRELOOM),
isBoss: false,
abilityIndex: 1, // Poison Heal
nature: Nature.JOLLY,
moveSet: [Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false
},
{
modifierType: generateModifierType(scene, modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.CAMERUPT),
isBoss: false,
formIndex: 1,
nature: Nature.CALM,
moveSet: [Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 3,
isTransferable: false
},
]
}
]
};
}
function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VICKY,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.MEDICHAM),
isBoss: false,
formIndex: 1,
nature: Nature.IMPISH,
moveSet: [Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
isTransferable: false
}
]
}
]
};
}
function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig {
return {
trainerType: TrainerType.VITO,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.HISUI_ELECTRODE),
isBoss: false,
abilityIndex: 0, // Soundproof
nature: Nature.MODEST,
moveSet: [Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.SWALOT),
isBoss: false,
abilityIndex: 2, // Gluttony
nature: Nature.QUIET,
moveSet: [Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
stackCount: 2,
}
]
},
{
species: getPokemonSpecies(Species.DODRIO),
isBoss: false,
abilityIndex: 2, // Tangled Feet
nature: Nature.JOLLY,
moveSet: [Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
}
]
},
{
species: getPokemonSpecies(Species.ALAKAZAM),
isBoss: false,
formIndex: 1,
nature: Nature.BOLD,
moveSet: [Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
]
},
{
species: getPokemonSpecies(Species.DARMANITAN),
isBoss: false,
abilityIndex: 0, // Sheer Force
nature: Nature.IMPISH,
moveSet: [Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE],
modifierConfigs: [
{
modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false
},
]
}
]
};
}

View File

@ -14,7 +14,7 @@ import { randSeedShuffle } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -28,7 +28,7 @@ const namespace = "mysteryEncounter:trainingSession";
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/43 | GitHub Issue #43}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrainingSessionEncounter: IMysteryEncounter =
export const TrainingSessionEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
@ -444,7 +444,7 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon,segment
formIndex: playerPokemon.formIndex,
level: playerPokemon.level,
dataSource: data,
modifierTypes: modifierTypes,
modifierConfigs: modifierTypes,
},
],
};

View File

@ -1,8 +1,8 @@
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { ModifierRewardPhase } from "#app/phases";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -28,7 +28,7 @@ const SOUND_EFFECT_WAIT_TIME = 700;
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/74 | GitHub Issue #74}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrashToTreasureEncounter: IMysteryEncounter =
export const TrashToTreasureEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180)
@ -147,8 +147,8 @@ export const TrashToTreasureEncounter: IMysteryEncounter =
.build();
async function tryApplyDigRewardItems(scene: BattleScene) {
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType;
const leftovers = generateModifierTypeOption(scene, modifierTypes.LEFTOVERS).type as PokemonHeldItemModifierType;
const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
const leftovers = generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
const party = scene.getParty();

View File

@ -2,7 +2,7 @@ import { Type } from "#app/data/type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -82,7 +82,7 @@ const excludedPokemon = [
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/137 | GitHub Issue #137}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const WeirdDreamEncounter: IMysteryEncounter =
export const WeirdDreamEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
.withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([
@ -205,11 +205,6 @@ export const WeirdDreamEncounter: IMysteryEncounter =
return true;
}
)
.withOutroDialogue([
{
text: `${namespace}.outro`
}
])
.build();
interface PokemonTransformation {

View File

@ -6,25 +6,16 @@ import BattleScene from "#app/battle-scene";
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import * as Utils from "#app/utils";
import { StatusEffect } from "../status-effect";
import MysteryEncounterDialogue, {
OptionTextDisplay
} from "./mystery-encounter-dialogue";
import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter-dialogue";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
import {
EncounterPokemonRequirement,
EncounterSceneRequirement,
HealthRatioRequirement,
PartySizeRequirement,
StatusEffectRequirement,
WaveRangeRequirement
} from "./mystery-encounter-requirements";
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";
export interface StartOfBattleEffect {
export interface EncounterStartOfBattleEffect {
sourcePokemon?: Pokemon;
sourceBattlerIndex?: BattlerIndex;
targets: BattlerIndex[];
@ -33,7 +24,7 @@ export interface StartOfBattleEffect {
followUp?: boolean;
}
export default interface IMysteryEncounter {
export default interface MysteryEncounter {
/**
* Required params
*/
@ -44,13 +35,44 @@ export default interface IMysteryEncounter {
* Optional params
*/
encounterTier?: MysteryEncounterTier;
/**
* Custom battle animations that are configured for encounter effects and visuals
* Specify here so that assets are loaded on initialization of encounter
*/
encounterAnimations?: EncounterAnim[];
/**
* If true, hides "A Wild X Appeared" etc. messages
* Default true
*/
hideBattleIntroMessage?: boolean;
/**
* If true, when an option is selected the field visuals will fade out automatically
* Default false
*/
autoHideIntroVisuals?: boolean;
/**
* Intro visuals on the field will slide in from the right instead of the left
* Default false
*/
enterIntroVisualsFromRight?: boolean;
/**
* If true, allows catching a wild pokemon during the encounter
* Default false
*/
catchAllowed?: boolean;
/**
* If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave
* MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE
* Default false
*/
continuousEncounter?: boolean;
/**
* Maximum number of times the encounter can be seen per run
* Rogue tier encounters default to 1, others default to 3
*/
maxAllowedEncounters?: number;
/**
* Event callback functions
*/
@ -62,6 +84,8 @@ export default interface IMysteryEncounter {
doEncounterExp?: (scene: BattleScene) => boolean;
/** Will provide the player a rewards shop for that wave */
doEncounterRewards?: (scene: BattleScene) => boolean;
/** Will execute callback during VictoryPhase of a continuousEncounter */
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
/**
* Requirements
@ -90,6 +114,7 @@ export default interface IMysteryEncounter {
/**
* Data used for setting up/initializing enemy party in battles
* Can store multiple configs so that one can be chosen based on option selected
* Should usually be defined in `onInit()` or `onPreOptionPhase()`
*/
enemyPartyConfigs?: EnemyPartyConfig[];
/**
@ -131,7 +156,7 @@ export default interface IMysteryEncounter {
/**
* Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases
*/
startOfBattleEffects?: StartOfBattleEffect[];
startOfBattleEffects?: EncounterStartOfBattleEffect[];
/**
* Can be set higher or lower based on the type of battle or exp gained for an option/encounter
* Defaults to 1
@ -149,14 +174,14 @@ export default interface IMysteryEncounter {
* These objects will be saved as part of session data any time the player is on a floor with an encounter
* Unless you know what you're doing, you should use MysteryEncounterBuilder to create an instance for this class
*/
export default class IMysteryEncounter implements IMysteryEncounter {
export default class MysteryEncounter implements MysteryEncounter {
/**
* Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave
* You should only need to interact via getter/update methods
*/
private seedOffset?: any;
constructor(encounter: IMysteryEncounter) {
constructor(encounter: MysteryEncounter) {
if (!isNullOrUndefined(encounter)) {
Object.assign(this, encounter);
}
@ -170,6 +195,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false;
this.autoHideIntroVisuals = this.autoHideIntroVisuals ?? true;
this.enterIntroVisualsFromRight = this.enterIntroVisualsFromRight ?? false;
this.continuousEncounter = this.continuousEncounter ?? false;
// Reset any dirty flags or encounter data
this.startOfBattleEffectsComplete = false;
@ -238,11 +264,11 @@ export default class IMysteryEncounter implements IMysteryEncounter {
}
if (truePrimaryPool.length > 0) {
// always choose from the non-overlapping pokemon first
// Always choose from the non-overlapping pokemon first
this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
return true;
} else {
// if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool
// If there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool
if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
// is this working?
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)];
@ -371,7 +397,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
}
/**
* If an encounter uses {@link MysteryEncounterMode.CONTINUOUS_ENCOUNTER},
* If an encounter uses {@link MysteryEncounterMode.continuousEncounter},
* should rely on this value for seed offset instead of wave index.
*
* This offset is incremented for each new {@link MysteryEncounterPhase} that occurs,
@ -396,7 +422,11 @@ export default class IMysteryEncounter implements IMysteryEncounter {
}
}
export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Builder class for creating a MysteryEncounter
* must call `build()` at the end after specifying all params for the MysteryEncounter
*/
export class MysteryEncounterBuilder implements Partial<MysteryEncounter> {
encounterType?: MysteryEncounterType;
options?: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [null, null];
spriteConfigs?: MysteryEncounterSpriteConfig[];
@ -418,7 +448,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
hideBattleIntroMessage?: boolean;
hideIntroVisuals?: boolean;
enterIntroVisualsFromRight?: boolean;
enemyPartyConfigs?: EnemyPartyConfig[] = [];
continuousEncounter?: boolean;
/**
* REQUIRED
@ -429,7 +459,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterType
* @returns this
*/
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<IMysteryEncounter, "encounterType"> {
static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick<MysteryEncounter, "encounterType"> {
return Object.assign(new MysteryEncounterBuilder(), { encounterType: encounterType });
}
@ -442,7 +472,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
* @returns
*/
withOption(option: MysteryEncounterOption): this & Pick<IMysteryEncounter, "options"> {
withOption(option: MysteryEncounterOption): this & Pick<MysteryEncounter, "options"> {
if (this.options[0] === null) {
return Object.assign(this, { options: [option, this.options[0]] });
} else if (this.options[1] === null) {
@ -464,7 +494,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param callback - {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<MysteryEncounter, "options"> {
return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build());
}
@ -478,7 +508,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param callback - {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<MysteryEncounter, "options"> {
return this.withOption(new MysteryEncounterOptionBuilder()
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
@ -492,7 +522,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param spriteConfigs
* @returns
*/
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<IMysteryEncounter, "spriteConfigs"> {
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<MysteryEncounter, "spriteConfigs"> {
return Object.assign(this, { spriteConfigs: spriteConfigs });
}
@ -521,28 +551,38 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterTier
* @returns
*/
withEncounterTier(encounterTier: MysteryEncounterTier): this & Required<Pick<IMysteryEncounter, "encounterTier">> {
withEncounterTier(encounterTier: MysteryEncounterTier): this & Required<Pick<MysteryEncounter, "encounterTier">> {
return Object.assign(this, { encounterTier: encounterTier });
}
/**
* Defines any EncounterAnim animations that are intended to be used during the encounter
* EncounterAnims can be played at any point during an encounter or callback
* EncounterAnims are custom battle animations (think Ice Beam) that can be played at any point during an encounter or callback
* They just need to be specified here so that resources are loaded on encounter init
* @param encounterAnimations
* @returns
*/
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> {
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<MysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
return Object.assign(this, { encounterAnimations: animations });
}
/**
* If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave
* MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE
* Default false
* @param continuousEncounter
*/
withContinuousEncounter(continuousEncounter: boolean): this & Required<Pick<MysteryEncounter, "continuousEncounter">> {
return Object.assign(this, { continuousEncounter: continuousEncounter });
}
/**
* Sets the maximum number of times that an encounter can spawn in a given Classic run
* @param maxAllowedEncounters
* @returns
*/
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> {
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<MysteryEncounter, "maxAllowedEncounters">> {
return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters });
}
@ -553,7 +593,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement
* @returns
*/
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounter, "requirements">> {
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounter, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement.");
}
@ -568,7 +608,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param max optional max wave. If not given, defaults to min => exact wave
* @returns
*/
withSceneWaveRangeRequirement(min: number, max?: number): this & Required<Pick<IMysteryEncounter, "requirements">> {
withSceneWaveRangeRequirement(min: number, max?: number): this & Required<Pick<MysteryEncounter, "requirements">> {
return this.withSceneRequirement(new WaveRangeRequirement([min, max ?? min]));
}
@ -580,7 +620,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param excludeFainted - if true, only counts unfainted mons
* @returns
*/
withScenePartySizeRequirement(min: number, max?: number, excludeFainted?: boolean): this & Required<Pick<IMysteryEncounter, "requirements">> {
withScenePartySizeRequirement(min: number, max?: number, excludeFainted?: boolean): this & Required<Pick<MysteryEncounter, "requirements">> {
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
}
@ -590,7 +630,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement {@linkcode EncounterPokemonRequirement}
* @returns
*/
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<MysteryEncounter, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
@ -607,7 +647,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param invertQuery if true will invert the query
* @returns
*/
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<MysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery));
}
@ -619,14 +659,14 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param invertQuery if true will invert the query
* @returns
*/
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> {
withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required<Pick<MysteryEncounter, "primaryPokemonRequirements">> {
return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery));
}
// TODO: Maybe add an optional parameter for excluding primary pokemon from the support cast?
// ex. if your only grass type pokemon, a snivy, is chosen as primary, if the support pokemon requires a grass type, the event won't trigger because
// it's already been
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<IMysteryEncounter, "secondaryPokemonRequirements">> {
withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required<Pick<MysteryEncounter, "secondaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement.");
}
@ -646,7 +686,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "doEncounterRewards">> {
return Object.assign(this, { doEncounterRewards: doEncounterRewards });
}
@ -660,7 +700,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "doEncounterExp">> {
return Object.assign(this, { doEncounterExp: doEncounterExp });
}
@ -671,7 +711,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param onInit - synchronous callback function to perform as soon as the encounter is selected for the next phase
* @returns
*/
withOnInit(onInit: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onInit">> {
withOnInit(onInit: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "onInit">> {
return Object.assign(this, { onInit: onInit });
}
@ -681,27 +721,17 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in
* @returns
*/
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onVisualsStart">> {
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<MysteryEncounter, "onVisualsStart">> {
return Object.assign(this, { onVisualsStart: onVisualsStart });
}
/**
* Defines any enemies to use for a battle from the mystery encounter
* @param enemyPartyConfig
* @returns
*/
withEnemyPartyConfig(enemyPartyConfig: EnemyPartyConfig): this & Required<Pick<IMysteryEncounter, "enemyPartyConfigs">> {
this.enemyPartyConfigs.push(enemyPartyConfig);
return Object.assign(this, { enemyPartyConfigs: this.enemyPartyConfigs });
}
/**
* Can set whether catching is allowed or not on the encounter
* This flag can also be programmatically set inside option event functions or elsewhere
* @param catchAllowed - if true, allows enemy pokemon to be caught during the encounter
* @returns
*/
withCatchAllowed(catchAllowed: boolean): this & Required<Pick<IMysteryEncounter, "catchAllowed">> {
withCatchAllowed(catchAllowed: boolean): this & Required<Pick<MysteryEncounter, "catchAllowed">> {
return Object.assign(this, { catchAllowed: catchAllowed });
}
@ -709,7 +739,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param hideBattleIntroMessage - if true, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
* @returns
*/
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<MysteryEncounter, "hideBattleIntroMessage">> {
return Object.assign(this, { hideBattleIntroMessage: hideBattleIntroMessage });
}
@ -717,7 +747,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param autoHideIntroVisuals - if false, will not hide the intro visuals that are displayed at the beginning of encounter
* @returns
*/
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<MysteryEncounter, "autoHideIntroVisuals">> {
return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals });
}
@ -726,7 +756,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false
* @returns
*/
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> {
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<MysteryEncounter, "enterIntroVisualsFromRight">> {
return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight });
}
@ -806,7 +836,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
*
* @returns
*/
build(this: IMysteryEncounter): IMysteryEncounter {
return new IMysteryEncounter(this);
build(this: MysteryEncounter): MysteryEncounter {
return new MysteryEncounter(this);
}
}

View File

@ -10,7 +10,7 @@ import { MysteriousChestEncounter } from "./encounters/mysterious-chest-encounte
import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
import { SlumberingSnorlaxEncounter } from "./encounters/slumbering-snorlax-encounter";
import { TrainingSessionEncounter } from "./encounters/training-session-encounter";
import IMysteryEncounter from "./mystery-encounter";
import MysteryEncounter from "./mystery-encounter";
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
@ -25,6 +25,7 @@ import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters
import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter";
import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter";
import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-encounter";
import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter";
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
@ -132,7 +133,7 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [
Biome.ISLAND
];
export const allMysteryEncounters: { [encounterType: number]: IMysteryEncounter } = {};
export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {};
const extremeBiomeEncounters: MysteryEncounterType[] = [];
@ -147,6 +148,7 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.SHADY_VITAMIN_DEALER,
MysteryEncounterType.THE_POKEMON_SALESMAN,
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
MysteryEncounterType.THE_WINSTRATE_CHALLENGE
];
const civilizationBiomeEncounters: MysteryEncounterType[] = [
@ -270,6 +272,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.PART_TIMER] = PartTimerEncounter;
allMysteryEncounters[MysteryEncounterType.DANCING_LESSONS] = DancingLessonsEncounter;
allMysteryEncounters[MysteryEncounterType.WEIRD_DREAM] = WeirdDreamEncounter;
allMysteryEncounters[MysteryEncounterType.THE_WINSTRATE_CHALLENGE] = TheWinstrateChallengeEncounter;
// Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => {

View File

@ -3,9 +3,9 @@ import { biomeLinks, BiomePoolTier } from "#app/data/biomes";
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon";
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { BattleEndPhase, EggLapsePhase, ExpPhase, GameOverPhase, MovePhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
import PokemonData from "#app/system/pokemon-data";
@ -30,8 +30,8 @@ import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-co
import PokemonSpecies from "#app/data/pokemon-species";
import Overrides from "#app/overrides";
import { Egg, IEggOptions } from "#app/data/egg";
import { Abilities } from "#enums/abilities";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
/**
* Animates exclamation sprite over trainer's head at start of encounter
@ -67,18 +67,18 @@ export interface EnemyPokemonConfig {
bossSegmentModifier?: number; // Additive to the determined segment number
mysteryEncounterData?: MysteryEncounterPokemonData;
formIndex?: number;
abilityIndex?: number;
level?: number;
gender?: Gender;
passive?: boolean;
moveSet?: Moves[];
nature?: Nature;
ivs?: [integer, integer, integer, integer, integer, integer];
ability?: Abilities;
shiny?: boolean;
/** Can set just the status, or pass a timer on the status turns */
status?: StatusEffect | [StatusEffect, number];
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
modifierTypes?: PokemonHeldItemModifierType[];
modifierConfigs?: HeldModifierConfig[];
tags?: BattlerTagType[];
dataSource?: PokemonData;
}
@ -258,10 +258,13 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
}
// Set summon data fields
if (!enemyPokemon.summonData) {
enemyPokemon.summonData = new PokemonSummonData();
}
// Set ability
if (!isNullOrUndefined(config.ability)) {
enemyPokemon.summonData.ability = config.ability;
if (!isNullOrUndefined(config.abilityIndex)) {
enemyPokemon.abilityIndex = config.abilityIndex;
}
// Set gender
@ -293,6 +296,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemyPokemon.initBattleInfo();
enemyPokemon.getBattleInfo().initInfo(enemyPokemon);
enemyPokemon.generateName();
}
loadEnemyAssets.push(enemyPokemon.loadAssets());
@ -315,8 +319,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
});
if (!loaded) {
regenerateModifierPoolThresholds(scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
const customModifiers = partyConfig?.pokemonConfigs?.map(config => config?.modifierTypes);
scene.generateEnemyModifiers(customModifiers);
const customModifierTypes = partyConfig?.pokemonConfigs?.map(config => config?.modifierConfigs);
scene.generateEnemyModifiers(customModifierTypes);
}
}
@ -363,7 +367,7 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS
* @param modifier
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
*/
export function generateModifierTypeOption(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption {
export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType {
const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
let result: ModifierType = modifierTypes[modifierId]?.();
@ -373,6 +377,17 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
.withTierFromPool();
result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result;
return result;
}
/**
* Converts modifier bullshit to an actual item
* @param scene - Battle Scene
* @param modifier
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc.
*/
export function generateModifierTypeOption(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption {
const result = generateModifierType(scene, modifier, pregenArgs);
return new ModifierTypeOption(result, 0);
}
@ -619,9 +634,10 @@ export function initSubsequentOptionSelect(scene: BattleScene, optionSelectSetti
* Will skip any shops and rewards, and queue the next encounter phase as normal
* @param scene
* @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available
* @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase)
*/
export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false) {
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.NO_BATTLE;
export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) {
scene.currentBattle.mysteryEncounter.encounterMode = encounterMode;
scene.clearPhaseQueue();
scene.clearPhaseQueueSplice();
handleMysteryEncounterVictory(scene, addHealPhase);
@ -644,18 +660,22 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
// If in repeated encounter variant, do nothing
// Variant must eventually be swapped in order to handle "true" end of the encounter
if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.CONTINUOUS_ENCOUNTER || doNotContinue) {
const encounter = scene.currentBattle.mysteryEncounter;
if (encounter.continuousEncounter || doNotContinue) {
return;
} else if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
} else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
scene.pushPhase(new EggLapsePhase(scene));
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
} else if (!scene.getEnemyParty().find(p => scene.currentBattle.mysteryEncounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
} else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
scene.pushPhase(new BattleEndPhase(scene));
if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
scene.pushPhase(new TrainerVictoryPhase(scene));
}
if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) {
if (!encounter.doContinueEncounter) {
// Only lapse eggs once for multi-battle encounters
scene.pushPhase(new EggLapsePhase(scene));
}
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
}
}

View File

@ -11,13 +11,13 @@ export enum TransformationScreenPosition {
}
/**
* Initiates an "evolution-like" animation to transform a pokemon (presumably from the player's party) into a new one, not necessarily an evolution species.
* Initiates an "evolution-like" animation to transform a previousPokemon (presumably from the player's party) into a new one, not necessarily an evolution species.
* @param scene
* @param pokemon
* @param transformedPokemon
* @param previousPokemon
* @param transformPokemon
* @param screenPosition
*/
export function doPokemonTransformationSequence(scene: BattleScene, pokemon: PlayerPokemon, transformedPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) {
export function doPokemonTransformationSequence(scene: BattleScene, previousPokemon: PlayerPokemon, transformPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) {
return new Promise<void>(resolve => {
const transformationContainer = scene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container;
const transformationBaseBg = scene.add.image(0, 0, "default_bg");
@ -36,7 +36,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, pokemon: Pla
const yOffset = screenPosition !== TransformationScreenPosition.CENTER ? -15 : 0;
const getPokemonSprite = () => {
const ret = scene.addPokemonSprite(pokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub");
const ret = scene.addPokemonSprite(previousPokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub");
ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
return ret;
};
@ -54,31 +54,31 @@ export function doPokemonTransformationSequence(scene: BattleScene, pokemon: Pla
pokemonEvoTintSprite.setTintFill(0xFFFFFF);
[ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
sprite.play(pokemon.getSpriteKey(true));
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(pokemon.getTeraType()) });
sprite.play(previousPokemon.getSpriteKey(true));
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) });
sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", pokemon.getSpriteKey());
sprite.setPipelineData("shiny", pokemon.shiny);
sprite.setPipelineData("variant", pokemon.variant);
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
sprite.setPipelineData("shiny", previousPokemon.shiny);
sprite.setPipelineData("variant", previousPokemon.variant);
[ "spriteColors", "fusionSpriteColors" ].map(k => {
if (pokemon.summonData?.speciesForm) {
if (previousPokemon.summonData?.speciesForm) {
k += "Base";
}
sprite.pipelineData[k] = pokemon.getSprite().pipelineData[k];
sprite.pipelineData[k] = previousPokemon.getSprite().pipelineData[k];
});
});
[ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
sprite.play(transformedPokemon.getSpriteKey(true));
sprite.play(transformPokemon.getSpriteKey(true));
sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", transformedPokemon.shiny);
sprite.setPipelineData("variant", transformedPokemon.variant);
sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey());
sprite.setPipelineData("shiny", transformPokemon.shiny);
sprite.setPipelineData("variant", transformPokemon.variant);
[ "spriteColors", "fusionSpriteColors" ].map(k => {
if (transformedPokemon.summonData?.speciesForm) {
if (transformPokemon.summonData?.speciesForm) {
k += "Base";
}
sprite.pipelineData[k] = transformedPokemon.getSprite().pipelineData[k];
sprite.pipelineData[k] = transformPokemon.getSprite().pipelineData[k];
});
});
@ -123,9 +123,11 @@ export function doPokemonTransformationSequence(scene: BattleScene, pokemon: Pla
duration: 2000,
delay: 150,
easing: "Sine.easeIn",
// onComplete: () => {
// transformedPokemon.destroy();
// }
onComplete: () => {
previousPokemon.destroy();
transformPokemon.setVisible(false);
transformPokemon.setAlpha(1);
}
});
});
}

View File

@ -1958,4 +1958,19 @@ export const trainerConfigs: TrainerConfigs = {
}
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.VICTOR]: new TrainerConfig(++t).setName("Victor").setTitle("The Winstrates")
.setMoneyMultiplier(1) // The Winstrate trainers have total money multiplier of 6
.setPartyTemplates(trainerPartyTemplates.ONE_AVG_ONE_STRONG),
[TrainerType.VICTORIA]: new TrainerConfig(++t).setName("Victoria").setTitle("The Winstrates")
.setMoneyMultiplier(1)
.setPartyTemplates(trainerPartyTemplates.ONE_AVG_ONE_STRONG),
[TrainerType.VIVI]: new TrainerConfig(++t).setName("Vivi").setTitle("The Winstrates")
.setMoneyMultiplier(1)
.setPartyTemplates(trainerPartyTemplates.TWO_AVG_ONE_STRONG),
[TrainerType.VICKY]: new TrainerConfig(++t).setName("Vicky").setTitle("The Winstrates")
.setMoneyMultiplier(1)
.setPartyTemplates(trainerPartyTemplates.ONE_AVG),
[TrainerType.VITO]: new TrainerConfig(++t).setName("Vito").setTitle("The Winstrates")
.setMoneyMultiplier(2)
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(2, PartyMemberStrength.STRONG)))
};

View File

@ -2,8 +2,7 @@ export enum MysteryEncounterMode {
DEFAULT,
TRAINER_BATTLE,
WILD_BATTLE,
/** Enables wild boss music during encounter */
BOSS_BATTLE,
NO_BATTLE,
/** For spawning new encounter queries instead of continuing to next wave */
CONTINUOUS_ENCOUNTER
NO_BATTLE
}

View File

@ -22,5 +22,6 @@ export enum MysteryEncounterType {
CLOWNING_AROUND,
PART_TIMER,
DANCING_LESSONS,
WEIRD_DREAM
WEIRD_DREAM,
THE_WINSTRATE_CHALLENGE
}

View File

@ -75,6 +75,11 @@ export enum TrainerType {
MARLEY,
MIRA,
RILEY,
VICTOR,
VICTORIA,
VIVI,
VICKY,
VITO,
BROCK = 200,
MISTY,

View File

@ -1,6 +1,6 @@
import { GameObjects } from "phaser";
import BattleScene from "../battle-scene";
import IMysteryEncounter from "../data/mystery-encounters/mystery-encounter";
import MysteryEncounter from "../data/mystery-encounters/mystery-encounter";
import { Species } from "#enums/species";
import { isNullOrUndefined } from "#app/utils";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
@ -70,11 +70,11 @@ export class MysteryEncounterSpriteConfig {
* Note: intro visuals are not "Trainers" or any other specific game object, though they may contain trainer sprites
*/
export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container {
public encounter: IMysteryEncounter;
public encounter: MysteryEncounter;
public spriteConfigs: MysteryEncounterSpriteConfig[];
public enterFromRight: boolean;
constructor(scene: BattleScene, encounter: IMysteryEncounter) {
constructor(scene: BattleScene, encounter: MysteryEncounter) {
super(scene, -72, 76);
this.encounter = encounter;
this.enterFromRight = encounter.enterIntroVisualsFromRight ?? false;

View File

@ -10,7 +10,7 @@ import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp";
import { Stat } from "../data/pokemon-stat";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatTotalModifier } from "../modifier/modifier";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier } from "../modifier/modifier";
import { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
@ -804,6 +804,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
this.stats[s] = value;
}
this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, this.stats);
}
getNature(): Nature {

View File

@ -0,0 +1,7 @@
import { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
export default interface HeldModifierConfig {
modifierType: PokemonHeldItemModifierType;
stackCount?: number;
isTransferable?: boolean;
}

View File

@ -627,6 +627,51 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "You put up quite the display.\nBetter luck next time."
}
},
"winstrates_victor": {
"encounter": {
1: "That's the spirit! I like you!",
},
"victory": {
1: "A-ha! You're stronger than I thought!"
}
},
"winstrates_victoria": {
"encounter": {
1: `My goodness! Aren't you young?
$You must be quite the trainer to beat my husband, though.
$Now I suppose it's my turn to battle!`,
},
"victory": {
1: "Uwah! Just how strong are you?!"
}
},
"winstrates_vivi": {
"encounter": {
1: `You're stronger than Mom? Wow!
$But I'm strong, too!\nReally! Honestly!`,
},
"victory": {
1: "Huh? Did I really lose?\nSnivel... Grandmaaa!"
}
},
"winstrates_vicky": {
"encounter": {
1: `How dare you make my precious\ngranddaughter cry!
$I see I need to teach you a lesson.\nPrepare to feel the sting of defeat!`,
},
"victory": {
1: "Whoa! So strong!\nMy granddaughter wasn't lying."
}
},
"winstrates_vito": {
"encounter": {
1: `I trained together with my whole family,\nevery one of us!
$I'm not losing to anyone!`,
},
"victory": {
1: "I was better than everyone in my family.\nI've never lost before..."
}
},
"brock": {
"encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!",

View File

@ -262,6 +262,7 @@ export const modifierType: ModifierTypeTranslationEntries = {
"MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { name: "Shuckle Juice" },
"MYSTERY_ENCOUNTER_BLACK_SLUDGE": { name: "Black Sludge", description: "The stench is so powerful that healing items are no longer available to purchase in shops." },
"MYSTERY_ENCOUNTER_MACHO_BRACE": { name: "Macho Brace", description: "Defeating a Pokémon grants the holder a Macho Brace stack. Each stack slightly boosts stats, with an extra bonus at max stacks." },
"MYSTERY_ENCOUNTER_OLD_GATEAU": { name: "Old Gateau", description: "Increases the holder's {{stats}} stats by {{statValue}}." },
},
SpeciesBoosterItem: {

View File

@ -22,6 +22,7 @@ import { clowningAroundDialogue } from "#app/locales/en/mystery-encounters/clown
import { partTimerDialogue } from "#app/locales/en/mystery-encounters/part-timer-dialogue";
import { dancingLessonsDialogue } from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue";
import { weirdDreamDialogue } from "#app/locales/en/mystery-encounters/weird-dream-dialogue";
import { theWinstrateChallengeDialogue } from "#app/locales/en/mystery-encounters/the-winstrate-challenge-dialogue";
/**
* Injection patterns that can be used:
@ -71,5 +72,6 @@ export const mysteryEncounter = {
clowningAround: clowningAroundDialogue,
partTimer: partTimerDialogue,
dancingLessons: dancingLessonsDialogue,
weirdDream: weirdDreamDialogue
weirdDream: weirdDreamDialogue,
theWinstrateChallenge: theWinstrateChallengeDialogue
} as const;

View File

@ -0,0 +1,24 @@
export const theWinstrateChallengeDialogue = {
intro: "It's a family standing outside their house!",
speaker: "The Winstrates",
intro_dialogue: `We're the Winstrates!
$What do you say to taking on our family in a series of Pokémon battles?`,
title: "The Winstrate Challenge",
description: "The Winstrates are a family of 5 trainers, and they want to battle! If you beat all of them back-to-back, they'll give you a grand prize. But can you handle the heat?",
query: "What will you do?",
option: {
1: {
label: "Accept the Challenge",
tooltip: "(-) Brutal Battle\n(+) Special Item Reward",
selected: "That's the spirit! I like you!",
},
2: {
label: "Refuse the Challenge",
tooltip: "(+) Full Heal Party\n(+) Gain a Rarer Candy",
selected: "That's too bad. Say, your team looks worn out, why don't you stay awhile and rest?"
},
},
victory: `Congratulations on beating our challenge!
$Our family uses this Macho Brace to strengthen our Pokémon more effectively during their training.
$You may not need it, considering that you beat the whole lot of us, but we hope you'll accept it anyway!`,
};

View File

@ -19,6 +19,7 @@ export const titles: SimpleTranslationEntries = {
"galactic_boss": "Team Galactic Boss",
"plasma_boss": "Team Plasma Boss",
"flare_boss": "Team Flare Boss",
"the_winstrates": "The Winstrates'"
// Maybe if we add the evil teams we can add "Team Rocket" and "Team Aqua" etc. here as well as "Team Rocket Boss" and "Team Aqua Admin" etc.
} as const;
@ -275,6 +276,11 @@ export const trainerNames: SimpleTranslationEntries = {
"marley": "Marley",
"mira": "Mira",
"riley": "Riley",
"victor": "Victor",
"victoria": "Victoria",
"vivi": "Vivi",
"vicky": "Vicky",
"vito": "Vito",
// Double Names
"blue_red_double": "Blue & Red",

View File

@ -1506,6 +1506,7 @@ export const modifierTypes = {
return new PokemonBaseStatFlatModifierType(Utils.randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]);
}),
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.RemoveHealShopModifier(type)),
MYSTERY_ENCOUNTER_MACHO_BRACE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", "macho_brace", (type, args) => new Modifiers.PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id)),
};
interface ModifierPool {

View File

@ -793,6 +793,54 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
}
}
export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
readonly isTransferrable: boolean = false;
constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
}
matchType(modifier: Modifier): boolean {
return modifier instanceof PokemonIncrementingStatModifier;
}
clone(): PersistentModifier {
return new PokemonIncrementingStatModifier(this.type, this.pokemonId);
}
getArgs(): any[] {
return super.getArgs();
}
shouldApply(args: any[]): boolean {
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
}
apply(args: any[]): boolean {
// Modifies the passed in stats[] array by +1 per stack for HP, +2 per stack for other stats
// If the Macho Brace is at max stacks (50), adds additional 5% to total HP and 10% to other stats
args[1].forEach((v, i) => {
const isHp = i === 0;
let mult = 1;
if (this.stackCount === this.getMaxHeldItemCount(null)) {
mult = isHp ? 1.05 : 1.1;
}
const newVal = Math.floor((v + this.stackCount * (isHp ? 1 : 2)) * mult);
args[1][i] = Math.min(Math.max(newVal, 1), 999999);
});
return true;
}
getScoreMultiplier(): number {
return 1.2;
}
getMaxHeldItemCount(pokemon: Pokemon): integer {
return 50;
}
}
/**
* Modifier used for held items that apply {@linkcode Stat} boost(s)
* using a multiplier.

View File

@ -6,7 +6,7 @@ import { allMoves, applyFilteredMoveAttrs, applyMoveAttrs, AttackMove, BypassRed
import { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat";
import { BerryModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, IvScannerModifier, LapsingPersistentModifier, LapsingPokemonHeldItemModifier, MapModifier, Modifier, MoneyInterestModifier, MoneyMultiplierModifier, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PokemonMultiHitModifier, PokemonResetNegativeStatStageModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier } from "./modifier/modifier";
import { BerryModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, IvScannerModifier, LapsingPersistentModifier, LapsingPokemonHeldItemModifier, MapModifier, Modifier, MoneyInterestModifier, MoneyMultiplierModifier, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonMultiHitModifier, PokemonResetNegativeStatStageModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier } from "./modifier/modifier";
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
import { CommonAnim, CommonBattleAnim, initEncounterAnims, initMoveAnim, loadEncounterAnimAssets, loadMoveAnimAssets, MoveAnim } from "./data/battle-anims";
@ -1302,8 +1302,7 @@ export class NextEncounterPhase extends EncounterPhase {
this.scene.lastEnemyTrainer.destroy();
}
if (lastEncounterVisuals) {
this.scene.field.remove(lastEncounterVisuals);
lastEncounterVisuals.destroy();
this.scene.field.remove(lastEncounterVisuals, true);
this.scene.lastMysteryEncounter.introVisuals = null;
}
@ -1583,7 +1582,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
onComplete: () => this.scene.trainer.setVisible(false)
});
this.scene.time.delayedCall(750, () => this.summon());
} else if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene?.currentBattle?.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
} else if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
const trainerName = this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
const pokemonName = getPokemonNameWithAffix(this.getPokemon());
const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName });
@ -2287,7 +2286,7 @@ export class CommandPhase extends FieldPhase {
this.scene.ui.showText(null, 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else if (!isSwitch && this.scene.currentBattle.battleType === BattleType.TRAINER) {
} else if (!isSwitch && (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => {
@ -4138,6 +4137,12 @@ export class VictoryPhase extends PokemonPhase {
const participated = participantIds.has(pId);
if (participated) {
partyMember.addFriendship(2);
const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier);
if (!isNullOrUndefined(machoBraceModifier) && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this.scene)) {
machoBraceModifier.stackCount++;
this.scene.updateModifiers(true, true);
partyMember.updateInfo();
}
}
if (!expPartyMembers.includes(partyMember)) {
continue;

View File

@ -381,6 +381,10 @@ export class MysteryEncounterBattlePhase extends Phase {
/**
* Will handle (in order):
* - doContinueEncounter() callback for continuous encounters with back-to-back battles (this should push/shift its own phases as needed)
*
* OR
*
* - Any encounter reward logic that is set within MysteryEncounter doEncounterExp
* - Any encounter reward logic that is set within MysteryEncounter doEncounterRewards
* - Otherwise, can add a no-reward-item shop with only Potions, etc. if addHealPhase is true
@ -396,7 +400,13 @@ export class MysteryEncounterRewardsPhase extends Phase {
start() {
super.start();
const encounter = this.scene.currentBattle.mysteryEncounter;
if (encounter.doContinueEncounter) {
encounter.doContinueEncounter(this.scene).then(() => {
this.end();
});
} else {
this.scene.executeWithSeedOffset(() => {
if (this.scene.currentBattle.mysteryEncounter.doEncounterExp) {
this.scene.currentBattle.mysteryEncounter.doEncounterExp(this.scene);
@ -414,6 +424,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene));
this.end();
}
}
}
/**

View File

@ -43,7 +43,7 @@ import { Species } from "#enums/species";
import { applyChallenges, ChallengeType } from "#app/data/challenge.js";
import { Abilities } from "#app/enums/abilities.js";
import { MysteryEncounterData } from "../data/mystery-encounters/mystery-encounter-data";
import IMysteryEncounter from "../data/mystery-encounters/mystery-encounter";
import MysteryEncounter from "../data/mystery-encounters/mystery-encounter";
export const defaultStarterSpecies: Species[] = [
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
@ -126,7 +126,7 @@ export interface SessionSaveData {
gameVersion: string;
timestamp: integer;
challenges: ChallengeData[];
mysteryEncounter: IMysteryEncounter;
mysteryEncounter: MysteryEncounter;
mysteryEncounterData: MysteryEncounterData;
}
@ -1167,7 +1167,7 @@ export class GameData {
}
if (k === "mysteryEncounter") {
return new IMysteryEncounter(v);
return new MysteryEncounter(v);
}
if (k === "mysteryEncounterData") {

View File

@ -7,7 +7,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite
import { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, NewBattlePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves";
@ -123,7 +123,6 @@ describe("Clowning Around - Mystery Encounter", () => {
});
expect(config.pokemonConfigs[1]).toEqual({
species: getPokemonSpecies(Species.BLACEPHALON),
ability: expect.any(Number),
mysteryEncounterData: expect.anything(),
isBoss: true,
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
@ -145,8 +144,8 @@ describe("Clowning Around - Mystery Encounter", () => {
Abilities.MAGICIAN,
Abilities.SHEER_FORCE,
Abilities.PRANKSTER
]).toContain(config.pokemonConfigs[1].ability);
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].ability);
]).toContain(config.pokemonConfigs[1].mysteryEncounterData.ability);
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].mysteryEncounterData.ability);
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
expect(onInitResult).toBe(true);
@ -258,26 +257,26 @@ describe("Clowning Around - Mystery Encounter", () => {
// 2 Sitrus Berries on lead
scene.modifiers = [];
let itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType;
let itemType = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
// 2 Ganlon Berries on lead
itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.GANLON]).type as PokemonHeldItemModifierType;
itemType = generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
// 5 Golden Punch on lead (ultra)
itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_PUNCH).type as PokemonHeldItemModifierType;
itemType = generateModifierType(scene, modifierTypes.GOLDEN_PUNCH) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
// 5 Lucky Egg on lead (ultra)
itemType = generateModifierTypeOption(scene, modifierTypes.LUCKY_EGG).type as PokemonHeldItemModifierType;
itemType = generateModifierType(scene, modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
// 5 Soul Dew on lead (rogue)
itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType;
itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
// 2 Golden Egg on lead (rogue)
itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_EGG).type as PokemonHeldItemModifierType;
itemType = generateModifierType(scene, modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
// 5 Soul Dew on second party pokemon (these should not change)
itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType;
itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType;
await addItemToPokemon(scene, scene.getParty()[1], 5, itemType);
await runMysteryEncounterToEnd(game, 2);

View File

@ -13,7 +13,7 @@ import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encount
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
import { BerryType } from "#enums/berry-type";
@ -137,7 +137,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// 5 Healing Charms
scene.modifiers = [];
const abilityCharm = generateModifierTypeOption(scene, modifierTypes.ABILITY_CHARM).type.newModifier() as HiddenAbilityRateBoosterModifier;
const abilityCharm = generateModifierType(scene, modifierTypes.ABILITY_CHARM).newModifier() as HiddenAbilityRateBoosterModifier;
abilityCharm.stackCount = 4;
await scene.addModifier(abilityCharm, true, false, false, true);
await scene.updateModifiers(true);
@ -206,7 +206,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 2 Sitrus berries on party lead
scene.modifiers = [];
const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type;
const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]);
const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier;
sitrusMod.stackCount = 2;
await scene.addModifier(sitrusMod, true, false, false, true);
@ -227,7 +227,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead
scene.modifiers = [];
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);
@ -248,10 +248,10 @@ describe("Delibird-y - Mystery Encounter", () => {
// 99 Candy Jars
scene.modifiers = [];
const candyJar = generateModifierTypeOption(scene, modifierTypes.CANDY_JAR).type.newModifier() as LevelIncrementBoosterModifier;
const candyJar = generateModifierType(scene, modifierTypes.CANDY_JAR).newModifier() as LevelIncrementBoosterModifier;
candyJar.stackCount = 99;
await scene.addModifier(candyJar, true, false, false, true);
const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type;
const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]);
// Sitrus berries on party
const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier;
@ -277,12 +277,12 @@ describe("Delibird-y - Mystery Encounter", () => {
// 5 Healing Charms
scene.modifiers = [];
const healingCharm = generateModifierTypeOption(scene, modifierTypes.HEALING_CHARM).type.newModifier() as HealingBoosterModifier;
const healingCharm = generateModifierType(scene, modifierTypes.HEALING_CHARM).newModifier() as HealingBoosterModifier;
healingCharm.stackCount = 5;
await scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Reviver Seed on party lead
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);
@ -306,7 +306,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW);
const modifier = soulDew.newModifier(scene.getParty()[0]);
await scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -334,7 +334,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
// Set 1 Reviver Seed on party lead
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);
@ -368,7 +368,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 2 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW);
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 2;
await scene.addModifier(modifier, true, false, false, true);
@ -389,7 +389,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW);
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);
@ -410,12 +410,12 @@ describe("Delibird-y - Mystery Encounter", () => {
// 5 Healing Charms
scene.modifiers = [];
const healingCharm = generateModifierTypeOption(scene, modifierTypes.BERRY_POUCH).type.newModifier() as PreserveBerryModifier;
const healingCharm = generateModifierType(scene, modifierTypes.BERRY_POUCH).newModifier() as PreserveBerryModifier;
healingCharm.stackCount = 3;
await scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Soul Dew on party lead
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW);
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);
@ -439,7 +439,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead
scene.modifiers = [];
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type;
const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const modifier = revSeed.newModifier(scene.getParty()[0]);
await scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true);
@ -468,7 +468,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead
scene.modifiers = [];
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type;
const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW);
const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true);

View File

@ -18,7 +18,7 @@ import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyTemplate } fro
import { PartyMemberStrength } from "#enums/party-member-strength";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import IMysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
const namespace = "mysteryEncounter:mysteriousChallengers";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -96,7 +96,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(MysteriousChallengersEncounter);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(MysteriousChallengersEncounter);
const encounter = scene.currentBattle.mysteryEncounter;
scene.currentBattle.waveIndex = defaultWave;

View File

@ -123,7 +123,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierTypes: expect.any(Array),
modifierConfigs: expect.any(Array),
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: expect.any(Function)
}

View File

@ -0,0 +1,385 @@
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
import { Biome } from "#app/enums/biome";
import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
import { Species } from "#app/enums/species";
import GameManager from "#app/test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, PartyHealPhase, SelectModifierPhase, VictoryPhase } from "#app/phases";
import BattleScene from "#app/battle-scene";
import { Mode } from "#app/ui/ui";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { TrainerType } from "#enums/trainer-type";
import { Nature } from "#enums/nature";
import { Moves } from "#enums/moves";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter";
import { Status, StatusEffect } from "#app/data/status-effect";
import { MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
const namespace = "mysteryEncounter:theWinstrateChallenge";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
const defaultBiome = Biome.CAVE;
const defaultWave = 45;
describe("The Winstrate Challenge - Mystery Encounter", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
let scene: BattleScene;
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves(true);
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[Biome.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]],
]);
HUMAN_TRANSITABLE_BIOMES.forEach(biome => {
biomeMap.set(biome, [MysteryEncounterType.THE_WINSTRATE_CHALLENGE]);
});
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap);
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.clearAllMocks();
vi.resetAllMocks();
});
it("should have the correct properties", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
expect(TheWinstrateChallengeEncounter.encounterType).toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE);
expect(TheWinstrateChallengeEncounter.encounterTier).toBe(MysteryEncounterTier.ROGUE);
expect(TheWinstrateChallengeEncounter.dialogue).toBeDefined();
expect(TheWinstrateChallengeEncounter.dialogue.intro).toStrictEqual([
{ text: `${namespace}.intro` },
{
speaker: `${namespace}.speaker`,
text: `${namespace}.intro_dialogue`,
}
]);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
expect(TheWinstrateChallengeEncounter.options.length).toBe(2);
});
it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
game.override.startingBiome(Biome.VOLCANO);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(TheWinstrateChallengeEncounter);
const encounter = scene.currentBattle.mysteryEncounter;
scene.currentBattle.waveIndex = defaultWave;
const { onInit } = encounter;
expect(encounter.onInit).toBeDefined();
encounter.populateDialogueTokensFromRequirements(scene);
const onInitResult = onInit(scene);
expect(encounter.enemyPartyConfigs).toBeDefined();
expect(encounter.enemyPartyConfigs.length).toBe(5);
expect(encounter.enemyPartyConfigs).toEqual([
{
trainerType: TrainerType.VITO,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.HISUI_ELECTRODE),
isBoss: false,
abilityIndex: 0, // Soundproof
nature: Nature.MODEST,
moveSet: [Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.SWALOT),
isBoss: false,
abilityIndex: 2, // Gluttony
nature: Nature.QUIET,
moveSet: [Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.DODRIO),
isBoss: false,
abilityIndex: 2, // Tangled Feet
nature: Nature.JOLLY,
moveSet: [Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.ALAKAZAM),
isBoss: false,
formIndex: 1,
nature: Nature.BOLD,
moveSet: [Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.DARMANITAN),
isBoss: false,
abilityIndex: 0, // Sheer Force
nature: Nature.IMPISH,
moveSet: [Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE],
modifierConfigs: expect.any(Array)
}
]
},
{
trainerType: TrainerType.VICKY,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.MEDICHAM),
isBoss: false,
formIndex: 1,
nature: Nature.IMPISH,
moveSet: [Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH],
modifierConfigs: expect.any(Array)
}
]
},
{
trainerType: TrainerType.VIVI,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SEAKING),
isBoss: false,
abilityIndex: 3, // Lightning Rod
nature: Nature.ADAMANT,
moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.BRELOOM),
isBoss: false,
abilityIndex: 1, // Poison Heal
nature: Nature.JOLLY,
moveSet: [Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.CAMERUPT),
isBoss: false,
formIndex: 1,
nature: Nature.CALM,
moveSet: [Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT],
modifierConfigs: expect.any(Array)
}
]
},
{
trainerType: TrainerType.VICTORIA,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.ROSERADE),
isBoss: false,
abilityIndex: 0, // Natural Cure
nature: Nature.CALM,
moveSet: [Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.GARDEVOIR),
isBoss: false,
formIndex: 1,
nature: Nature.TIMID,
moveSet: [Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP],
modifierConfigs: expect.any(Array)
}
]
},
{
trainerType: TrainerType.VICTOR,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.SWELLOW),
isBoss: false,
abilityIndex: 0, // Guts
nature: Nature.ADAMANT,
moveSet: [Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK],
modifierConfigs: expect.any(Array)
},
{
species: getPokemonSpecies(Species.OBSTAGOON),
isBoss: false,
abilityIndex: 1, // Guts
nature: Nature.ADAMANT,
moveSet: [Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH],
modifierConfigs: expect.any(Array)
}
]
}
]);
expect(encounter.spriteConfigs).toBeDefined();
expect(encounter.spriteConfigs.length).toBe(5);
expect(onInitResult).toBe(true);
});
describe("Option 1 - Normal Battle", () => {
it("should have the correct properties", () => {
const option = TheWinstrateChallengeEncounter.options[0];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
selected: [
{
speaker: "trainerNames:victor",
text: `${namespace}.option.1.selected`,
},
],
});
});
it("should battle all 5 trainers for a Macho Brace reward", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 1, null, true);
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICTOR);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(4);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICTORIA);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(3);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VIVI);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(2);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICKY);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(1);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
await skipBattleToNextBattle(game);
expect(scene.currentBattle.trainer).toBeDefined();
expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VITO);
expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(0);
expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE);
// Should have Macho Brace in the rewards
await skipBattleToNextBattle(game, true);
await game.phaseInterceptor.to(SelectModifierPhase, false);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
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(1);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_MACHO_BRACE");
}, 15000);
});
describe("Option 2 - Refuse the Challenge", () => {
it("should have the correct properties", () => {
const option = TheWinstrateChallengeEncounter.options[1];
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
expect(option.dialogue).toBeDefined();
expect(option.dialogue).toStrictEqual({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
speaker: `${namespace}.speaker`,
text: `${namespace}.option.2.selected`,
},
],
});
});
it("Should fully heal the party", async () => {
const phaseSpy = vi.spyOn(scene, "unshiftPhase");
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
const partyHealPhases = phaseSpy.mock.calls.filter(p => p[0] instanceof PartyHealPhase).map(p => p[0]);
expect(partyHealPhases.length).toBe(1);
});
it("should have a Rarer Candy in the rewards", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty);
await runMysteryEncounterToEnd(game, 2);
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
await game.phaseInterceptor.run(SelectModifierPhase);
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(1);
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("RARER_CANDY");
});
});
});
/**
* For any MysteryEncounter that has a battle, can call this to skip battle and proceed to MysteryEncounterRewardsPhase
* @param game
* @param isFinalBattle
*/
async function skipBattleToNextBattle(game: GameManager, isFinalBattle: boolean = false) {
game.scene.clearPhaseQueue();
game.scene.clearPhaseQueueSplice();
const commandUiHandler = game.scene.ui.handlers[Mode.COMMAND];
commandUiHandler.clear();
game.scene.getEnemyParty().forEach(p => {
p.hp = 0;
p.status = new Status(StatusEffect.FAINT);
game.scene.field.remove(p);
});
game.phaseInterceptor["onHold"] = [];
game.scene.pushPhase(new VictoryPhase(game.scene, 0));
game.phaseInterceptor.superEndPhase();
if (isFinalBattle) {
await game.phaseInterceptor.to(MysteryEncounterRewardsPhase);
} else {
await game.phaseInterceptor.to(CommandPhase);
}
}

View File

@ -5,7 +5,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect";
import IMysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MessagePhase } from "#app/phases";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { Type } from "#app/data/type";
@ -250,7 +250,7 @@ describe("Mystery Encounter Utils", () => {
describe("getTextWithEncounterDialogueTokens", () => {
it("injects dialogue tokens and color styling", () => {
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
@ -258,7 +258,7 @@ describe("Mystery Encounter Utils", () => {
});
it("can perform nested dialogue token injection", () => {
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new");
@ -269,7 +269,7 @@ describe("Mystery Encounter Utils", () => {
describe("queueEncounterMessage", () => {
it("queues a message with encounter dialogue tokens", async () => {
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene, "queueMessage");
const phaseSpy = vi.spyOn(game.scene, "unshiftPhase");
@ -282,7 +282,7 @@ describe("Mystery Encounter Utils", () => {
describe("showEncounterText", () => {
it("showText with dialogue tokens", async () => {
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene.ui, "showText");
@ -293,7 +293,7 @@ describe("Mystery Encounter Utils", () => {
describe("showEncounterDialogue", () => {
it("showText with dialogue tokens", async () => {
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null);
scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene.ui, "showDialogue");

View File

@ -127,6 +127,7 @@ export default class GameWrapper {
manager: {
game: this.game,
},
destroy: () => null,
setVolume: () => null,
stopByKey: () => null,
on: (evt, callback) => callback(),