add unit tests for clowning around
This commit is contained in:
parent
afe1015094
commit
42eadcb36c
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "encounter_radar.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 17,
|
||||
"h": 16
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 15,
|
||||
"h": 14
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 15,
|
||||
"h": 14
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 15,
|
||||
"h": 14
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:eb3445f19546ab36edb2909c89b8aa86:c8de156a28ef70ee4ddf70cffe1ba3ba:e7008b81ccf0cb0325145a809afa6165$"
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "exclaim.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 32,
|
||||
"h": 32
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 32,
|
||||
"h": 32
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 32,
|
||||
"h": 32
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 32,
|
||||
"h": 32
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:895f0a79b89fa0fb44167f4584fd9a22:357b46953b7e17c6b2f43a62d52855d8:cc1ed0e4f90aaa9dcf1b39a0af1283b0$"
|
||||
}
|
||||
}
|
|
@ -534,13 +534,11 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
|||
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
|
||||
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
|
||||
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
|
||||
// const encounterAnimIds = Utils.getEnumValues(EncounterAnim);
|
||||
const encounterAnimFetches = [];
|
||||
for (const anim of anims) {
|
||||
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
|
||||
continue;
|
||||
}
|
||||
// const encounterAnimId = encounterAnimIds[anim];
|
||||
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
|
||||
.then(response => response.json())
|
||||
.then(cas => encounterAnims.set(anim, new AnimConfig(cas))));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
|
@ -104,16 +104,14 @@ export const ClowningAroundEncounter: IMysteryEncounter =
|
|||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Clown trainer is pulled from pool of boss trainers (gym leaders) for the biome
|
||||
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
|
||||
const clownTrainerType = TrainerType.HARLEQUIN;
|
||||
const clownConfig = trainerConfigs[clownTrainerType].copy();
|
||||
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
|
||||
const clownConfig = trainerConfigs[clownTrainerType].copy();
|
||||
clownConfig.setPartyTemplates(clownPartyTemplate);
|
||||
clownConfig.setDoubleOnly();
|
||||
clownConfig.partyTemplateFunc = null; // Overrides party template func
|
||||
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
||||
|
||||
// Generate random ability for Blacephalon from pool
|
||||
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
||||
|
@ -142,28 +140,6 @@ export const ClowningAroundEncounter: IMysteryEncounter =
|
|||
// Load animations/sfx for start of fight moves
|
||||
loadCustomMovesForEncounter(scene, [Moves.ROLE_PLAY, Moves.TAUNT]);
|
||||
|
||||
// These have to be defined at runtime so that modifierTypes exist
|
||||
encounter.misc.RANDOM_ULTRA_POOL = [
|
||||
modifierTypes.REVIVER_SEED,
|
||||
modifierTypes.GOLDEN_PUNCH,
|
||||
modifierTypes.ATTACK_TYPE_BOOSTER,
|
||||
modifierTypes.QUICK_CLAW,
|
||||
modifierTypes.WIDE_LENS,
|
||||
modifierTypes.WHITE_HERB
|
||||
];
|
||||
|
||||
encounter.misc.RANDOM_ROGUE_POOL = [
|
||||
modifierTypes.LEFTOVERS,
|
||||
modifierTypes.SHELL_BELL,
|
||||
modifierTypes.SOUL_DEW,
|
||||
modifierTypes.SOOTHE_BELL,
|
||||
modifierTypes.SCOPE_LENS,
|
||||
modifierTypes.BATON,
|
||||
modifierTypes.FOCUS_BAND,
|
||||
modifierTypes.KINGS_ROCK,
|
||||
modifierTypes.GRIP_CLAW
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
|
@ -187,7 +163,7 @@ export const ClowningAroundEncounter: IMysteryEncounter =
|
|||
// Spawn battle
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true });
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
|
||||
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
|
||||
encounter.startOfBattleEffects.push(
|
||||
|
@ -217,7 +193,6 @@ export const ClowningAroundEncounter: IMysteryEncounter =
|
|||
// After the battle, offer the player the opportunity to permanently swap ability
|
||||
const abilityWasSwapped = await handleSwapAbility(scene);
|
||||
if (abilityWasSwapped) {
|
||||
await scene.ui.setMode(Mode.MESSAGE);
|
||||
await showEncounterText(scene, `${namespace}.option.1.ability_gained`);
|
||||
}
|
||||
|
||||
|
@ -284,44 +259,33 @@ export const ClowningAroundEncounter: IMysteryEncounter =
|
|||
const items = mostHeldItemsPokemon.getHeldItems();
|
||||
|
||||
// Shuffles Berries (if they have any)
|
||||
const berries = items.filter(m => m instanceof BerryModifier);
|
||||
let numBerries = 0;
|
||||
items.filter(m => m instanceof BerryModifier)
|
||||
.forEach(m => {
|
||||
numBerries += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
});
|
||||
|
||||
berries.forEach(berry => {
|
||||
const stackCount = berry.stackCount;
|
||||
scene.removeModifier(berry);
|
||||
const newBerry = generateModifierTypeOption(scene, modifierTypes.BERRY, [randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType]).type as BerryModifierType;
|
||||
for (let i = 0; i < stackCount; i++) {
|
||||
applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newBerry);
|
||||
}
|
||||
});
|
||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
|
||||
|
||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||
const transferableItems = items.filter(m => m.isTransferrable && !(m instanceof BerryModifier));
|
||||
|
||||
transferableItems.forEach(transferableItem => {
|
||||
const stackCount = transferableItem.stackCount;
|
||||
transferableItem.type.withTierFromPool();
|
||||
|
||||
// Lucky Eggs and other items that do not appear in item pools are treated as Ultra rarity
|
||||
const tier = transferableItem.type.tier ?? ModifierTier.ULTRA;
|
||||
|
||||
if (tier === ModifierTier.ULTRA) {
|
||||
scene.removeModifier(transferableItem);
|
||||
for (let i = 0; i < stackCount; i++) {
|
||||
const newItemType = encounter.misc.RANDOM_ULTRA_POOL[randSeedInt(encounter.misc.RANDOM_ULTRA_POOL.length)];
|
||||
const newMod = generateModifierTypeOption(scene, newItemType).type as PokemonHeldItemModifierType;
|
||||
applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newMod);
|
||||
let numUltra = 0;
|
||||
let numRogue = 0;
|
||||
items.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||
.forEach(m => {
|
||||
const type = m.type.withTierFromPool();
|
||||
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||
if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) {
|
||||
numUltra += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
} else if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||
numRogue += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
}
|
||||
} else if (tier === ModifierTier.ROGUE) {
|
||||
scene.removeModifier(transferableItem);
|
||||
for (let i = 0; i < stackCount; i++) {
|
||||
const newItemType = encounter.misc.RANDOM_ROGUE_POOL[randSeedInt(encounter.misc.RANDOM_ROGUE_POOL.length)];
|
||||
const newMod = generateModifierTypeOption(scene, newItemType).type as PokemonHeldItemModifierType;
|
||||
applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newMod);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
|
@ -456,7 +420,7 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
|
|||
}
|
||||
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||
resolve(true);
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
||||
};
|
||||
|
||||
const onPokemonNotSelected = () => {
|
||||
|
@ -467,3 +431,67 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
|
|||
|
||||
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
|
||||
}
|
||||
|
||||
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") {
|
||||
// These pools have to be defined at runtime so that modifierTypes exist
|
||||
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||
const ultraPool = [
|
||||
[modifierTypes.REVIVER_SEED, 1],
|
||||
[modifierTypes.GOLDEN_PUNCH, 5],
|
||||
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
|
||||
[modifierTypes.QUICK_CLAW, 3],
|
||||
[modifierTypes.WIDE_LENS, 3],
|
||||
[modifierTypes.WHITE_HERB, 2]
|
||||
];
|
||||
|
||||
const roguePool = [
|
||||
[modifierTypes.LEFTOVERS, 4],
|
||||
[modifierTypes.SHELL_BELL, 4],
|
||||
[modifierTypes.SOUL_DEW, 10],
|
||||
[modifierTypes.SOOTHE_BELL, 3],
|
||||
[modifierTypes.SCOPE_LENS, 5],
|
||||
[modifierTypes.BATON, 1],
|
||||
[modifierTypes.FOCUS_BAND, 5],
|
||||
[modifierTypes.KINGS_ROCK, 3],
|
||||
[modifierTypes.GRIP_CLAW, 5]
|
||||
];
|
||||
|
||||
const berryPool = [
|
||||
[BerryType.APICOT, 3],
|
||||
[BerryType.ENIGMA, 2],
|
||||
[BerryType.GANLON, 3],
|
||||
[BerryType.LANSAT, 3],
|
||||
[BerryType.LEPPA, 2],
|
||||
[BerryType.LIECHI, 3],
|
||||
[BerryType.LUM, 2],
|
||||
[BerryType.PETAYA, 3],
|
||||
[BerryType.SALAC, 2],
|
||||
[BerryType.SITRUS, 2],
|
||||
[BerryType.STARF, 3]
|
||||
];
|
||||
|
||||
let pool: any[];
|
||||
if (tier === "Berries") {
|
||||
pool = berryPool;
|
||||
} else {
|
||||
pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool;
|
||||
}
|
||||
|
||||
for (let i = 0; i < numItems; i++) {
|
||||
const randIndex = randSeedInt(pool.length);
|
||||
const newItemType = pool[randIndex];
|
||||
let newMod;
|
||||
if (tier === "Berries") {
|
||||
newMod = generateModifierTypeOption(scene, modifierTypes.BERRY, [newItemType[0]]).type as PokemonHeldItemModifierType;
|
||||
} else {
|
||||
newMod = generateModifierTypeOption(scene, newItemType[0]).type as PokemonHeldItemModifierType;
|
||||
}
|
||||
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
|
||||
// Decrement max stacks and remove from pool if at max
|
||||
newItemType[1]--;
|
||||
if (newItemType[1] <= 0) {
|
||||
pool.splice(randIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/myster
|
|||
* @param scene
|
||||
*/
|
||||
export function doTrainerExclamation(scene: BattleScene) {
|
||||
const exclamationSprite = scene.addFieldSprite(0, 0, "exclaim");
|
||||
const exclamationSprite = scene.add.sprite(0, 0, "exclaim");
|
||||
exclamationSprite.setName("exclamation");
|
||||
scene.field.add(exclamationSprite);
|
||||
scene.field.moveTo(exclamationSprite, scene.field.getAll().length - 1);
|
||||
|
@ -386,10 +386,12 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M
|
|||
*/
|
||||
export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: (pokemon: PlayerPokemon) => string): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const modeToSetOnExit = scene.ui.getMode();
|
||||
|
||||
// Open party screen to choose pokemon to train
|
||||
scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: integer, option: PartyOption) => {
|
||||
if (slotIndex < scene.getParty().length) {
|
||||
scene.ui.setMode(Mode.MYSTERY_ENCOUNTER).then(() => {
|
||||
scene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
const pokemon = scene.getParty()[slotIndex];
|
||||
const secondaryOptions = onPokemonSelected(pokemon);
|
||||
if (!secondaryOptions) {
|
||||
|
@ -443,7 +445,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
|
|||
});
|
||||
});
|
||||
} else {
|
||||
scene.ui.setMode(Mode.MYSTERY_ENCOUNTER).then(() => {
|
||||
scene.ui.setMode(modeToSetOnExit).then(() => {
|
||||
if (onPokemonNotSelected) {
|
||||
onPokemonNotSelected();
|
||||
}
|
||||
|
|
|
@ -190,9 +190,6 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
}
|
||||
});
|
||||
|
||||
// Load dex progress icon
|
||||
this.scene.loadAtlas("encounter_radar", "mystery-encounters");
|
||||
|
||||
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => {
|
||||
this.spriteConfigs.every((config) => {
|
||||
if (config.isItem) {
|
||||
|
|
|
@ -275,6 +275,9 @@ export class LoadingScene extends SceneBase {
|
|||
}
|
||||
}
|
||||
|
||||
// Load Mystery Encounter dex progress icon
|
||||
this.loadImage("encounter_radar", "mystery-encounters");
|
||||
|
||||
this.loadAtlas("dualshock", "inputs");
|
||||
this.loadAtlas("xbox", "inputs");
|
||||
this.loadAtlas("keyboard", "inputs");
|
||||
|
|
|
@ -127,9 +127,9 @@ class DefaultOverrides {
|
|||
// -------------------------
|
||||
|
||||
// 1 to 256, set to null to ignore
|
||||
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null;
|
||||
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256;
|
||||
readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null;
|
||||
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null;
|
||||
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.TRAINING_SESSION;
|
||||
|
||||
// -------------------------
|
||||
// MODIFIER / ITEM OVERRIDES
|
||||
|
|
|
@ -915,7 +915,7 @@ export class EncounterPhase extends BattlePhase {
|
|||
// Load Mystery Encounter Exclamation bubble and sfx
|
||||
loadEnemyAssets.push(new Promise<void>(resolve => {
|
||||
this.scene.loadSe("GEN8- Exclaim", "battle_anims", "GEN8- Exclaim.wav");
|
||||
this.scene.loadAtlas("exclaim", "mystery-encounters");
|
||||
this.scene.loadImage("exclaim", "mystery-encounters");
|
||||
this.scene.load.once(Phaser.Loader.Events.COMPLETE, () => resolve());
|
||||
if (!this.scene.load.isLoading()) {
|
||||
this.scene.load.start();
|
||||
|
|
|
@ -0,0 +1,374 @@
|
|||
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 { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import * as BattleAnims from "#app/data/battle-anims";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { CommandPhase, MovePhase, NewBattlePhase, SelectModifierPhase } from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import Pokemon, { PokemonMove } from "#app/field/pokemon";
|
||||
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 { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { Button } from "#enums/buttons";
|
||||
import PartyUiHandler from "#app/ui/party-ui-handler";
|
||||
import OptionSelectUiHandler from "#app/ui/settings/option-select-ui-handler";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { Type } from "#app/data/type";
|
||||
|
||||
const namespace = "mysteryEncounter:clowningAround";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.CAVE;
|
||||
const defaultWave = 45;
|
||||
|
||||
describe("Clowning Around - 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.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.CAVE, [MysteryEncounterType.CLOWNING_AROUND]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||
|
||||
expect(ClowningAroundEncounter.encounterType).toBe(MysteryEncounterType.CLOWNING_AROUND);
|
||||
expect(ClowningAroundEncounter.encounterTier).toBe(MysteryEncounterTier.ULTRA);
|
||||
expect(ClowningAroundEncounter.dialogue).toBeDefined();
|
||||
expect(ClowningAroundEncounter.dialogue.intro).toStrictEqual([
|
||||
{ text: `${namespace}.intro` },
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
},
|
||||
]);
|
||||
expect(ClowningAroundEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
|
||||
expect(ClowningAroundEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
|
||||
expect(ClowningAroundEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
|
||||
expect(ClowningAroundEncounter.options.length).toBe(3);
|
||||
});
|
||||
|
||||
it("should not run below wave 80", async () => {
|
||||
game.override.startingWave(79);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.CLOWNING_AROUND);
|
||||
});
|
||||
|
||||
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 = ClowningAroundEncounter;
|
||||
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
|
||||
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||
|
||||
const { onInit } = ClowningAroundEncounter;
|
||||
|
||||
expect(ClowningAroundEncounter.onInit).toBeDefined();
|
||||
|
||||
ClowningAroundEncounter.populateDialogueTokensFromRequirements(scene);
|
||||
const onInitResult = onInit(scene);
|
||||
const config = ClowningAroundEncounter.enemyPartyConfigs[0];
|
||||
|
||||
expect(config.doubleBattle).toBe(true);
|
||||
expect(config.trainerConfig.trainerType).toBe(TrainerType.HARLEQUIN);
|
||||
expect(config.pokemonConfigs[0]).toEqual({
|
||||
species: getPokemonSpecies(Species.MR_MIME),
|
||||
isBoss: true,
|
||||
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC]
|
||||
});
|
||||
expect(config.pokemonConfigs[1]).toEqual({
|
||||
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||
ability: expect.any(Number),
|
||||
mysteryEncounterData: expect.anything(),
|
||||
isBoss: true,
|
||||
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
|
||||
});
|
||||
expect(config.pokemonConfigs[1].mysteryEncounterData.types.length).toBe(2);
|
||||
expect([
|
||||
Abilities.STURDY,
|
||||
Abilities.PICKUP,
|
||||
Abilities.INTIMIDATE,
|
||||
Abilities.GUTS,
|
||||
Abilities.DROUGHT,
|
||||
Abilities.DRIZZLE,
|
||||
Abilities.SNOW_WARNING,
|
||||
Abilities.SAND_STREAM,
|
||||
Abilities.ELECTRIC_SURGE,
|
||||
Abilities.PSYCHIC_SURGE,
|
||||
Abilities.GRASSY_SURGE,
|
||||
Abilities.MISTY_SURGE,
|
||||
Abilities.MAGICIAN,
|
||||
Abilities.SHEER_FORCE,
|
||||
Abilities.PRANKSTER
|
||||
]).toContain(config.pokemonConfigs[1].ability);
|
||||
expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].ability);
|
||||
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
|
||||
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
|
||||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
describe("Option 1 - Battle the Clown", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = ClowningAroundEncounter.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: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should start double battle against the clown", async () => {
|
||||
const phaseSpy = vi.spyOn(scene, "pushPhase");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||
expect(enemyField.length).toBe(2);
|
||||
expect(enemyField[0].species.speciesId).toBe(Species.MR_MIME);
|
||||
expect(enemyField[0].moveset).toEqual([new PokemonMove(Moves.TEETER_DANCE), new PokemonMove(Moves.ALLY_SWITCH), new PokemonMove(Moves.DAZZLING_GLEAM), new PokemonMove(Moves.PSYCHIC)]);
|
||||
expect(enemyField[1].species.speciesId).toBe(Species.BLACEPHALON);
|
||||
expect(enemyField[1].moveset).toEqual([new PokemonMove(Moves.TRICK), new PokemonMove(Moves.HYPNOSIS), new PokemonMove(Moves.SHADOW_BALL), new PokemonMove(Moves.MIND_BLOWN)]);
|
||||
|
||||
// Should have used moves pre-battle
|
||||
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
|
||||
expect(movePhases.length).toBe(3);
|
||||
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.ROLE_PLAY).length).toBe(1);
|
||||
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.TAUNT).length).toBe(2);
|
||||
});
|
||||
|
||||
it("should let the player gain the ability after battle completion", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, null, true);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||
const abilityToTrain = scene.currentBattle.mysteryEncounter.misc.ability;
|
||||
|
||||
game.onNextPrompt("PostMysteryEncounterPhase", Mode.MESSAGE, () => {
|
||||
game.scene.ui.getHandler().processInput(Button.ACTION);
|
||||
});
|
||||
|
||||
// Run to ability train option selection
|
||||
const optionSelectUiHandler = game.scene.ui.handlers[Mode.OPTION_SELECT] as OptionSelectUiHandler;
|
||||
vi.spyOn(optionSelectUiHandler, "show");
|
||||
const partyUiHandler = game.scene.ui.handlers[Mode.PARTY] as PartyUiHandler;
|
||||
vi.spyOn(partyUiHandler, "show");
|
||||
game.endPhase();
|
||||
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(PostMysteryEncounterPhase.name);
|
||||
|
||||
// Wait for Yes/No confirmation to appear
|
||||
await vi.waitFor(() => expect(optionSelectUiHandler.show).toHaveBeenCalled());
|
||||
// Select "Yes" on train ability
|
||||
optionSelectUiHandler.processInput(Button.ACTION);
|
||||
// Select first pokemon in party to train
|
||||
await vi.waitFor(() => expect(partyUiHandler.show).toHaveBeenCalled());
|
||||
partyUiHandler.processInput(Button.ACTION);
|
||||
// Click "Select" on Pokemon
|
||||
partyUiHandler.processInput(Button.ACTION);
|
||||
// Stop next battle before it runs
|
||||
await game.phaseInterceptor.to(NewBattlePhase, false);
|
||||
|
||||
const leadPokemon = scene.getParty()[0];
|
||||
expect(leadPokemon.mysteryEncounterData.ability).toBe(abilityToTrain);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Remain Unprovoked", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = ClowningAroundEncounter.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`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.2.selected_2`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.2.selected_3`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should randomize held items of the Pokemon with the most items, and not the held items of other pokemon", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||
|
||||
// 2 Sitrus Berries on lead
|
||||
scene.modifiers = [];
|
||||
let itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
|
||||
// 2 Ganlon Berries on lead
|
||||
itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.GANLON]).type as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
|
||||
// 5 Golden Punch on lead (ultra)
|
||||
itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_PUNCH).type as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
|
||||
// 5 Lucky Egg on lead (ultra)
|
||||
itemType = generateModifierTypeOption(scene, modifierTypes.LUCKY_EGG).type as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
|
||||
// 5 Soul Dew on lead (rogue)
|
||||
itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getParty()[0], 5, itemType);
|
||||
// 2 Golden Egg on lead (rogue)
|
||||
itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_EGG).type as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getParty()[0], 2, itemType);
|
||||
|
||||
// 5 Soul Dew on second party pokemon (these should not change)
|
||||
itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getParty()[1], 5, itemType);
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
|
||||
const leadItemsAfter = scene.getParty()[0].getHeldItems();
|
||||
const ultraCountAfter = leadItemsAfter
|
||||
.filter(m => m.type.tier === ModifierTier.ULTRA)
|
||||
.reduce((a, b) => a + b.stackCount, 0);
|
||||
const rogueCountAfter = leadItemsAfter
|
||||
.filter(m => m.type.tier === ModifierTier.ROGUE)
|
||||
.reduce((a, b) => a + b.stackCount, 0);
|
||||
expect(ultraCountAfter).toBe(10);
|
||||
expect(rogueCountAfter).toBe(7);
|
||||
|
||||
const secondItemsAfter = scene.getParty()[1].getHeldItems();
|
||||
expect(secondItemsAfter.length).toBe(1);
|
||||
expect(secondItemsAfter[0].type.id).toBe("SOUL_DEW");
|
||||
expect(secondItemsAfter[0].stackCount).toBe(5);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - Return the Insults", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = ClowningAroundEncounter.options[2];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.3.label`,
|
||||
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.3.selected`,
|
||||
},
|
||||
{
|
||||
text: `${namespace}.option.3.selected_2`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.option.3.selected_3`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should randomize the pokemon types of the party", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||
|
||||
// Same type moves on lead
|
||||
scene.getParty()[0].moveset = [new PokemonMove(Moves.ICE_BEAM), new PokemonMove(Moves.SURF)];
|
||||
// Different type moves on second
|
||||
scene.getParty()[1].moveset = [new PokemonMove(Moves.GRASS_KNOT), new PokemonMove(Moves.ELECTRO_BALL)];
|
||||
// No moves on third
|
||||
scene.getParty()[2].moveset = [];
|
||||
await runMysteryEncounterToEnd(game, 3);
|
||||
|
||||
const leadTypesAfter = scene.getParty()[0].mysteryEncounterData.types;
|
||||
const secondaryTypesAfter = scene.getParty()[1].mysteryEncounterData.types;
|
||||
const thirdTypesAfter = scene.getParty()[2].mysteryEncounterData.types;
|
||||
|
||||
expect(leadTypesAfter.length).toBe(2);
|
||||
expect(leadTypesAfter).not.toBe([Type.ICE, Type.WATER]);
|
||||
expect(secondaryTypesAfter.length).toBe(2);
|
||||
expect(secondaryTypesAfter.includes(Type.GRASS)).toBeTruthy();
|
||||
expect(secondaryTypesAfter.includes(Type.ELECTRIC)).toBeTruthy();
|
||||
expect(thirdTypesAfter.length).toBe(1);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.CLOWNING_AROUND, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 3);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function addItemToPokemon(scene: BattleScene, pokemon: Pokemon, stackCount: integer, itemType: PokemonHeldItemModifierType) {
|
||||
const itemMod = itemType.newModifier(pokemon) as PokemonHeldItemModifier;
|
||||
itemMod.stackCount = stackCount;
|
||||
await scene.addModifier(itemMod, true, false, false, true);
|
||||
await scene.updateModifiers(true);
|
||||
}
|
|
@ -23,6 +23,7 @@ import { PokemonBaseStatTotalModifier } from "#app/modifier/modifier";
|
|||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
|
||||
const namespace = "mysteryEncounter:theStrongStuff";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
|
@ -118,7 +119,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
|||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
spriteScale: 1.5,
|
||||
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||
modifierTypes: expect.any(Array),
|
||||
|
|
|
@ -81,8 +81,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
this.rarityBall.setScale(0.75);
|
||||
this.descriptionContainer.add(this.rarityBall);
|
||||
|
||||
const dexProgressIndicator = this.scene.add.sprite(12, 9, "encounter_radar");
|
||||
dexProgressIndicator.setScale(0.85);
|
||||
const dexProgressIndicator = this.scene.add.sprite(12, 10, "encounter_radar");
|
||||
dexProgressIndicator.setScale(0.80);
|
||||
this.dexProgressContainer.add(dexProgressIndicator);
|
||||
this.dexProgressContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 24, 28), Phaser.Geom.Rectangle.Contains);
|
||||
this.dexProgressContainer.on("pointerover", () => {
|
||||
|
|
Loading…
Reference in New Issue