Adds Uncommon Breed ME and misc. ME bug fixes
This commit is contained in:
parent
e77c6adb4f
commit
b6e90931ac
|
@ -1107,12 +1107,13 @@ export abstract class BattleAnim {
|
|||
let r = anim!.frames.length;
|
||||
let f = 0;
|
||||
|
||||
const existingFieldSprites = [...scene.field.getAll()];
|
||||
let existingFieldSprites = scene.field.getAll().slice(0);
|
||||
|
||||
scene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(3) * frameTimeMult,
|
||||
repeat: anim!.frames.length,
|
||||
onRepeat: () => {
|
||||
existingFieldSprites = scene.field.getAll().slice(0);
|
||||
const spriteFrames = anim!.frames[f];
|
||||
const frameData = this.getGraphicFrameDataWithoutTarget(anim!.frames[f], targetInitialX, targetInitialY);
|
||||
const u = 0;
|
||||
|
@ -1139,7 +1140,8 @@ export abstract class BattleAnim {
|
|||
const setSpritePriority = (priority: integer) => {
|
||||
if (existingFieldSprites.length > priority) {
|
||||
// Move to specified priority index
|
||||
scene.field.moveTo(moveSprite, scene.field.getIndex(existingFieldSprites[priority]));
|
||||
const index = scene.field.getIndex(existingFieldSprites[priority]);
|
||||
scene.field.moveTo(moveSprite, index);
|
||||
} else {
|
||||
// Move to top of scene
|
||||
scene.field.moveTo(moveSprite, scene.field.getAll().length - 1);
|
||||
|
|
|
@ -175,7 +175,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
||||
scene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
|
||||
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
|
@ -351,7 +351,7 @@ function doGreedentSpriteSteal(scene: BattleScene) {
|
|||
|
||||
const greedentSprites = scene.currentBattle.mysteryEncounter!.introVisuals?.getSpriteAtIndex(1);
|
||||
|
||||
scene.playSound("battle-anims/Follow Me");
|
||||
scene.playSound("battle_anims/Follow Me");
|
||||
scene.tweens.chain({
|
||||
targets: greedentSprites,
|
||||
tweens: [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
|
@ -88,7 +88,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon());
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getParty()[0]);
|
||||
danceAnim.play(scene);
|
||||
|
||||
return true;
|
||||
|
@ -105,7 +105,8 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const species = getPokemonSpecies(Species.ORICORIO);
|
||||
const enemyPokemon = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false);
|
||||
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
|
||||
const enemyPokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, false);
|
||||
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
|
||||
if (enemyPokemon.moveset.length < 4) {
|
||||
enemyPokemon.moveset.push(new PokemonMove(Moves.REVELATION_DANCE));
|
||||
|
@ -130,10 +131,11 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
|||
}
|
||||
|
||||
const oricorioData = new PokemonData(enemyPokemon);
|
||||
const oricorio = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false, oricorioData);
|
||||
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
scene.currentBattle.enemyParty[0] = enemyPokemon;
|
||||
scene.field.add(enemyPokemon);
|
||||
scene.currentBattle.enemyParty[0] = oricorio;
|
||||
scene.field.add(oricorio);
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { EnemyPokemon } from "#app/field/pokemon";
|
||||
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import {
|
||||
getPartyLuckValue,
|
||||
|
@ -24,6 +24,10 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
|||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:fightOrFlight";
|
||||
|
@ -58,7 +62,13 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
|||
level: level,
|
||||
species: bossSpecies,
|
||||
dataSource: new PokemonData(bossPokemon),
|
||||
isBoss: true
|
||||
isBoss: true,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
||||
// Randomly boost 1 stat 2 stages
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [randSeedInt(8)], 2));
|
||||
}
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
@ -122,6 +132,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
|||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
// Pokemon will randomly boost 1 stat by 2 stages
|
||||
const item = scene.currentBattle.mysteryEncounter!.misc as ModifierTypeOption;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
|
||||
await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
||||
|
|
|
@ -74,9 +74,9 @@ export const SafariZoneEncounter: MysteryEncounter =
|
|||
};
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Load bait/mud assets
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims");
|
||||
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims");
|
||||
scene.loadSe("PRSFX- Taunt2", "battle_anims");
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
||||
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims", "PRSFX- Sludge Bomb2.wav");
|
||||
scene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav");
|
||||
scene.loadAtlas("bait", "mystery-encounters");
|
||||
scene.loadAtlas("mud", "mystery-encounters");
|
||||
await summonSafariPokemon(scene);
|
||||
|
@ -353,12 +353,12 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
|
|||
y: originalY - 5,
|
||||
loop: 6,
|
||||
onStart: () => {
|
||||
scene.playSound("battle-anims/PRSFX- Bug Bite");
|
||||
scene.playSound("battle_anims/PRSFX- Bug Bite");
|
||||
bait.setFrame("0002.png");
|
||||
},
|
||||
onLoop: () => {
|
||||
if (index % 2 === 0) {
|
||||
scene.playSound("battle-anims/PRSFX- Bug Bite");
|
||||
scene.playSound("battle_anims/PRSFX- Bug Bite");
|
||||
}
|
||||
if (index === 4) {
|
||||
bait.setFrame("0003.png");
|
||||
|
@ -409,7 +409,7 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<bool
|
|||
duration: 500,
|
||||
onComplete: () => {
|
||||
// Mud frame 2
|
||||
scene.playSound("battle-anims/PRSFX- Sludge Bomb2");
|
||||
scene.playSound("battle_anims/PRSFX- Sludge Bomb2");
|
||||
mud.setFrame("0002.png");
|
||||
// Mud splat
|
||||
scene.time.delayedCall(200, () => {
|
||||
|
@ -435,10 +435,10 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<bool
|
|||
y: originalY - 20,
|
||||
loop: 1,
|
||||
onStart: () => {
|
||||
scene.playSound("battle-anims/PRSFX- Taunt2");
|
||||
scene.playSound("battle_anims/PRSFX- Taunt2");
|
||||
},
|
||||
onLoop: () => {
|
||||
scene.playSound("battle-anims/PRSFX- Taunt2");
|
||||
scene.playSound("battle_anims/PRSFX- Taunt2");
|
||||
},
|
||||
onComplete: () => {
|
||||
resolve(true);
|
||||
|
|
|
@ -209,12 +209,12 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
|||
}
|
||||
|
||||
async function doGarbageDig(scene: BattleScene) {
|
||||
scene.playSound("battle-anims/PRSFX- Dig2");
|
||||
scene.playSound("battle_anims/PRSFX- Dig2");
|
||||
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
|
||||
scene.playSound("battle-anims/PRSFX- Dig2");
|
||||
scene.playSound("battle-anims/PRSFX- Venom Drench", { volume: 2 });
|
||||
scene.playSound("battle_anims/PRSFX- Dig2");
|
||||
scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
|
||||
});
|
||||
scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => {
|
||||
scene.playSound("battle-anims/PRSFX- Dig2");
|
||||
scene.playSound("battle_anims/PRSFX- Dig2");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { getPartyLuckValue } 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 { MoveRequirement, PersistentModifierRequirement } from "../mystery-encounter-requirements";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { SelfStatusMove } from "#app/data/move";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { StatChangePhase } from "#app/phases/stat-change-phase";
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:uncommonBreed";
|
||||
|
||||
/**
|
||||
* Uncommon Breed encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3811 | GitHub Issue #3811}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const UncommonBreedEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.UNCOMMON_BREED)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withCatchAllowed(true)
|
||||
.withHideWildIntroMessage(true)
|
||||
.withIntroSpriteConfigs([]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon
|
||||
// Level equal to 2 below highest party member
|
||||
const level = getHighestLevelPlayerPokemon(scene).level - 2;
|
||||
const species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
|
||||
const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true);
|
||||
const speciesRootForm = pokemon.species.getRootSpeciesId();
|
||||
encounter.misc = {
|
||||
pokemon
|
||||
};
|
||||
|
||||
// Pokemon will always have one of its egg moves in its moveset
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove: Moves = eggMoves[eggMoveIndex];
|
||||
encounter.misc.eggMove = randomEggMove;
|
||||
if (pokemon.moveset.length < 4) {
|
||||
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
pokemon.moveset[0] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
}
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
level: level,
|
||||
species: species,
|
||||
dataSource: new PokemonData(pokemon),
|
||||
isBoss: false,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
|
||||
}
|
||||
}],
|
||||
};
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(pokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: spriteKey,
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
},
|
||||
];
|
||||
|
||||
encounter.setDialogueToken("enemyPokemon", pokemon.getNameToRender());
|
||||
scene.loadSe("PRSFX- Spotlight2", "battle_anims", "PRSFX- Spotlight2.wav");
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
// Animate the pokemon
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const pokemonSprite = encounter.introVisuals!.getSprites();
|
||||
|
||||
scene.tweens.add({ // Bounce at the end
|
||||
targets: pokemonSprite,
|
||||
duration: 300,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: "-=20",
|
||||
loop: 1,
|
||||
});
|
||||
|
||||
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
const eggMove = encounter.misc.eggMove;
|
||||
if (!isNullOrUndefined(eggMove)) {
|
||||
// Check what type of move the egg move is to determine target
|
||||
const pokemonMove = new PokemonMove(eggMove);
|
||||
const move = pokemonMove.getMove();
|
||||
const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
|
||||
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [target],
|
||||
move: pokemonMove,
|
||||
ignorePp: true
|
||||
});
|
||||
}
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Give it some food
|
||||
|
||||
// Remove 4 random berries from player's party
|
||||
// Get all player berry items, remove from party, and store reference
|
||||
let berryItems: BerryModifier[] = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||
const randBerry = berryItems[randSeedInt(berryItems.length)];
|
||||
randBerry.stackCount--;
|
||||
if (randBerry.stackCount === 0) {
|
||||
scene.removeModifier(randBerry);
|
||||
}
|
||||
scene.updateModifiers(true, true);
|
||||
}
|
||||
|
||||
// Pokemon joins the team, with 2 egg moves
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
|
||||
// Give 1 additional egg move
|
||||
const previousEggMove = encounter.misc.eggMove;
|
||||
const speciesRootForm = pokemon.species.getRootSpeciesId();
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
|
||||
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
|
||||
while (randomEggMove === previousEggMove) {
|
||||
randomEggMove = eggMoves[randSeedInt(4)];
|
||||
}
|
||||
if (pokemon.moveset.length < 4) {
|
||||
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
pokemon.moveset[1] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
}
|
||||
|
||||
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Attract the pokemon with a move
|
||||
// Pokemon joins the team, with 2 egg moves and IVs rolled an additional time
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
|
||||
// Give 1 additional egg move
|
||||
const previousEggMove = encounter.misc.eggMove;
|
||||
const speciesRootForm = pokemon.species.getRootSpeciesId();
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
|
||||
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
|
||||
while (randomEggMove === previousEggMove) {
|
||||
randomEggMove = eggMoves[randSeedInt(4)];
|
||||
}
|
||||
if (pokemon.moveset.length < 4) {
|
||||
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
pokemon.moveset[1] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
}
|
||||
|
||||
// Roll IVs a second time
|
||||
pokemon.ivs = pokemon.ivs.map(iv => {
|
||||
const newValue = randSeedInt(31);
|
||||
return newValue > iv ? newValue : iv;
|
||||
});
|
||||
|
||||
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
|
||||
if (encounter.selectedOption?.primaryPokemon?.id) {
|
||||
setEncounterExp(scene, encounter.selectedOption.primaryPokemon.id, pokemon.getExpValue(), false);
|
||||
}
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.build();
|
|
@ -29,6 +29,7 @@ import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/enc
|
|||
import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter";
|
||||
import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounters/bug-type-superfan-encounter";
|
||||
import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter";
|
||||
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
|
||||
|
||||
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256
|
||||
export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;
|
||||
|
@ -175,7 +176,8 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
|||
MysteryEncounterType.CLOWNING_AROUND,
|
||||
MysteryEncounterType.WEIRD_DREAM,
|
||||
MysteryEncounterType.TELEPORTING_HIJINKS,
|
||||
MysteryEncounterType.BUG_TYPE_SUPERFAN
|
||||
MysteryEncounterType.BUG_TYPE_SUPERFAN,
|
||||
MysteryEncounterType.UNCOMMON_BREED
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -282,6 +284,7 @@ export function initMysteryEncounters() {
|
|||
allMysteryEncounters[MysteryEncounterType.TELEPORTING_HIJINKS] = TeleportingHijinksEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.BUG_TYPE_SUPERFAN] = BugTypeSuperfanEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.FUN_AND_GAMES] = FunAndGamesEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
|
|
@ -886,7 +886,7 @@ export function handleMysteryEncounterBattleStartEffects(scene: BattleScene) {
|
|||
|
||||
/**
|
||||
* Can queue extra phases or logic during {@link TurnInitPhase}
|
||||
* Mostly useful for allowing MysteryEncounter enemies to "cheat" and use moves before the first turn
|
||||
* Should mostly just be used for injecting custom phases into the battle system on turn start
|
||||
* @param scene
|
||||
* @return boolean - if true, will skip the remainder of the {@link TurnInitPhase}
|
||||
*/
|
||||
|
|
|
@ -26,5 +26,6 @@ export enum MysteryEncounterType {
|
|||
THE_WINSTRATE_CHALLENGE,
|
||||
TELEPORTING_HIJINKS,
|
||||
BUG_TYPE_SUPERFAN,
|
||||
FUN_AND_GAMES
|
||||
FUN_AND_GAMES,
|
||||
UNCOMMON_BREED
|
||||
}
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
import lostAtSeaDialogue from "./mystery-encounters/lost-at-sea-dialogue.json";
|
||||
import mysteriousChestDialogue from "#app/locales/en/mystery-encounters/mysterious-chest-dialogue.json";
|
||||
import mysteriousChallengersDialogue from "#app/locales/en/mystery-encounters/mysterious-challengers-dialogue.json";
|
||||
import darkDealDialogue from "#app/locales/en/mystery-encounters/dark-deal-dialogue.json";
|
||||
import departmentStoreSaleDialogue from "#app/locales/en/mystery-encounters/department-store-sale-dialogue.json";
|
||||
import fieldTripDialogue from "#app/locales/en/mystery-encounters/field-trip-dialogue.json";
|
||||
import fieryFalloutDialogue from "#app/locales/en/mystery-encounters/fiery-fallout-dialogue.json";
|
||||
import fightOrFlightDialogue from "#app/locales/en/mystery-encounters/fight-or-flight-dialogue.json";
|
||||
import safariZoneDialogue from "#app/locales/en/mystery-encounters/safari-zone-dialogue.json";
|
||||
import shadyVitaminDealerDialogue from "#app/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.json";
|
||||
import slumberingSnorlaxDialogue from "#app/locales/en/mystery-encounters/slumbering-snorlax-dialogue.json";
|
||||
import trainingSessionDialogue from "#app/locales/en/mystery-encounters/training-session-dialogue.json";
|
||||
import theStrongStuffDialogue from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue.json";
|
||||
import thePokemonSalesmanDialogue from "#app/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json";
|
||||
import anOfferYouCantRefuseDialogue from "#app/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json";
|
||||
import delibirdyDialogue from "#app/locales/en/mystery-encounters/delibirdy-dialogue.json";
|
||||
import absoluteAvariceDialogue from "#app/locales/en/mystery-encounters/absolute-avarice-dialogue.json";
|
||||
import aTrainersTestDialogue from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue.json";
|
||||
import trashToTreasureDialogue from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue.json";
|
||||
import berriesAboundDialogue from "#app/locales/en/mystery-encounters/berries-abound-dialogue.json";
|
||||
import clowningAroundDialogue from "#app/locales/en/mystery-encounters/clowning-around-dialogue.json";
|
||||
import partTimerDialogue from "#app/locales/en/mystery-encounters/part-timer-dialogue.json";
|
||||
import dancingLessonsDialogue from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue.json";
|
||||
import weirdDreamDialogue from "#app/locales/en/mystery-encounters/weird-dream-dialogue.json";
|
||||
import theWinstrateChallengeDialogue from "#app/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json";
|
||||
import teleportingHijinksDialogue from "#app/locales/en/mystery-encounters/teleporting-hijinks-dialogue.json";
|
||||
import bugTypeSuperfanDialogue from "#app/locales/en/mystery-encounters/bug-type-superfan-dialogue.json";
|
||||
import funAndGamesDialogue from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json";
|
||||
import lostAtSea from "./mystery-encounters/lost-at-sea-dialogue.json";
|
||||
import mysteriousChest from "#app/locales/en/mystery-encounters/mysterious-chest-dialogue.json";
|
||||
import mysteriousChallengers from "#app/locales/en/mystery-encounters/mysterious-challengers-dialogue.json";
|
||||
import darkDeal from "#app/locales/en/mystery-encounters/dark-deal-dialogue.json";
|
||||
import departmentStoreSale from "#app/locales/en/mystery-encounters/department-store-sale-dialogue.json";
|
||||
import fieldTrip from "#app/locales/en/mystery-encounters/field-trip-dialogue.json";
|
||||
import fieryFallout from "#app/locales/en/mystery-encounters/fiery-fallout-dialogue.json";
|
||||
import fightOrFlight from "#app/locales/en/mystery-encounters/fight-or-flight-dialogue.json";
|
||||
import safariZone from "#app/locales/en/mystery-encounters/safari-zone-dialogue.json";
|
||||
import shadyVitaminDealer from "#app/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue.json";
|
||||
import slumberingSnorlax from "#app/locales/en/mystery-encounters/slumbering-snorlax-dialogue.json";
|
||||
import trainingSession from "#app/locales/en/mystery-encounters/training-session-dialogue.json";
|
||||
import theStrongStuff from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue.json";
|
||||
import pokemonSalesman from "#app/locales/en/mystery-encounters/the-pokemon-salesman-dialogue.json";
|
||||
import offerYouCantRefuse from "#app/locales/en/mystery-encounters/an-offer-you-cant-refuse-dialogue.json";
|
||||
import delibirdy from "#app/locales/en/mystery-encounters/delibirdy-dialogue.json";
|
||||
import absoluteAvarice from "#app/locales/en/mystery-encounters/absolute-avarice-dialogue.json";
|
||||
import aTrainersTest from "#app/locales/en/mystery-encounters/a-trainers-test-dialogue.json";
|
||||
import trashToTreasure from "#app/locales/en/mystery-encounters/trash-to-treasure-dialogue.json";
|
||||
import berriesAbound from "#app/locales/en/mystery-encounters/berries-abound-dialogue.json";
|
||||
import clowningAround from "#app/locales/en/mystery-encounters/clowning-around-dialogue.json";
|
||||
import partTimer from "#app/locales/en/mystery-encounters/part-timer-dialogue.json";
|
||||
import dancingLessons from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue.json";
|
||||
import weirdDream from "#app/locales/en/mystery-encounters/weird-dream-dialogue.json";
|
||||
import theWinstrateChallenge from "#app/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.json";
|
||||
import teleportingHijinks from "#app/locales/en/mystery-encounters/teleporting-hijinks-dialogue.json";
|
||||
import bugTypeSuperfan from "#app/locales/en/mystery-encounters/bug-type-superfan-dialogue.json";
|
||||
import funAndGames from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json";
|
||||
import uncommonBreed from "#app/locales/en/mystery-encounters/uncommon-breed-dialogue.json";
|
||||
|
||||
/**
|
||||
* Injection patterns that can be used:
|
||||
|
@ -46,32 +47,33 @@ export const mysteryEncounter = {
|
|||
// DO NOT REMOVE
|
||||
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
|
||||
|
||||
mysteriousChallengers: mysteriousChallengersDialogue,
|
||||
mysteriousChest: mysteriousChestDialogue,
|
||||
darkDeal: darkDealDialogue,
|
||||
fightOrFlight: fightOrFlightDialogue,
|
||||
slumberingSnorlax: slumberingSnorlaxDialogue,
|
||||
trainingSession: trainingSessionDialogue,
|
||||
departmentStoreSale: departmentStoreSaleDialogue,
|
||||
shadyVitaminDealer: shadyVitaminDealerDialogue,
|
||||
fieldTrip: fieldTripDialogue,
|
||||
safariZone: safariZoneDialogue,
|
||||
lostAtSea: lostAtSeaDialogue,
|
||||
fieryFallout: fieryFalloutDialogue,
|
||||
theStrongStuff: theStrongStuffDialogue,
|
||||
pokemonSalesman: thePokemonSalesmanDialogue,
|
||||
offerYouCantRefuse: anOfferYouCantRefuseDialogue,
|
||||
delibirdy: delibirdyDialogue,
|
||||
absoluteAvarice: absoluteAvariceDialogue,
|
||||
aTrainersTest: aTrainersTestDialogue,
|
||||
trashToTreasure: trashToTreasureDialogue,
|
||||
berriesAbound: berriesAboundDialogue,
|
||||
clowningAround: clowningAroundDialogue,
|
||||
partTimer: partTimerDialogue,
|
||||
dancingLessons: dancingLessonsDialogue,
|
||||
weirdDream: weirdDreamDialogue,
|
||||
theWinstrateChallenge: theWinstrateChallengeDialogue,
|
||||
teleportingHijinks: teleportingHijinksDialogue,
|
||||
bugTypeSuperfan: bugTypeSuperfanDialogue,
|
||||
funAndGames: funAndGamesDialogue
|
||||
mysteriousChallengers,
|
||||
mysteriousChest,
|
||||
darkDeal,
|
||||
fightOrFlight,
|
||||
slumberingSnorlax,
|
||||
trainingSession,
|
||||
departmentStoreSale,
|
||||
shadyVitaminDealer,
|
||||
fieldTrip,
|
||||
safariZone,
|
||||
lostAtSea,
|
||||
fieryFallout,
|
||||
theStrongStuff,
|
||||
pokemonSalesman,
|
||||
offerYouCantRefuse,
|
||||
delibirdy,
|
||||
absoluteAvarice,
|
||||
aTrainersTest,
|
||||
trashToTreasure,
|
||||
berriesAbound,
|
||||
clowningAround,
|
||||
partTimer,
|
||||
dancingLessons,
|
||||
weirdDream,
|
||||
theWinstrateChallenge,
|
||||
teleportingHijinks,
|
||||
bugTypeSuperfan,
|
||||
funAndGames,
|
||||
uncommonBreed,
|
||||
} as const;
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
"1": {
|
||||
"label": "Battle the Pokémon",
|
||||
"tooltip": "(-) Hard Battle\n(+) New Item",
|
||||
"selected": "You approach the\nPokémon without fear."
|
||||
"selected": "You approach the\nPokémon without fear.",
|
||||
"stat_boost": "The {{enemyPokemon}} latent strength boosted one of its stats!"
|
||||
},
|
||||
"2": {
|
||||
"label": "Steal the Item",
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"intro": "That isn't just an ordinary Pokémon!",
|
||||
"title": "Uncommon Breed",
|
||||
"description": "That {{enemyPokemon}} looks special compared to others of its kind. @[TOOLTIP_TITLE]{Perhaps it knows a special move?} You could battle and catch it outright, but there might also be a way to befriend it.",
|
||||
"query": "What will you do?",
|
||||
"option": {
|
||||
"1": {
|
||||
"label": "Battle the Pokémon",
|
||||
"tooltip": "(-) Tricky Battle\n(+) Strong Catchable Foe",
|
||||
"selected": "You approach the\n{{enemyPokemon}} without fear.",
|
||||
"stat_boost": "The {{enemyPokemon}} heightened abilities boost its stats!"
|
||||
},
|
||||
"2": {
|
||||
"label": "Give It Food",
|
||||
"disabled_tooltip": "You need 4 berry items to choose this",
|
||||
"tooltip": "(-) Give 4 Berries\n(+) The {{enemyPokemon}} Likes You",
|
||||
"selected": "You toss the berries at the {{enemyPokemon}}!$It eats them happily!$The {{enemyPokemon}} wants to join your party!"
|
||||
},
|
||||
"3": {
|
||||
"label": "Befriend It",
|
||||
"disabled_tooltip": "Your Pokémon need to know certain moves to choose this",
|
||||
"tooltip": "(+) {{option3PrimaryName}} uses {{option3PrimaryMove}}\n(+) The {{enemyPokemon}} Likes You",
|
||||
"selected": "Your {{option3PrimaryName}} uses {{option3PrimaryMove}} to charm the {{enemyPokemon}}!$The {{enemyPokemon}} wants to join your party!"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,9 +133,9 @@ class DefaultOverrides {
|
|||
// -------------------------
|
||||
|
||||
/** 1 to 256, set to null to ignore */
|
||||
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = 256;
|
||||
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null;
|
||||
readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null;
|
||||
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = MysteryEncounterType.MYSTERIOUS_CHEST;
|
||||
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null;
|
||||
|
||||
// -------------------------
|
||||
// MODIFIER / ITEM OVERRIDES
|
||||
|
|
|
@ -22,6 +22,8 @@ import { ReturnPhase } from "#app/phases/return-phase";
|
|||
import { CheckSwitchPhase } from "#app/phases/check-switch-phase";
|
||||
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
||||
import { GameOverPhase } from "#app/phases/game-over-phase";
|
||||
import { SwitchPhase } from "#app/phases/switch-phase";
|
||||
|
||||
/**
|
||||
* Will handle (in order):
|
||||
|
@ -161,21 +163,22 @@ export class MysteryEncounterOptionSelectedPhase extends Phase {
|
|||
this.onOptionSelect(this.scene).finally(() => {
|
||||
this.end();
|
||||
});
|
||||
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset());
|
||||
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset() * 500);
|
||||
});
|
||||
} else {
|
||||
this.scene.executeWithSeedOffset(() => {
|
||||
this.onOptionSelect(this.scene).finally(() => {
|
||||
this.end();
|
||||
});
|
||||
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset());
|
||||
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset() * 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs at the beginning of an Encounter's battle
|
||||
* Will cleanup any residual flinches, Endure, etc. that are left over from startOfBattleEffects
|
||||
* Will clean up any residual flinches, Endure, etc. that are left over from startOfBattleEffects
|
||||
* Will also handle Game Overs, switches, etc. that could happen from handleMysteryEncounterBattleStartEffects
|
||||
* See [TurnEndPhase](../phases.ts) for more details
|
||||
*/
|
||||
export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
||||
|
@ -196,6 +199,28 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
|||
this.scene.tryRemovePhase(p => p instanceof PostTurnStatusEffectPhase);
|
||||
}
|
||||
|
||||
// The total number of Pokemon in the player's party that can legally fight
|
||||
const legalPlayerPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle());
|
||||
// The total number of legal player Pokemon that aren't currently on the field
|
||||
const legalPlayerPartyPokemon = legalPlayerPokemon.filter(p => !p.isActive(true));
|
||||
if (!legalPlayerPokemon.length) {
|
||||
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
||||
}
|
||||
|
||||
// Check for any KOd player mons and switch
|
||||
// For each fainted mon on the field, if there is a legal replacement, summon it
|
||||
const playerField = this.scene.getPlayerField();
|
||||
playerField.forEach((pokemon, i) => {
|
||||
if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) {
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, i, true, false));
|
||||
}
|
||||
});
|
||||
|
||||
// THEN, if is a double battle, and player only has 1 summoned pokemon, center pokemon on field
|
||||
if (this.scene.currentBattle.double && legalPlayerPokemon.length === 1 && legalPlayerPartyPokemon.length === 0) {
|
||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||
}
|
||||
|
||||
super.end();
|
||||
}
|
||||
}
|
||||
|
@ -477,7 +502,7 @@ export class PostMysteryEncounterPhase extends Phase {
|
|||
this.continueEncounter();
|
||||
}
|
||||
});
|
||||
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset());
|
||||
}, this.scene.currentBattle.mysteryEncounter?.getSeedOffset() * 2000);
|
||||
} else {
|
||||
this.continueEncounter();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
import * as MysteryEncounters 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, runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounter-test-utils";
|
||||
import { Moves } from "#enums/moves";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { CommandPhase } from "#app/phases/command-phase";
|
||||
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
|
||||
import { MovePhase } from "#app/phases/move-phase";
|
||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
|
||||
const namespace = "mysteryEncounter:uncommonBreed";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.CAVE;
|
||||
const defaultWave = 45;
|
||||
|
||||
describe("Uncommon Breed - 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();
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.CAVE, [MysteryEncounterType.UNCOMMON_BREED]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
||||
|
||||
expect(UncommonBreedEncounter.encounterType).toBe(MysteryEncounterType.UNCOMMON_BREED);
|
||||
expect(UncommonBreedEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
|
||||
expect(UncommonBreedEncounter.dialogue).toBeDefined();
|
||||
expect(UncommonBreedEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
|
||||
expect(UncommonBreedEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);
|
||||
expect(UncommonBreedEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}.description`);
|
||||
expect(UncommonBreedEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}.query`);
|
||||
expect(UncommonBreedEncounter.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.UNCOMMON_BREED);
|
||||
});
|
||||
|
||||
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 = UncommonBreedEncounter;
|
||||
|
||||
const { onInit } = UncommonBreedEncounter;
|
||||
|
||||
expect(UncommonBreedEncounter.onInit).toBeDefined();
|
||||
|
||||
UncommonBreedEncounter.populateDialogueTokensFromRequirements(scene);
|
||||
const onInitResult = onInit!(scene);
|
||||
|
||||
const config = UncommonBreedEncounter.enemyPartyConfigs[0];
|
||||
expect(config).toBeDefined();
|
||||
expect(config.pokemonConfigs?.[0].isBoss).toBe(false);
|
||||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
describe("Option 1 - Fight", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = UncommonBreedEncounter.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: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should start a fight against the boss", async () => {
|
||||
const phaseSpy = vi.spyOn(scene, "pushPhase");
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
||||
|
||||
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
||||
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
|
||||
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
|
||||
expect(enemyField.length).toBe(1);
|
||||
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
|
||||
expect(enemyField[0].summonData.battleStats).toEqual([1, 1, 1, 1, 1, 0, 0]);
|
||||
|
||||
// Should have used its egg move pre-battle
|
||||
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
|
||||
expect(movePhases.length).toBe(1);
|
||||
const eggMoves: Moves[] = speciesEggMoves[getPokemonSpecies(speciesToSpawn).getRootSpeciesId()];
|
||||
const usedMove = (movePhases[0] as MovePhase).move.moveId;
|
||||
expect(eggMoves.includes(usedMove)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Give it Food", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = UncommonBreedEncounter.options[1];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
}
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should NOT be selectable if the player doesn't have enough berries", async () => {
|
||||
game.override.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}]);
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should skip fight when player meets requirements", async () => {
|
||||
game.override.startingHeldItems([{name: "BERRY", count: 2, type: BerryType.SITRUS}, {name: "BERRY", count: 3, type: BerryType.GANLON}]);
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Use an Attracting Move", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = UncommonBreedEncounter.options[2];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.3.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.3.selected`,
|
||||
}
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should NOT be selectable if the player doesn't have an Attracting move", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
||||
scene.getParty().forEach(p => p.moveset = []);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase?.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
|
||||
expect(scene.getCurrentPhase()?.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should skip fight when player meets requirements", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
|
||||
// Mock moveset
|
||||
scene.getParty()[0].moveset = [new PokemonMove(Moves.CHARM)];
|
||||
await runMysteryEncounterToEnd(game, 3);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue