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

View File

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

View File

@ -2,7 +2,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattl
import { trainerConfigs, } from "#app/data/trainer-config"; import { trainerConfigs, } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/115 | GitHub Issue #115}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ATrainersTestEncounter: IMysteryEncounter = export const ATrainersTestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
.withEncounterTier(MysteryEncounterTier.ROGUE) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(100, 180)
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {
@ -139,13 +139,6 @@ export const ATrainersTestEncounter: IMysteryEncounter =
// Spawn standard trainer battle with memory mushroom reward // Spawn standard trainer battle with memory mushroom reward
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
let eggTier;
if (randSeedInt(64) >= 54) {
eggTier = EggTier.MASTER;
} else {
eggTier = EggTier.ULTRA;
}
await transitionMysteryEncounterIntroVisuals(scene); await transitionMysteryEncounterIntroVisuals(scene);
const eggOptions: IEggOptions = { const eggOptions: IEggOptions = {
@ -153,9 +146,9 @@ export const ATrainersTestEncounter: IMysteryEncounter =
pulled: false, pulled: false,
sourceType: EggSourceType.EVENT, sourceType: EggSourceType.EVENT,
eventEggTypeDescriptor: encounter.misc.trainerEggDescription, 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]); setEncounterRewards(scene, { fillRemaining: true }, [eggOptions]);
return initBattleWithEnemyConfig(scene, config); return initBattleWithEnemyConfig(scene, config);
@ -185,7 +178,7 @@ export const ATrainersTestEncounter: IMysteryEncounter =
) )
.withOutroDialogue([ .withOutroDialogue([
{ {
text: `${namespace}:outro`, text: `${namespace}.outro`,
}, },
]) ])
.build(); .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 Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { PersistentModifierRequirement } from "../mystery-encounter-requirements"; import { PersistentModifierRequirement } from "../mystery-encounter-requirements";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import { PokeballType } from "#app/data/pokeball"; import { PokeballType } from "#app/data/pokeball";
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:absoluteAvarice"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/58 | GitHub Issue #58}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const AbsoluteAvariceEncounter: IMysteryEncounter = export const AbsoluteAvariceEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)
@ -191,13 +192,13 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
encounter.misc = { berryItemsMap }; encounter.misc = { berryItemsMap };
// Generates copies of the stolen berries to put on the Greedent // Generates copies of the stolen berries to put on the Greedent
const bossModifierTypes: PokemonHeldItemModifierType[] = []; const bossModifierConfigs: HeldModifierConfig[] = [];
berryItems.forEach(berryMod => { berryItems.forEach(berryMod => {
// Can't define stack count on a ModifierType, have to just create separate instances for each stack // 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 // Overflow berries will be "lost" on the boss, but it's un-catchable anyway
for (let i = 0; i < berryMod.stackCount; i++) { for (let i = 0; i < berryMod.stackCount; i++) {
const modifierType = generateModifierTypeOption(scene, modifierTypes.BERRY, [berryMod.berryType]).type as PokemonHeldItemModifierType; const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
bossModifierTypes.push(modifierType); bossModifierConfigs.push({ modifierType });
} }
scene.removeModifier(berryMod); scene.removeModifier(berryMod);
@ -212,7 +213,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
isBoss: true, isBoss: true,
bossSegments: 3, bossSegments: 3,
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF], moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF],
modifierTypes: bossModifierTypes, modifierConfigs: bossModifierConfigs,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`); queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
@ -243,7 +244,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter; const encounter = scene.currentBattle.mysteryEncounter;
// Provides 1x Reviver Seed to each party member at end of battle // 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 givePartyPokemonReviverSeeds = () => {
const party = scene.getParty(); const party = scene.getParty();
party.forEach(p => { party.forEach(p => {
@ -296,7 +297,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter =
Phaser.Math.RND.shuffle(berryTypesAsArray); Phaser.Math.RND.shuffle(berryTypesAsArray);
const randBerryType = berryTypesAsArray.pop(); 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); 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements"; import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/72 | GitHub Issue #72}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const AnOfferYouCantRefuseEncounter: IMysteryEncounter = export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)

View File

@ -1,7 +1,7 @@
import { BattleStat } from "#app/data/battle-stat"; import { BattleStat } from "#app/data/battle-stat";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { import {
EnemyPartyConfig, generateModifierTypeOption, EnemyPartyConfig, generateModifierType, generateModifierTypeOption,
initBattleWithEnemyConfig, initBattleWithEnemyConfig,
leaveEncounterWithoutBattle, setEncounterExp, leaveEncounterWithoutBattle, setEncounterExp,
setEncounterRewards setEncounterRewards
@ -19,7 +19,7 @@ import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const BerriesAboundEncounter: IMysteryEncounter = export const BerriesAboundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
@ -315,7 +315,7 @@ export const BerriesAboundEncounter: IMysteryEncounter =
async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) { async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType; 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(); 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 { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ClowningAroundEncounter: IMysteryEncounter = export const ClowningAroundEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(80, 180) .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 { // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
species: getPokemonSpecies(Species.BLACEPHALON), species: getPokemonSpecies(Species.BLACEPHALON),
ability: ability, mysteryEncounterData: new MysteryEncounterPokemonData(null, ability, null, [randSeedInt(18), randSeedInt(18)]),
mysteryEncounterData: new MysteryEncounterPokemonData(null, null, null, [randSeedInt(18), randSeedInt(18)]),
isBoss: true, isBoss: true,
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN] 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]; const newItemType = pool[randIndex];
let newMod; let newMod;
if (tier === "Berries") { if (tier === "Berries") {
newMod = generateModifierTypeOption(scene, modifierTypes.BERRY, [newItemType[0]]).type as PokemonHeldItemModifierType; newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
} else { } else {
newMod = generateModifierTypeOption(scene, newItemType[0]).type as PokemonHeldItemModifierType; newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType;
} }
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod); applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
// Decrement max stacks and remove from pool if at max // 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/130 | GitHub Issue #130}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DancingLessonsEncounter: IMysteryEncounter = export const DancingLessonsEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)

View File

@ -6,7 +6,7 @@ import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils"; import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/61 | GitHub Issue #61}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DarkDealEncounter: IMysteryEncounter = export const DarkDealEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL)
.withEncounterTier(MysteryEncounterTier.ROGUE) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withIntroSpriteConfigs([ .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 Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements"; import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/57 | GitHub Issue #57}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DelibirdyEncounter: IMysteryEncounter = export const DelibirdyEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)
@ -113,7 +113,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead // 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); await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); 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 // If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon); const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) { if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:invalid_selection`); return getEncounterText(scene, `${namespace}.invalid_selection`);
} }
return null; return null;
@ -188,7 +188,7 @@ export const DelibirdyEncounter: IMysteryEncounter =
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead // 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); await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); 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)) { if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead // 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); await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); 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)) { if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead // 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); await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell);
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); 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 { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { import MysteryEncounter, {
MysteryEncounterBuilder, MysteryEncounterBuilder,
} from "../mystery-encounter"; } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/33 | GitHub Issue #33}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const DepartmentStoreSaleEncounter: IMysteryEncounter = export const DepartmentStoreSaleEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 100) .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 { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/17 | GitHub Issue #17}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const FieldTripEncounter: IMysteryEncounter = export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)

View File

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

View File

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

View File

@ -127,7 +127,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
* @param scene Battle scene * @param scene Battle scene
* @param guidePokemon pokemon choosen as a guide * @param guidePokemon pokemon choosen as a guide
*/ */
function handlePokemonGuidingYouPhase(scene: BattleScene) { async function handlePokemonGuidingYouPhase(scene: BattleScene) {
const laprasSpecies = getPokemonSpecies(Species.LAPRAS); const laprasSpecies = getPokemonSpecies(Species.LAPRAS);
const { mysteryEncounter } = scene.currentBattle; 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 { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils"; 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"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
/** the i18n namespace for the encounter */ /** 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/41 | GitHub Issue #41}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const MysteriousChallengersEncounter: IMysteryEncounter = export const MysteriousChallengersEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .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 { randSeedInt } from "#app/utils.js";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/32 | GitHub Issue #32}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const MysteriousChestEncounter: IMysteryEncounter = export const MysteriousChestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 2 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 2 to 180
@ -116,9 +116,8 @@ export const MysteriousChestEncounter: IMysteryEncounter =
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender()); scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
// Show which Pokemon was KOed, then leave encounter with no rewards // Show which Pokemon was KOed, then leave encounter with no rewards
// Does this synchronously so that game over doesn't happen over result message // 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); leaveEncounterWithoutBattle(scene);
});
} }
}) })
.build() .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 { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { MoveRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/82 | GitHub Issue #82}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const PartTimerEncounter: IMysteryEncounter = export const PartTimerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)
@ -115,7 +115,7 @@ export const PartTimerEncounter: IMysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected // Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) { if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}:invalid_selection`); return getEncounterText(scene, `${namespace}.invalid_selection`);
} }
return null; return null;
@ -194,7 +194,7 @@ export const PartTimerEncounter: IMysteryEncounter =
// Only Pokemon non-KOd pokemon can be selected // Only Pokemon non-KOd pokemon can be selected
const selectableFilter = (pokemon: Pokemon) => { const selectableFilter = (pokemon: Pokemon) => {
if (!pokemon.isAllowedInBattle()) { if (!pokemon.isAllowedInBattle()) {
return getEncounterText(scene, `${namespace}:invalid_selection`); return getEncounterText(scene, `${namespace}.invalid_selection`);
} }
return null; return null;

View File

@ -1,7 +1,7 @@
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import { ScanIvsPhase, SummonPhase } from "#app/phases"; import { ScanIvsPhase, SummonPhase } from "#app/phases";
@ -17,7 +17,6 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:safariZone"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/39 | GitHub Issue #39}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const SafariZoneEncounter: IMysteryEncounter = export const SafariZoneEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)
@ -66,7 +65,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Start safari encounter // Start safari encounter
const encounter = scene.currentBattle.mysteryEncounter; const encounter = scene.currentBattle.mysteryEncounter;
encounter.encounterMode = MysteryEncounterMode.CONTINUOUS_ENCOUNTER; encounter.continuousEncounter = true;
encounter.misc = { encounter.misc = {
safariPokemonRemaining: 3 safariPokemonRemaining: 3
}; };
@ -104,7 +103,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
/** /**
* SAFARI ZONE MINIGAME OPTIONS * 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 * https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone
* *
* Catch Rate calculation: * Catch Rate calculation:
@ -130,21 +129,23 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Throw a ball option // 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); const catchResult = await throwPokeball(scene, pokemon);
if (catchResult) { if (catchResult) {
// You caught pokemon // You caught pokemon
// Check how many safari pokemon left // Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) { if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene); await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true }); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true });
} else { } else {
// End safari mode // End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
} }
} else { } else {
// Pokemon failed to catch, end turn // Pokemon catch failed, end turn
await doEndTurn(scene, 0); await doEndTurn(scene, 0);
} }
return true; return true;
@ -217,14 +218,16 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Flee option // Flee option
const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon; const encounter = scene.currentBattle.mysteryEncounter;
const pokemon = encounter.misc.pokemon;
await doPlayerFlee(scene, pokemon); await doPlayerFlee(scene, pokemon);
// Check how many safari pokemon left // Check how many safari pokemon left
if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) { if (encounter.misc.safariPokemonRemaining > 0) {
await summonSafariPokemon(scene); await summonSafariPokemon(scene);
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true }); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true });
} else { } else {
// End safari mode // End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
} }
return true; return true;
@ -487,6 +490,7 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) {
initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true });
} else { } else {
// End safari mode // End safari mode
encounter.continuousEncounter = false;
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
} }
} else { } 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 { StatusEffect } from "#app/data/status-effect";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
@ -6,7 +6,7 @@ import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoneyRequirement } from "../mystery-encounter-requirements"; import { MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/34 | GitHub Issue #34}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ShadyVitaminDealerEncounter: IMysteryEncounter = export const ShadyVitaminDealerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)
@ -79,8 +79,8 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens // Calculate modifiers and dialogue tokens
const modifiers = [ const modifiers = [
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
]; ];
encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].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 // If pokemon meets primary pokemon reqs, it can be selected
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon); const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
if (!meetsReqs) { if (!meetsReqs) {
return getEncounterText(scene, `${namespace}:invalid_selection`); return getEncounterText(scene, `${namespace}.invalid_selection`);
} }
return null; return null;
@ -162,8 +162,8 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney);
// Calculate modifiers and dialogue tokens // Calculate modifiers and dialogue tokens
const modifiers = [ const modifiers = [
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER),
]; ];
encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost1", modifiers[0].name);
encounter.setDialogueToken("boost2", modifiers[1].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 { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements"; import { MoveRequirement } from "../mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/103 | GitHub Issue #103}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const SlumberingSnorlaxEncounter: IMysteryEncounter = export const SlumberingSnorlaxEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180

View File

@ -2,7 +2,7 @@ import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, up
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { MoneyRequirement } from "../mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/36 | GitHub Issue #36}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const ThePokemonSalesmanEncounter: IMysteryEncounter = export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180) .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 { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Nature } from "#app/data/nature"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/54 | GitHub Issue #54}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TheStrongStuffEncounter: IMysteryEncounter = export const TheStrongStuffEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
@ -74,12 +74,20 @@ export const TheStrongStuffEncounter: IMysteryEncounter =
mysteryEncounterData: new MysteryEncounterPokemonData(1.5), mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
nature: Nature.BOLD, nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER], moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierTypes: [ modifierConfigs: [
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType, {
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.APICOT]).type as PokemonHeldItemModifierType, modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) 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 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], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { 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 { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/43 | GitHub Issue #43}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TrainingSessionEncounter: IMysteryEncounter = export const TrainingSessionEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
@ -444,7 +444,7 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon,segment
formIndex: playerPokemon.formIndex, formIndex: playerPokemon.formIndex,
level: playerPokemon.level, level: playerPokemon.level,
dataSource: data, 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 { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene"; 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 { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { ModifierRewardPhase } from "#app/phases"; import { ModifierRewardPhase } from "#app/phases";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; 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 {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/74 | GitHub Issue #74}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome} * @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/ */
export const TrashToTreasureEncounter: IMysteryEncounter = export const TrashToTreasureEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180) .withSceneWaveRangeRequirement(10, 180)
@ -147,8 +147,8 @@ export const TrashToTreasureEncounter: IMysteryEncounter =
.build(); .build();
async function tryApplyDigRewardItems(scene: BattleScene) { async function tryApplyDigRewardItems(scene: BattleScene) {
const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType; const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
const leftovers = generateModifierTypeOption(scene, modifierTypes.LEFTOVERS).type as PokemonHeldItemModifierType; const leftovers = generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
const party = scene.getParty(); const party = scene.getParty();

View File

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

View File

@ -6,25 +6,16 @@ import BattleScene from "#app/battle-scene";
import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import { StatusEffect } from "../status-effect"; import { StatusEffect } from "../status-effect";
import MysteryEncounterDialogue, { import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter-dialogue";
OptionTextDisplay
} from "./mystery-encounter-dialogue";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option"; import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option";
import { import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements";
EncounterPokemonRequirement,
EncounterSceneRequirement,
HealthRatioRequirement,
PartySizeRequirement,
StatusEffectRequirement,
WaveRangeRequirement
} from "./mystery-encounter-requirements";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { EncounterAnim } from "#app/data/battle-anims"; import { EncounterAnim } from "#app/data/battle-anims";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
export interface StartOfBattleEffect { export interface EncounterStartOfBattleEffect {
sourcePokemon?: Pokemon; sourcePokemon?: Pokemon;
sourceBattlerIndex?: BattlerIndex; sourceBattlerIndex?: BattlerIndex;
targets: BattlerIndex[]; targets: BattlerIndex[];
@ -33,7 +24,7 @@ export interface StartOfBattleEffect {
followUp?: boolean; followUp?: boolean;
} }
export default interface IMysteryEncounter { export default interface MysteryEncounter {
/** /**
* Required params * Required params
*/ */
@ -44,13 +35,44 @@ export default interface IMysteryEncounter {
* Optional params * Optional params
*/ */
encounterTier?: MysteryEncounterTier; 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[]; encounterAnimations?: EncounterAnim[];
/**
* If true, hides "A Wild X Appeared" etc. messages
* Default true
*/
hideBattleIntroMessage?: boolean; hideBattleIntroMessage?: boolean;
/**
* If true, when an option is selected the field visuals will fade out automatically
* Default false
*/
autoHideIntroVisuals?: boolean; autoHideIntroVisuals?: boolean;
/**
* Intro visuals on the field will slide in from the right instead of the left
* Default false
*/
enterIntroVisualsFromRight?: boolean; enterIntroVisualsFromRight?: boolean;
/**
* If true, allows catching a wild pokemon during the encounter
* Default false
*/
catchAllowed?: boolean; 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; maxAllowedEncounters?: number;
/** /**
* Event callback functions * Event callback functions
*/ */
@ -62,6 +84,8 @@ export default interface IMysteryEncounter {
doEncounterExp?: (scene: BattleScene) => boolean; doEncounterExp?: (scene: BattleScene) => boolean;
/** Will provide the player a rewards shop for that wave */ /** Will provide the player a rewards shop for that wave */
doEncounterRewards?: (scene: BattleScene) => boolean; doEncounterRewards?: (scene: BattleScene) => boolean;
/** Will execute callback during VictoryPhase of a continuousEncounter */
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
/** /**
* Requirements * Requirements
@ -90,6 +114,7 @@ export default interface IMysteryEncounter {
/** /**
* Data used for setting up/initializing enemy party in battles * Data used for setting up/initializing enemy party in battles
* Can store multiple configs so that one can be chosen based on option selected * Can store multiple configs so that one can be chosen based on option selected
* Should usually be defined in `onInit()` or `onPreOptionPhase()`
*/ */
enemyPartyConfigs?: EnemyPartyConfig[]; 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 * 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 * Can be set higher or lower based on the type of battle or exp gained for an option/encounter
* Defaults to 1 * 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 * 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 * 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 * 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 * You should only need to interact via getter/update methods
*/ */
private seedOffset?: any; private seedOffset?: any;
constructor(encounter: IMysteryEncounter) { constructor(encounter: MysteryEncounter) {
if (!isNullOrUndefined(encounter)) { if (!isNullOrUndefined(encounter)) {
Object.assign(this, encounter); Object.assign(this, encounter);
} }
@ -170,6 +195,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false; this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false;
this.autoHideIntroVisuals = this.autoHideIntroVisuals ?? true; this.autoHideIntroVisuals = this.autoHideIntroVisuals ?? true;
this.enterIntroVisualsFromRight = this.enterIntroVisualsFromRight ?? false; this.enterIntroVisualsFromRight = this.enterIntroVisualsFromRight ?? false;
this.continuousEncounter = this.continuousEncounter ?? false;
// Reset any dirty flags or encounter data // Reset any dirty flags or encounter data
this.startOfBattleEffectsComplete = false; this.startOfBattleEffectsComplete = false;
@ -238,11 +264,11 @@ export default class IMysteryEncounter implements IMysteryEncounter {
} }
if (truePrimaryPool.length > 0) { 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)]; this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)];
return true; return true;
} else { } 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)) { if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) {
// is this working? // is this working?
this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)]; 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. * should rely on this value for seed offset instead of wave index.
* *
* This offset is incremented for each new {@link MysteryEncounterPhase} that occurs, * 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; encounterType?: MysteryEncounterType;
options?: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [null, null]; options?: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [null, null];
spriteConfigs?: MysteryEncounterSpriteConfig[]; spriteConfigs?: MysteryEncounterSpriteConfig[];
@ -418,7 +448,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
hideBattleIntroMessage?: boolean; hideBattleIntroMessage?: boolean;
hideIntroVisuals?: boolean; hideIntroVisuals?: boolean;
enterIntroVisualsFromRight?: boolean; enterIntroVisualsFromRight?: boolean;
enemyPartyConfigs?: EnemyPartyConfig[] = []; continuousEncounter?: boolean;
/** /**
* REQUIRED * REQUIRED
@ -429,7 +459,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterType * @param encounterType
* @returns this * @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 }); 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 * @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance
* @returns * @returns
*/ */
withOption(option: MysteryEncounterOption): this & Pick<IMysteryEncounter, "options"> { withOption(option: MysteryEncounterOption): this & Pick<MysteryEncounter, "options"> {
if (this.options[0] === null) { if (this.options[0] === null) {
return Object.assign(this, { options: [option, this.options[0]] }); return Object.assign(this, { options: [option, this.options[0]] });
} else if (this.options[1] === null) { } else if (this.options[1] === null) {
@ -464,7 +494,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param callback - {@linkcode OptionPhaseCallback} * @param callback - {@linkcode OptionPhaseCallback}
* @returns * @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()); 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} * @param callback - {@linkcode OptionPhaseCallback}
* @returns * @returns
*/ */
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> { withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<MysteryEncounter, "options"> {
return this.withOption(new MysteryEncounterOptionBuilder() return this.withOption(new MysteryEncounterOptionBuilder()
.withOptionMode(MysteryEncounterOptionMode.DEFAULT) .withOptionMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
@ -492,7 +522,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param spriteConfigs * @param spriteConfigs
* @returns * @returns
*/ */
withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<IMysteryEncounter, "spriteConfigs"> { withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick<MysteryEncounter, "spriteConfigs"> {
return Object.assign(this, { spriteConfigs: spriteConfigs }); return Object.assign(this, { spriteConfigs: spriteConfigs });
} }
@ -521,28 +551,38 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param encounterTier * @param encounterTier
* @returns * @returns
*/ */
withEncounterTier(encounterTier: MysteryEncounterTier): this & Required<Pick<IMysteryEncounter, "encounterTier">> { withEncounterTier(encounterTier: MysteryEncounterTier): this & Required<Pick<MysteryEncounter, "encounterTier">> {
return Object.assign(this, { encounterTier: encounterTier }); return Object.assign(this, { encounterTier: encounterTier });
} }
/** /**
* Defines any EncounterAnim animations that are intended to be used during the encounter * 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 * They just need to be specified here so that resources are loaded on encounter init
* @param encounterAnimations * @param encounterAnimations
* @returns * @returns
*/ */
withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<IMysteryEncounter, "encounterAnimations">> { withAnimations(...encounterAnimations: EncounterAnim[]): this & Required<Pick<MysteryEncounter, "encounterAnimations">> {
const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations]; const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations];
return Object.assign(this, { encounterAnimations: animations }); 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 * Sets the maximum number of times that an encounter can spawn in a given Classic run
* @param maxAllowedEncounters * @param maxAllowedEncounters
* @returns * @returns
*/ */
withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<IMysteryEncounter, "maxAllowedEncounters">> { withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required<Pick<MysteryEncounter, "maxAllowedEncounters">> {
return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters }); return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters });
} }
@ -553,7 +593,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement * @param requirement
* @returns * @returns
*/ */
withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<IMysteryEncounter, "requirements">> { withSceneRequirement(requirement: EncounterSceneRequirement): this & Required<Pick<MysteryEncounter, "requirements">> {
if (requirement instanceof EncounterPokemonRequirement) { if (requirement instanceof EncounterPokemonRequirement) {
Error("Incorrectly added pokemon requirement as scene requirement."); 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 * @param max optional max wave. If not given, defaults to min => exact wave
* @returns * @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])); 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 * @param excludeFainted - if true, only counts unfainted mons
* @returns * @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)); return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
} }
@ -590,7 +630,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* @param requirement {@linkcode EncounterPokemonRequirement} * @param requirement {@linkcode EncounterPokemonRequirement}
* @returns * @returns
*/ */
withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<IMysteryEncounter, "primaryPokemonRequirements">> { withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required<Pick<MysteryEncounter, "primaryPokemonRequirements">> {
if (requirement instanceof EncounterSceneRequirement) { if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement."); 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 * @param invertQuery if true will invert the query
* @returns * @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)); 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 * @param invertQuery if true will invert the query
* @returns * @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)); return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery));
} }
// TODO: Maybe add an optional parameter for excluding primary pokemon from the support cast? // 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 // 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 // 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) { if (requirement instanceof EncounterSceneRequirement) {
Error("Incorrectly added scene requirement as pokemon requirement."); 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 * @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter
* @returns * @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 }); 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 * @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter
* @returns * @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 }); 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 * @param onInit - synchronous callback function to perform as soon as the encounter is selected for the next phase
* @returns * @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 }); 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 * @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in
* @returns * @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 }); 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 * Can set whether catching is allowed or not on the encounter
* This flag can also be programmatically set inside option event functions or elsewhere * 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 * @param catchAllowed - if true, allows enemy pokemon to be caught during the encounter
* @returns * @returns
*/ */
withCatchAllowed(catchAllowed: boolean): this & Required<Pick<IMysteryEncounter, "catchAllowed">> { withCatchAllowed(catchAllowed: boolean): this & Required<Pick<MysteryEncounter, "catchAllowed">> {
return Object.assign(this, { catchAllowed: 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 * @param hideBattleIntroMessage - if true, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
* @returns * @returns
*/ */
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> { withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<MysteryEncounter, "hideBattleIntroMessage">> {
return Object.assign(this, { hideBattleIntroMessage: 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 * @param autoHideIntroVisuals - if false, will not hide the intro visuals that are displayed at the beginning of encounter
* @returns * @returns
*/ */
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> { withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<MysteryEncounter, "autoHideIntroVisuals">> {
return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals }); return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals });
} }
@ -726,7 +756,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Default false * Default false
* @returns * @returns
*/ */
withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<IMysteryEncounter, "enterIntroVisualsFromRight">> { withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required<Pick<MysteryEncounter, "enterIntroVisualsFromRight">> {
return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight }); return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight });
} }
@ -806,7 +836,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* *
* @returns * @returns
*/ */
build(this: IMysteryEncounter): IMysteryEncounter { build(this: MysteryEncounter): MysteryEncounter {
return new IMysteryEncounter(this); 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 { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter";
import { SlumberingSnorlaxEncounter } from "./encounters/slumbering-snorlax-encounter"; import { SlumberingSnorlaxEncounter } from "./encounters/slumbering-snorlax-encounter";
import { TrainingSessionEncounter } from "./encounters/training-session-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 { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-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 { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter";
import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter"; import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter";
import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-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 // 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; export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
@ -132,7 +133,7 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [
Biome.ISLAND Biome.ISLAND
]; ];
export const allMysteryEncounters: { [encounterType: number]: IMysteryEncounter } = {}; export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {};
const extremeBiomeEncounters: MysteryEncounterType[] = []; const extremeBiomeEncounters: MysteryEncounterType[] = [];
@ -147,6 +148,7 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.SHADY_VITAMIN_DEALER, MysteryEncounterType.SHADY_VITAMIN_DEALER,
MysteryEncounterType.THE_POKEMON_SALESMAN, MysteryEncounterType.THE_POKEMON_SALESMAN,
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
MysteryEncounterType.THE_WINSTRATE_CHALLENGE
]; ];
const civilizationBiomeEncounters: MysteryEncounterType[] = [ const civilizationBiomeEncounters: MysteryEncounterType[] = [
@ -270,6 +272,7 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.PART_TIMER] = PartTimerEncounter; allMysteryEncounters[MysteryEncounterType.PART_TIMER] = PartTimerEncounter;
allMysteryEncounters[MysteryEncounterType.DANCING_LESSONS] = DancingLessonsEncounter; allMysteryEncounters[MysteryEncounterType.DANCING_LESSONS] = DancingLessonsEncounter;
allMysteryEncounters[MysteryEncounterType.WEIRD_DREAM] = WeirdDreamEncounter; allMysteryEncounters[MysteryEncounterType.WEIRD_DREAM] = WeirdDreamEncounter;
allMysteryEncounters[MysteryEncounterType.THE_WINSTRATE_CHALLENGE] = TheWinstrateChallengeEncounter;
// Add extreme encounters to biome map // Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => { 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 MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; 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 { 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 { 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 { BattleEndPhase, EggLapsePhase, ExpPhase, GameOverPhase, MovePhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases";
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
import PokemonData from "#app/system/pokemon-data"; 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 PokemonSpecies from "#app/data/pokemon-species";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { Egg, IEggOptions } from "#app/data/egg"; import { Egg, IEggOptions } from "#app/data/egg";
import { Abilities } from "#enums/abilities";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; 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 * 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 bossSegmentModifier?: number; // Additive to the determined segment number
mysteryEncounterData?: MysteryEncounterPokemonData; mysteryEncounterData?: MysteryEncounterPokemonData;
formIndex?: number; formIndex?: number;
abilityIndex?: number;
level?: number; level?: number;
gender?: Gender; gender?: Gender;
passive?: boolean; passive?: boolean;
moveSet?: Moves[]; moveSet?: Moves[];
nature?: Nature; nature?: Nature;
ivs?: [integer, integer, integer, integer, integer, integer]; ivs?: [integer, integer, integer, integer, integer, integer];
ability?: Abilities;
shiny?: boolean; shiny?: boolean;
/** Can set just the status, or pass a timer on the status turns */ /** Can set just the status, or pass a timer on the status turns */
status?: StatusEffect | [StatusEffect, number]; status?: StatusEffect | [StatusEffect, number];
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
modifierTypes?: PokemonHeldItemModifierType[]; modifierConfigs?: HeldModifierConfig[];
tags?: BattlerTagType[]; tags?: BattlerTagType[];
dataSource?: PokemonData; dataSource?: PokemonData;
} }
@ -258,10 +258,13 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
} }
// Set summon data fields // Set summon data fields
if (!enemyPokemon.summonData) {
enemyPokemon.summonData = new PokemonSummonData();
}
// Set ability // Set ability
if (!isNullOrUndefined(config.ability)) { if (!isNullOrUndefined(config.abilityIndex)) {
enemyPokemon.summonData.ability = config.ability; enemyPokemon.abilityIndex = config.abilityIndex;
} }
// Set gender // Set gender
@ -293,6 +296,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemyPokemon.initBattleInfo(); enemyPokemon.initBattleInfo();
enemyPokemon.getBattleInfo().initInfo(enemyPokemon); enemyPokemon.getBattleInfo().initInfo(enemyPokemon);
enemyPokemon.generateName();
} }
loadEnemyAssets.push(enemyPokemon.loadAssets()); loadEnemyAssets.push(enemyPokemon.loadAssets());
@ -315,8 +319,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
}); });
if (!loaded) { if (!loaded) {
regenerateModifierPoolThresholds(scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); regenerateModifierPoolThresholds(scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
const customModifiers = partyConfig?.pokemonConfigs?.map(config => config?.modifierTypes); const customModifierTypes = partyConfig?.pokemonConfigs?.map(config => config?.modifierConfigs);
scene.generateEnemyModifiers(customModifiers); scene.generateEnemyModifiers(customModifierTypes);
} }
} }
@ -363,7 +367,7 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS
* @param modifier * @param modifier
* @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. * @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); const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier);
let result: ModifierType = modifierTypes[modifierId]?.(); let result: ModifierType = modifierTypes[modifierId]?.();
@ -373,6 +377,17 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
.withTierFromPool(); .withTierFromPool();
result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result; 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); 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 * Will skip any shops and rewards, and queue the next encounter phase as normal
* @param scene * @param scene
* @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available * @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) { export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) {
scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.NO_BATTLE; scene.currentBattle.mysteryEncounter.encounterMode = encounterMode;
scene.clearPhaseQueue(); scene.clearPhaseQueue();
scene.clearPhaseQueueSplice(); scene.clearPhaseQueueSplice();
handleMysteryEncounterVictory(scene, addHealPhase); handleMysteryEncounterVictory(scene, addHealPhase);
@ -644,18 +660,22 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
// If in repeated encounter variant, do nothing // If in repeated encounter variant, do nothing
// Variant must eventually be swapped in order to handle "true" end of the encounter // 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; 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 EggLapsePhase(scene));
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); 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)); scene.pushPhase(new BattleEndPhase(scene));
if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
scene.pushPhase(new TrainerVictoryPhase(scene)); scene.pushPhase(new TrainerVictoryPhase(scene));
} }
if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) { if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) {
scene.pushPhase(new EggLapsePhase(scene)); if (!encounter.doContinueEncounter) {
// Only lapse eggs once for multi-battle encounters
scene.pushPhase(new EggLapsePhase(scene));
}
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); 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 scene
* @param pokemon * @param previousPokemon
* @param transformedPokemon * @param transformPokemon
* @param screenPosition * @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 => { return new Promise<void>(resolve => {
const transformationContainer = scene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container; const transformationContainer = scene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container;
const transformationBaseBg = scene.add.image(0, 0, "default_bg"); 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 yOffset = screenPosition !== TransformationScreenPosition.CENTER ? -15 : 0;
const getPokemonSprite = () => { 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 }); ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
return ret; return ret;
}; };
@ -54,31 +54,31 @@ export function doPokemonTransformationSequence(scene: BattleScene, pokemon: Pla
pokemonEvoTintSprite.setTintFill(0xFFFFFF); pokemonEvoTintSprite.setTintFill(0xFFFFFF);
[ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => { [ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
sprite.play(pokemon.getSpriteKey(true)); sprite.play(previousPokemon.getSpriteKey(true));
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(pokemon.getTeraType()) }); 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("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", pokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
sprite.setPipelineData("shiny", pokemon.shiny); sprite.setPipelineData("shiny", previousPokemon.shiny);
sprite.setPipelineData("variant", pokemon.variant); sprite.setPipelineData("variant", previousPokemon.variant);
[ "spriteColors", "fusionSpriteColors" ].map(k => { [ "spriteColors", "fusionSpriteColors" ].map(k => {
if (pokemon.summonData?.speciesForm) { if (previousPokemon.summonData?.speciesForm) {
k += "Base"; k += "Base";
} }
sprite.pipelineData[k] = pokemon.getSprite().pipelineData[k]; sprite.pipelineData[k] = previousPokemon.getSprite().pipelineData[k];
}); });
}); });
[ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => { [ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
sprite.play(transformedPokemon.getSpriteKey(true)); sprite.play(transformPokemon.getSpriteKey(true));
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey());
sprite.setPipelineData("shiny", transformedPokemon.shiny); sprite.setPipelineData("shiny", transformPokemon.shiny);
sprite.setPipelineData("variant", transformedPokemon.variant); sprite.setPipelineData("variant", transformPokemon.variant);
[ "spriteColors", "fusionSpriteColors" ].map(k => { [ "spriteColors", "fusionSpriteColors" ].map(k => {
if (transformedPokemon.summonData?.speciesForm) { if (transformPokemon.summonData?.speciesForm) {
k += "Base"; 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, duration: 2000,
delay: 150, delay: 150,
easing: "Sine.easeIn", easing: "Sine.easeIn",
// onComplete: () => { onComplete: () => {
// transformedPokemon.destroy(); previousPokemon.destroy();
// } transformPokemon.setVisible(false);
transformPokemon.setAlpha(1);
}
}); });
}); });
} }

View File

@ -1958,4 +1958,19 @@ export const trainerConfigs: TrainerConfigs = {
} }
p.pokeball = PokeballType.MASTER_BALL; 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, DEFAULT,
TRAINER_BATTLE, TRAINER_BATTLE,
WILD_BATTLE, WILD_BATTLE,
/** Enables wild boss music during encounter */
BOSS_BATTLE, BOSS_BATTLE,
NO_BATTLE, NO_BATTLE
/** For spawning new encounter queries instead of continuing to next wave */
CONTINUOUS_ENCOUNTER
} }

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { GameObjects } from "phaser"; import { GameObjects } from "phaser";
import BattleScene from "../battle-scene"; 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 { Species } from "#enums/species";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-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 * 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 { export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container {
public encounter: IMysteryEncounter; public encounter: MysteryEncounter;
public spriteConfigs: MysteryEncounterSpriteConfig[]; public spriteConfigs: MysteryEncounterSpriteConfig[];
public enterFromRight: boolean; public enterFromRight: boolean;
constructor(scene: BattleScene, encounter: IMysteryEncounter) { constructor(scene: BattleScene, encounter: MysteryEncounter) {
super(scene, -72, 76); super(scene, -72, 76);
this.encounter = encounter; this.encounter = encounter;
this.enterFromRight = encounter.enterIntroVisualsFromRight ?? false; 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 { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp"; import { getLevelTotalExp } from "../data/exp";
import { Stat } from "../data/pokemon-stat"; 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 { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender"; import { Gender } from "../data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; 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.stats[s] = value;
} }
this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, this.stats);
} }
getNature(): Nature { 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." 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": { "brock": {
"encounter": { "encounter": {
1: "My expertise on Rock-type Pokémon will take you down! Come on!", 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_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_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}}." }, "MYSTERY_ENCOUNTER_OLD_GATEAU": { name: "Old Gateau", description: "Increases the holder's {{stats}} stats by {{statValue}}." },
}, },
SpeciesBoosterItem: { 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 { partTimerDialogue } from "#app/locales/en/mystery-encounters/part-timer-dialogue";
import { dancingLessonsDialogue } from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue"; import { dancingLessonsDialogue } from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue";
import { weirdDreamDialogue } from "#app/locales/en/mystery-encounters/weird-dream-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: * Injection patterns that can be used:
@ -71,5 +72,6 @@ export const mysteryEncounter = {
clowningAround: clowningAroundDialogue, clowningAround: clowningAroundDialogue,
partTimer: partTimerDialogue, partTimer: partTimerDialogue,
dancingLessons: dancingLessonsDialogue, dancingLessons: dancingLessonsDialogue,
weirdDream: weirdDreamDialogue weirdDream: weirdDreamDialogue,
theWinstrateChallenge: theWinstrateChallengeDialogue
} as const; } 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", "galactic_boss": "Team Galactic Boss",
"plasma_boss": "Team Plasma Boss", "plasma_boss": "Team Plasma Boss",
"flare_boss": "Team Flare 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. // 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; } as const;
@ -275,6 +276,11 @@ export const trainerNames: SimpleTranslationEntries = {
"marley": "Marley", "marley": "Marley",
"mira": "Mira", "mira": "Mira",
"riley": "Riley", "riley": "Riley",
"victor": "Victor",
"victoria": "Victoria",
"vivi": "Vivi",
"vicky": "Vicky",
"vito": "Vito",
// Double Names // Double Names
"blue_red_double": "Blue & Red", "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]); 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_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 { 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) * Modifier used for held items that apply {@linkcode Stat} boost(s)
* using a multiplier. * using a multiplier.

View File

@ -6,7 +6,7 @@ import { allMoves, applyFilteredMoveAttrs, applyMoveAttrs, AttackMove, BypassRed
import { Mode } from "./ui/ui"; import { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat"; 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 PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
import { CommonAnim, CommonBattleAnim, initEncounterAnims, initMoveAnim, loadEncounterAnimAssets, loadMoveAnimAssets, MoveAnim } from "./data/battle-anims"; 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(); this.scene.lastEnemyTrainer.destroy();
} }
if (lastEncounterVisuals) { if (lastEncounterVisuals) {
this.scene.field.remove(lastEncounterVisuals); this.scene.field.remove(lastEncounterVisuals, true);
lastEncounterVisuals.destroy();
this.scene.lastMysteryEncounter.introVisuals = null; this.scene.lastMysteryEncounter.introVisuals = null;
} }
@ -1583,7 +1582,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
onComplete: () => this.scene.trainer.setVisible(false) onComplete: () => this.scene.trainer.setVisible(false)
}); });
this.scene.time.delayedCall(750, () => this.summon()); 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 trainerName = this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER);
const pokemonName = getPokemonNameWithAffix(this.getPokemon()); const pokemonName = getPokemonNameWithAffix(this.getPokemon());
const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); 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.showText(null, 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true); }, 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.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => {
@ -4138,6 +4137,12 @@ export class VictoryPhase extends PokemonPhase {
const participated = participantIds.has(pId); const participated = participantIds.has(pId);
if (participated) { if (participated) {
partyMember.addFriendship(2); 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)) { if (!expPartyMembers.includes(partyMember)) {
continue; continue;

View File

@ -381,6 +381,10 @@ export class MysteryEncounterBattlePhase extends Phase {
/** /**
* Will handle (in order): * 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 doEncounterExp
* - Any encounter reward logic that is set within MysteryEncounter doEncounterRewards * - 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 * - Otherwise, can add a no-reward-item shop with only Potions, etc. if addHealPhase is true
@ -396,23 +400,30 @@ export class MysteryEncounterRewardsPhase extends Phase {
start() { start() {
super.start(); super.start();
const encounter = this.scene.currentBattle.mysteryEncounter;
this.scene.executeWithSeedOffset(() => { if (encounter.doContinueEncounter) {
if (this.scene.currentBattle.mysteryEncounter.doEncounterExp) { encounter.doContinueEncounter(this.scene).then(() => {
this.scene.currentBattle.mysteryEncounter.doEncounterExp(this.scene); this.end();
} });
} else {
this.scene.executeWithSeedOffset(() => {
if (this.scene.currentBattle.mysteryEncounter.doEncounterExp) {
this.scene.currentBattle.mysteryEncounter.doEncounterExp(this.scene);
}
if (this.scene.currentBattle.mysteryEncounter.doEncounterRewards) { if (this.scene.currentBattle.mysteryEncounter.doEncounterRewards) {
this.scene.currentBattle.mysteryEncounter.doEncounterRewards(this.scene); this.scene.currentBattle.mysteryEncounter.doEncounterRewards(this.scene);
} else if (this.addHealPhase) { } else if (this.addHealPhase) {
this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase); this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase);
this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 })); this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 }));
} }
// Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave) // Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave)
}, this.scene.currentBattle.waveIndex * 1000); }, this.scene.currentBattle.waveIndex * 1000);
this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene)); this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene));
this.end(); this.end();
}
} }
} }

View File

@ -43,7 +43,7 @@ import { Species } from "#enums/species";
import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; import { applyChallenges, ChallengeType } from "#app/data/challenge.js";
import { Abilities } from "#app/enums/abilities.js"; import { Abilities } from "#app/enums/abilities.js";
import { MysteryEncounterData } from "../data/mystery-encounters/mystery-encounter-data"; 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[] = [ export const defaultStarterSpecies: Species[] = [
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
@ -126,7 +126,7 @@ export interface SessionSaveData {
gameVersion: string; gameVersion: string;
timestamp: integer; timestamp: integer;
challenges: ChallengeData[]; challenges: ChallengeData[];
mysteryEncounter: IMysteryEncounter; mysteryEncounter: MysteryEncounter;
mysteryEncounterData: MysteryEncounterData; mysteryEncounterData: MysteryEncounterData;
} }
@ -1167,7 +1167,7 @@ export class GameData {
} }
if (k === "mysteryEncounter") { if (k === "mysteryEncounter") {
return new IMysteryEncounter(v); return new MysteryEncounter(v);
} }
if (k === "mysteryEncounterData") { 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 { getPokemonSpecies } from "#app/data/pokemon-species";
import * as BattleAnims from "#app/data/battle-anims"; import * as BattleAnims from "#app/data/battle-anims";
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; 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 { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
import { CommandPhase, MovePhase, NewBattlePhase, SelectModifierPhase } from "#app/phases"; import { CommandPhase, MovePhase, NewBattlePhase, SelectModifierPhase } from "#app/phases";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -123,7 +123,6 @@ describe("Clowning Around - Mystery Encounter", () => {
}); });
expect(config.pokemonConfigs[1]).toEqual({ expect(config.pokemonConfigs[1]).toEqual({
species: getPokemonSpecies(Species.BLACEPHALON), species: getPokemonSpecies(Species.BLACEPHALON),
ability: expect.any(Number),
mysteryEncounterData: expect.anything(), mysteryEncounterData: expect.anything(),
isBoss: true, isBoss: true,
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN] moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
@ -145,8 +144,8 @@ describe("Clowning Around - Mystery Encounter", () => {
Abilities.MAGICIAN, Abilities.MAGICIAN,
Abilities.SHEER_FORCE, Abilities.SHEER_FORCE,
Abilities.PRANKSTER Abilities.PRANKSTER
]).toContain(config.pokemonConfigs[1].ability); ]).toContain(config.pokemonConfigs[1].mysteryEncounterData.ability);
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].ability); expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].mysteryEncounterData.ability);
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled()); await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled()); await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
expect(onInitResult).toBe(true); expect(onInitResult).toBe(true);
@ -258,26 +257,26 @@ describe("Clowning Around - Mystery Encounter", () => {
// 2 Sitrus Berries on lead // 2 Sitrus Berries on lead
scene.modifiers = []; 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); await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
// 2 Ganlon Berries on lead // 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); await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
// 5 Golden Punch on lead (ultra) // 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); await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
// 5 Lucky Egg on lead (ultra) // 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); await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
// 5 Soul Dew on lead (rogue) // 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); await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
// 2 Golden Egg on lead (rogue) // 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); await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
// 5 Soul Dew on second party pokemon (these should not change) // 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 addItemToPokemon(scene, scene.getParty()[1], 5, itemType);
await runMysteryEncounterToEnd(game, 2); 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 { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; 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 { modifierTypes } from "#app/modifier/modifier-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
@ -137,7 +137,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// 5 Healing Charms // 5 Healing Charms
scene.modifiers = []; 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; abilityCharm.stackCount = 4;
await scene.addModifier(abilityCharm, true, false, false, true); await scene.addModifier(abilityCharm, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
@ -206,7 +206,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 2 Sitrus berries on party lead // Set 2 Sitrus berries on party lead
scene.modifiers = []; 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; const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier;
sitrusMod.stackCount = 2; sitrusMod.stackCount = 2;
await scene.addModifier(sitrusMod, true, false, false, true); await scene.addModifier(sitrusMod, true, false, false, true);
@ -227,7 +227,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead // Set 1 Reviver Seed on party lead
scene.modifiers = []; 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; const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
@ -248,10 +248,10 @@ describe("Delibird-y - Mystery Encounter", () => {
// 99 Candy Jars // 99 Candy Jars
scene.modifiers = []; 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; candyJar.stackCount = 99;
await scene.addModifier(candyJar, true, false, false, true); 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 // Sitrus berries on party
const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier; const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier;
@ -277,12 +277,12 @@ describe("Delibird-y - Mystery Encounter", () => {
// 5 Healing Charms // 5 Healing Charms
scene.modifiers = []; 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; healingCharm.stackCount = 5;
await scene.addModifier(healingCharm, true, false, false, true); await scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Reviver Seed on party lead // 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; const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
@ -306,7 +306,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead // Set 1 Soul Dew on party lead
scene.modifiers = []; scene.modifiers = [];
const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type; const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW);
const modifier = soulDew.newModifier(scene.getParty()[0]); const modifier = soulDew.newModifier(scene.getParty()[0]);
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
@ -334,7 +334,7 @@ describe("Delibird-y - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
// Set 1 Reviver Seed on party lead // 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; const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
@ -368,7 +368,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 2 Soul Dew on party lead // Set 2 Soul Dew on party lead
scene.modifiers = []; 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; const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 2; modifier.stackCount = 2;
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
@ -389,7 +389,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead // Set 1 Soul Dew on party lead
scene.modifiers = []; 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; const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
@ -410,12 +410,12 @@ describe("Delibird-y - Mystery Encounter", () => {
// 5 Healing Charms // 5 Healing Charms
scene.modifiers = []; 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; healingCharm.stackCount = 3;
await scene.addModifier(healingCharm, true, false, false, true); await scene.addModifier(healingCharm, true, false, false, true);
// Set 1 Soul Dew on party lead // 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; const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
@ -439,7 +439,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Reviver Seed on party lead // Set 1 Reviver Seed on party lead
scene.modifiers = []; scene.modifiers = [];
const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type; const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED);
const modifier = revSeed.newModifier(scene.getParty()[0]); const modifier = revSeed.newModifier(scene.getParty()[0]);
await scene.addModifier(modifier, true, false, false, true); await scene.addModifier(modifier, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
@ -468,7 +468,7 @@ describe("Delibird-y - Mystery Encounter", () => {
// Set 1 Soul Dew on party lead // Set 1 Soul Dew on party lead
scene.modifiers = []; 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; const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier;
modifier.stackCount = 1; modifier.stackCount = 1;
await scene.addModifier(modifier, true, false, false, true); 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 { PartyMemberStrength } from "#enums/party-member-strength";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; 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 namespace = "mysteryEncounter:mysteriousChallengers";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -96,7 +96,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => {
it("should initialize fully", async () => { it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty); initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(MysteriousChallengersEncounter); scene.currentBattle.mysteryEncounter = new MysteryEncounter(MysteriousChallengersEncounter);
const encounter = scene.currentBattle.mysteryEncounter; const encounter = scene.currentBattle.mysteryEncounter;
scene.currentBattle.waveIndex = defaultWave; scene.currentBattle.waveIndex = defaultWave;

View File

@ -123,7 +123,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
mysteryEncounterData: new MysteryEncounterPokemonData(1.5), mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
nature: Nature.BOLD, nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER], 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], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: expect.any(Function) 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 { Species } from "#enums/species";
import BattleScene from "#app/battle-scene"; import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect"; 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 { MessagePhase } from "#app/phases";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { Type } from "#app/data/type"; import { Type } from "#app/data/type";
@ -250,7 +250,7 @@ describe("Mystery Encounter Utils", () => {
describe("getTextWithEncounterDialogueTokens", () => { describe("getTextWithEncounterDialogueTokens", () => {
it("injects dialogue tokens and color styling", () => { 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"); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue"); const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue");
@ -258,7 +258,7 @@ describe("Mystery Encounter Utils", () => {
}); });
it("can perform nested dialogue token injection", () => { 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("test", "value");
scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new"); scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new");
@ -269,7 +269,7 @@ describe("Mystery Encounter Utils", () => {
describe("queueEncounterMessage", () => { describe("queueEncounterMessage", () => {
it("queues a message with encounter dialogue tokens", async () => { 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"); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene, "queueMessage"); const spy = vi.spyOn(game.scene, "queueMessage");
const phaseSpy = vi.spyOn(game.scene, "unshiftPhase"); const phaseSpy = vi.spyOn(game.scene, "unshiftPhase");
@ -282,7 +282,7 @@ describe("Mystery Encounter Utils", () => {
describe("showEncounterText", () => { describe("showEncounterText", () => {
it("showText with dialogue tokens", async () => { it("showText with dialogue tokens", async () => {
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null); scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene.ui, "showText"); const spy = vi.spyOn(game.scene.ui, "showText");
@ -293,7 +293,7 @@ describe("Mystery Encounter Utils", () => {
describe("showEncounterDialogue", () => { describe("showEncounterDialogue", () => {
it("showText with dialogue tokens", async () => { it("showText with dialogue tokens", async () => {
scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null); scene.currentBattle.mysteryEncounter = new MysteryEncounter(null);
scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value");
const spy = vi.spyOn(game.scene.ui, "showDialogue"); const spy = vi.spyOn(game.scene.ui, "showDialogue");

View File

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