Merge pull request #106 from AsdarDevelops/one-for-all
The Strong Stuff
This commit is contained in:
commit
4afcdad3db
|
@ -50,4 +50,4 @@ jobs:
|
|||
run: npm ci # Use 'npm ci' to install dependencies
|
||||
|
||||
- name: tests # Step to run tests
|
||||
run: npm run test${{ runner.debug == '0' &&':silent' || '' }} # silent on default. if debug run loud
|
||||
run: npm run test${{ runner.debug == '0' &&':silent' || '' }} # silent on default. if debug run loud.
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "berry_juice.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 24,
|
||||
"h": 23
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 24,
|
||||
"h": 24
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"w": 22,
|
||||
"h": 21
|
||||
},
|
||||
"frame": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 22,
|
||||
"h": 21
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:04685a0eb6ef9095824b65408ec1b38f:9891674d538df100fcddde29330c21ae:927f117bdb1c2a27226a5540ce00ee8b$"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 318 B |
|
@ -4,7 +4,7 @@ import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
|||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
import { AddPokeballModifierType } from "../../../modifier/modifier-type";
|
||||
import { AddPokeballModifierType } from "#app/modifier/modifier-type";
|
||||
import { PokeballType } from "../../pokeball";
|
||||
import { getPokemonSpecies } from "../../pokemon-species";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { applyDamageToPokemon, EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, initCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, initCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { modifierTypes, } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "../../../battle-scene";
|
||||
|
@ -17,6 +17,7 @@ import { WeatherType } from "#app/data/weather";
|
|||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:fieryFallout";
|
||||
|
|
|
@ -5,7 +5,8 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||
import BattleScene from "../../../battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier } from "../mystery-encounter";
|
||||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { applyDamageToPokemon, leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
|
||||
import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
|
||||
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
const OPTION_1_REQUIRED_MOVE = Moves.SURF;
|
||||
const OPTION_2_REQUIRED_MOVE = Moves.FLY;
|
||||
|
|
|
@ -28,7 +28,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
|||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 10,
|
||||
yShadowOffset: 3,
|
||||
yShadow: 3,
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
},
|
||||
])
|
||||
|
@ -109,7 +109,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
|
|||
scene,
|
||||
true
|
||||
);
|
||||
koPlayerPokemon(highestLevelPokemon);
|
||||
koPlayerPokemon(scene, highestLevelPokemon);
|
||||
|
||||
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.name);
|
||||
// Show which Pokemon was KOed, then leave encounter with no rewards
|
||||
|
|
|
@ -38,7 +38,7 @@ export const SafariZoneEncounter: IMysteryEncounter =
|
|||
hasShadow: true,
|
||||
x: 4,
|
||||
y: 10,
|
||||
yShadowOffset: 3
|
||||
yShadow: 3
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
|
|
|
@ -10,6 +10,7 @@ import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } fro
|
|||
import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { MoneyRequirement } from "../mystery-encounter-requirements";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:shadyVitaminDealer";
|
||||
|
@ -35,7 +36,7 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
|
|||
repeat: true,
|
||||
x: 12,
|
||||
y: -5,
|
||||
yShadowOffset: -5
|
||||
yShadow: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "b2w2_veteran_m",
|
||||
|
@ -43,7 +44,7 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
|
|||
hasShadow: true,
|
||||
x: -12,
|
||||
y: 3,
|
||||
yShadowOffset: 3
|
||||
yShadow: 3
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
|
@ -122,8 +123,7 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter =
|
|||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
|
||||
// Pokemon takes 1/3 max HP damage
|
||||
const damage = Math.round(chosenPokemon.getMaxHp() / 3);
|
||||
chosenPokemon.hp = Math.max(chosenPokemon.hp - damage, 0);
|
||||
applyDamageToPokemon(scene, chosenPokemon, Math.floor(chosenPokemon.getMaxHp() / 3));
|
||||
|
||||
// Roll for poison (80%)
|
||||
if (randSeedInt(10) < 8) {
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, initCustomMovesForEncounter, 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 "../../../battle-scene";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Species } from "#enums/species";
|
||||
import { Nature } from "#app/data/nature";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { StatChangePhase } from "#app/phases";
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:theStrongStuff";
|
||||
|
||||
export const TheStrongStuffEncounter: IMysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "berry_juice",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
scale: 1.5,
|
||||
x: -15,
|
||||
y: 3,
|
||||
yShadow: 0
|
||||
},
|
||||
{
|
||||
spriteKey: Species.SHUCKLE.toString(),
|
||||
fileRoot: "pokemon",
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
scale: 1.5,
|
||||
x: 20,
|
||||
y: 10,
|
||||
yShadow: 7
|
||||
},
|
||||
]) // Set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}:intro`,
|
||||
},
|
||||
])
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
|
||||
// Calculate boss mon
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
disableSwitch: true,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
spriteScale: 1.5,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||
modifierTypes: [
|
||||
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType,
|
||||
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.APICOT]).type as PokemonHeldItemModifierType,
|
||||
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.GANLON]).type as PokemonHeldItemModifierType,
|
||||
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.LUM]).type as PokemonHeldItemModifierType,
|
||||
generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.LUM]).type as PokemonHeldItemModifierType
|
||||
],
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}:option:2:stat_boost`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], 2));
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
initCustomMovesForEncounter(scene, [Moves.GASTRO_ACID, Moves.STEALTH_ROCK]);
|
||||
|
||||
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) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
// Do blackout and hide intro visuals during blackout
|
||||
scene.time.delayedCall(750, () => {
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 50);
|
||||
});
|
||||
|
||||
// -20 to all base stats of highest BST, +10 to all base stats of rest of party
|
||||
// Get highest BST mon
|
||||
const party = scene.getParty();
|
||||
let highestBst: PlayerPokemon = null;
|
||||
let statTotal = 0;
|
||||
for (const pokemon of party) {
|
||||
if (!highestBst) {
|
||||
highestBst = pokemon;
|
||||
statTotal = pokemon.getSpeciesForm().getBaseStatTotal();
|
||||
continue;
|
||||
}
|
||||
|
||||
const total = pokemon.getSpeciesForm().getBaseStatTotal();
|
||||
if (total > statTotal) {
|
||||
highestBst = pokemon;
|
||||
statTotal = total;
|
||||
}
|
||||
}
|
||||
|
||||
if (!highestBst) {
|
||||
highestBst = party[0];
|
||||
}
|
||||
|
||||
modifyPlayerPokemonBST(highestBst, -20);
|
||||
for (const pokemon of party) {
|
||||
if (highestBst.id === pokemon.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
modifyPlayerPokemonBST(pokemon, 10);
|
||||
}
|
||||
|
||||
encounter.setDialogueToken("highBstPokemon", highestBst.name);
|
||||
await showEncounterText(scene, `${namespace}:option:1:selected_2`, null, true);
|
||||
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}:option:2:label`,
|
||||
buttonTooltip: `${namespace}:option:2:tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:2:selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Pick battle
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW], fillRemaining: true });
|
||||
encounter.startOfBattleEffects.push(
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.GASTRO_ACID),
|
||||
ignorePp: true
|
||||
},
|
||||
{
|
||||
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||
targets: [BattlerIndex.PLAYER],
|
||||
move: new PokemonMove(Moves.STEALTH_ROCK),
|
||||
ignorePp: true
|
||||
});
|
||||
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
}
|
||||
)
|
||||
.build();
|
|
@ -40,7 +40,7 @@ export const TrainingSessionEncounter: IMysteryEncounter =
|
|||
hasShadow: true,
|
||||
y: 6,
|
||||
x: 5,
|
||||
yShadowOffset: -2
|
||||
yShadow: -2
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
|
|
|
@ -13,6 +13,7 @@ import { TrainingSessionEncounter } from "./encounters/training-session-encounte
|
|||
import IMysteryEncounter from "./mystery-encounter";
|
||||
import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter";
|
||||
import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter";
|
||||
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
|
||||
|
||||
// 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;
|
||||
|
@ -181,7 +182,9 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
|
|||
[Biome.SEABED, []],
|
||||
[Biome.MOUNTAIN, []],
|
||||
[Biome.BADLANDS, []],
|
||||
[Biome.CAVE, []],
|
||||
[Biome.CAVE, [
|
||||
MysteryEncounterType.THE_STRONG_STUFF
|
||||
]],
|
||||
[Biome.DESERT, []],
|
||||
[Biome.ICE_CAVE, []],
|
||||
[Biome.MEADOW, []],
|
||||
|
@ -221,6 +224,7 @@ export function initMysteryEncounters() {
|
|||
allMysteryEncounters[MysteryEncounterType.SAFARI_ZONE] = SafariZoneEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.LOST_AT_SEA] = LostAtSeaEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.THE_STRONG_STUFF] = TheStrongStuffEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
|
|
@ -2,9 +2,8 @@ import { BattlerIndex, BattleType } from "#app/battle";
|
|||
import { biomeLinks } from "#app/data/biomes";
|
||||
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
import { queueEncounterMessage, 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 { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier";
|
||||
import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
||||
import * as Overrides from "#app/overrides";
|
||||
|
@ -27,6 +26,7 @@ import { Status, StatusEffect } from "../../status-effect";
|
|||
import { TrainerConfig, trainerConfigs, TrainerSlot } from "../../trainer-config";
|
||||
import { MysteryEncounterVariant } from "../mystery-encounter";
|
||||
import { Gender } from "#app/data/gender";
|
||||
import { Nature } from "#app/data/nature";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
|
||||
|
||||
|
@ -54,7 +54,7 @@ export function doTrainerExclamation(scene: BattleScene) {
|
|||
}
|
||||
});
|
||||
|
||||
scene.playSound("GEN8- Exclaim.wav", { volume: 0.8 });
|
||||
scene.playSound("GEN8- Exclaim.wav", { volume: 0.7 });
|
||||
}
|
||||
|
||||
export interface EnemyPokemonConfig {
|
||||
|
@ -62,11 +62,14 @@ export interface EnemyPokemonConfig {
|
|||
isBoss: boolean;
|
||||
bossSegments?: number;
|
||||
bossSegmentModifier?: number; // Additive to the determined segment number
|
||||
spriteScale?: number;
|
||||
formIndex?: number;
|
||||
level?: number;
|
||||
gender?: Gender;
|
||||
passive?: boolean;
|
||||
moveSet?: Moves[];
|
||||
nature?: Nature;
|
||||
ivs?: [integer, integer, integer, integer, integer, integer];
|
||||
/** Can set just the status, or pass a timer on the status turns */
|
||||
status?: StatusEffect | [StatusEffect, number];
|
||||
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
|
||||
|
@ -196,6 +199,13 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||
enemyPokemon.formIndex = config.formIndex;
|
||||
}
|
||||
|
||||
// Set scale
|
||||
if (!isNullOrUndefined(config.spriteScale)) {
|
||||
enemyPokemon.mysteryEncounterData = {
|
||||
spriteScale: config.spriteScale
|
||||
};
|
||||
}
|
||||
|
||||
// Set Boss
|
||||
if (config.isBoss) {
|
||||
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true);
|
||||
|
@ -206,12 +216,22 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||
}
|
||||
|
||||
// Set Passive
|
||||
if (partyConfig.pokemonConfigs[e].passive) {
|
||||
if (config.passive) {
|
||||
enemyPokemon.passive = true;
|
||||
}
|
||||
|
||||
// Set Nature
|
||||
if (config.nature) {
|
||||
enemyPokemon.nature = config.nature;
|
||||
}
|
||||
|
||||
// Set IVs
|
||||
if (config.ivs) {
|
||||
enemyPokemon.ivs = config.ivs;
|
||||
}
|
||||
|
||||
// Set Status
|
||||
const statusEffects = partyConfig.pokemonConfigs[e].status;
|
||||
const statusEffects = config.status;
|
||||
if (statusEffects) {
|
||||
// Default to cureturn 3 for sleep
|
||||
const status = Array.isArray(statusEffects) ? statusEffects[0] : statusEffects;
|
||||
|
@ -811,65 +831,3 @@ export function calculateMEAggregateStats(scene: BattleScene, baseSpawnWeight: n
|
|||
|
||||
console.log(`Starting weight: ${baseSpawnWeight}\nAverage MEs per run: ${totalMean}\nStandard Deviation: ${totalStd}\nAvg Commons: ${commonMean}\nAvg Uncommons: ${uncommonMean}\nAvg Rares: ${rareMean}\nAvg Super Rares: ${superRareMean}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of handling player pokemon KO (with all its side effects)
|
||||
*
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to KO
|
||||
*/
|
||||
export function koPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon) {
|
||||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
pokemon.updateInfo();
|
||||
queueEncounterMessage(scene, i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying hp changes to a player pokemon.
|
||||
* Takes care of not going below `0`, above max-hp, adding `FNT` status correctly and updating the pokemon info.
|
||||
* TODO: handle special cases like wonder-guard/ninjask
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply the hp change to
|
||||
* @param value the hp change amount. Positive for heal. Negative for damage
|
||||
*
|
||||
*/
|
||||
function applyHpChangeToPokemon(scene: BattleScene, pokemon: PlayerPokemon, value: number) {
|
||||
const hpChange = Math.round(pokemon.hp + value);
|
||||
const nextHp = Math.max(Math.min(hpChange, pokemon.getMaxHp()), 0);
|
||||
if (nextHp === 0) {
|
||||
koPlayerPokemon(scene, pokemon);
|
||||
} else {
|
||||
pokemon.hp = nextHp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying damage to a player pokemon
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply damage to
|
||||
* @param damage the amount of damage to apply
|
||||
* @see {@linkcode applyHpChangeToPokemon}
|
||||
*/
|
||||
export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon, damage: number) {
|
||||
if (damage <= 0) {
|
||||
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
||||
}
|
||||
|
||||
applyHpChangeToPokemon(scene, pokemon, -damage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying heal to a player pokemon
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply heal to
|
||||
* @param heal the amount of heal to apply
|
||||
* @see {@linkcode applyHpChangeToPokemon}
|
||||
*/
|
||||
export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, heal: number) {
|
||||
if (heal <= 0) {
|
||||
console.warn("Damaging pokemong with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
|
||||
}
|
||||
|
||||
applyHpChangeToPokemon(scene, pokemon, heal);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,12 @@ import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
|||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { 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";
|
||||
|
||||
export interface MysteryEncounterPokemonData {
|
||||
spriteScale?: number
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -131,10 +136,81 @@ export function getRandomSpeciesByStarterTier(starterTiers: number | [number, nu
|
|||
return Species.BULBASAUR;
|
||||
}
|
||||
|
||||
export function koPlayerPokemon(pokemon: PlayerPokemon) {
|
||||
/**
|
||||
* Takes care of handling player pokemon KO (with all its side effects)
|
||||
*
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to KO
|
||||
*/
|
||||
export function koPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon) {
|
||||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
pokemon.updateInfo();
|
||||
queueEncounterMessage(scene, i18next.t("battle:fainted", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying hp changes to a player pokemon.
|
||||
* Takes care of not going below `0`, above max-hp, adding `FNT` status correctly and updating the pokemon info.
|
||||
* TODO: handle special cases like wonder-guard/ninjask
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply the hp change to
|
||||
* @param value the hp change amount. Positive for heal. Negative for damage
|
||||
*
|
||||
*/
|
||||
function applyHpChangeToPokemon(scene: BattleScene, pokemon: PlayerPokemon, value: number) {
|
||||
const hpChange = Math.round(pokemon.hp + value);
|
||||
const nextHp = Math.max(Math.min(hpChange, pokemon.getMaxHp()), 0);
|
||||
if (nextHp === 0) {
|
||||
koPlayerPokemon(scene, pokemon);
|
||||
} else {
|
||||
pokemon.hp = nextHp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying damage to a player pokemon
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply damage to
|
||||
* @param damage the amount of damage to apply
|
||||
* @see {@linkcode applyHpChangeToPokemon}
|
||||
*/
|
||||
export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon, damage: number) {
|
||||
if (damage <= 0) {
|
||||
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
||||
}
|
||||
|
||||
applyHpChangeToPokemon(scene, pokemon, -damage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles applying heal to a player pokemon
|
||||
* @param scene the battle scene
|
||||
* @param pokemon the player pokemon to apply heal to
|
||||
* @param heal the amount of heal to apply
|
||||
* @see {@linkcode applyHpChangeToPokemon}
|
||||
*/
|
||||
export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, heal: number) {
|
||||
if (heal <= 0) {
|
||||
console.warn("Damaging pokemong with `applyHealToPokemon` is not recommended! Please use `applyDamageToPokemon` instead.");
|
||||
}
|
||||
|
||||
applyHpChangeToPokemon(scene, pokemon, heal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will modify all of a Pokemon's base stats by a flat value
|
||||
* Base stats can never go below 1
|
||||
* @param pokemon
|
||||
* @param value
|
||||
*/
|
||||
export function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
||||
pokemon.getSpeciesForm().baseStats = [...pokemon.getSpeciesForm().baseStats].map(v => {
|
||||
const newVal = Math.floor(v + value);
|
||||
return Math.max(newVal, 1);
|
||||
});
|
||||
pokemon.calculateStats();
|
||||
pokemon.updateInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -221,6 +221,14 @@ export abstract class PokemonSpeciesForm {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the BST for the species
|
||||
* @returns The species' BST.
|
||||
*/
|
||||
getBaseStatTotal(): integer {
|
||||
return this.baseStats.reduce((i, n) => n + i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the species' base stat amount for the given stat.
|
||||
* @param stat The desired stat.
|
||||
|
|
|
@ -10,5 +10,6 @@ export enum MysteryEncounterType {
|
|||
FIELD_TRIP,
|
||||
SAFARI_ZONE,
|
||||
LOST_AT_SEA, //might be generalized later on
|
||||
FIERY_FALLOUT
|
||||
FIERY_FALLOUT,
|
||||
THE_STRONG_STUFF
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ export class MysteryEncounterSpriteConfig {
|
|||
/** Y offset */
|
||||
y?: number;
|
||||
/** Y shadow offset */
|
||||
yShadowOffset?: number;
|
||||
yShadow?: number;
|
||||
/** Sprite scale. `0` - `n` */
|
||||
scale?: number;
|
||||
/** If you are using an item sprite, set to `true` */
|
||||
|
@ -72,10 +72,10 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
return;
|
||||
}
|
||||
|
||||
const getSprite = (spriteKey: string, hasShadow?: boolean, yShadowOffset?: number) => {
|
||||
const getSprite = (spriteKey: string, hasShadow?: boolean, yShadow?: number) => {
|
||||
const ret = this.scene.addFieldSprite(0, 0, spriteKey);
|
||||
ret.setOrigin(0.5, 1);
|
||||
ret.setPipeline(this.scene.spritePipeline, { tone: [0.0, 0.0, 0.0, 0.0], hasShadow: !!hasShadow, yShadowOffset: yShadowOffset ?? 0 });
|
||||
ret.setPipeline(this.scene.spritePipeline, { tone: [0.0, 0.0, 0.0, 0.0], hasShadow: !!hasShadow, yShadowOffset: yShadow ?? 0 });
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
@ -94,13 +94,13 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
||||
|
||||
this.spriteConfigs?.forEach((config) => {
|
||||
const { spriteKey, isItem, hasShadow, scale, x, y, yShadowOffset, alpha } = config;
|
||||
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha } = config;
|
||||
|
||||
let sprite: GameObjects.Sprite;
|
||||
let tintSprite: GameObjects.Sprite;
|
||||
|
||||
if (!isItem) {
|
||||
sprite = getSprite(spriteKey, hasShadow, yShadowOffset);
|
||||
sprite = getSprite(spriteKey, hasShadow, yShadow);
|
||||
tintSprite = getSprite(spriteKey);
|
||||
} else {
|
||||
sprite = getItemSprite(spriteKey);
|
||||
|
|
|
@ -50,6 +50,7 @@ import { BerryType } from "#enums/berry-type";
|
|||
import { Biome } from "#enums/biome";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
export enum FieldPosition {
|
||||
CENTER,
|
||||
|
@ -100,6 +101,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
public battleData: PokemonBattleData;
|
||||
public battleSummonData: PokemonBattleSummonData;
|
||||
public turnData: PokemonTurnData;
|
||||
public mysteryEncounterData: MysteryEncounterPokemonData;
|
||||
|
||||
/** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */
|
||||
public mysteryEncounterBattleEffects: (pokemon: Pokemon) => void = null;
|
||||
|
@ -524,6 +526,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
const formKey = this.getFormKey();
|
||||
if (formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1 || formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1) {
|
||||
return 1.5;
|
||||
} else if (this?.mysteryEncounterData?.spriteScale) {
|
||||
return this.mysteryEncounterData.spriteScale;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { safariZoneDialogue } from "#app/locales/en/mystery-encounters/safari-zo
|
|||
import { shadyVitaminDealerDialogue } from "#app/locales/en/mystery-encounters/shady-vitamin-dealer-dialogue";
|
||||
import { slumberingSnorlaxDialogue } from "#app/locales/en/mystery-encounters/slumbering-snorlax-dialogue";
|
||||
import { trainingSessionDialogue } from "#app/locales/en/mystery-encounters/training-session-dialogue";
|
||||
import { theStrongStuffDialogue } from "#app/locales/en/mystery-encounters/the-strong-stuff-dialogue";
|
||||
|
||||
/**
|
||||
* Patterns that can be used:
|
||||
|
@ -44,4 +45,5 @@ export const mysteryEncounter = {
|
|||
safariZone: safariZoneDialogue,
|
||||
lostAtSea: lostAtSeaDialogue,
|
||||
fieryFallout: fieryFalloutDialogue,
|
||||
theStrongStuff: theStrongStuffDialogue,
|
||||
} as const;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
export const theStrongStuffDialogue = {
|
||||
intro: "It's a massive Shuckle and what appears\nto be an equally large stash of... juice?",
|
||||
title: "The Strong Stuff",
|
||||
description: "The Shuckle that blocks your path looks incredibly strong. Meanwhile, the juice next to it is emanating power of some kind.\n\nThe Shuckle extends its feelers in your direction. It seems like it wants to touch you, but is that really a good idea?",
|
||||
query: "What will you do?",
|
||||
option: {
|
||||
1: {
|
||||
label: "Let it touch you",
|
||||
tooltip: "(?) Something awful or amazing might happen",
|
||||
selected: "You black out.",
|
||||
selected_2: `@f{150}When you awaken, the Shuckle is gone\nand juice stash completely drained.
|
||||
$Your {{highBstPokemon}} feels a\nterrible lethargy come over it!
|
||||
$It's base stats were reduced by 20 in each stat!
|
||||
$Your remaining Pokémon feel an incredible vigor, though!
|
||||
$Their base stats are increased by 10 in each stat!`
|
||||
},
|
||||
2: {
|
||||
label: "Battle the Shuckle",
|
||||
tooltip: "(-) Hard Battle\n(+) Special Rewards",
|
||||
selected: "Enraged, the Shuckle drinks some of its juice and attacks!",
|
||||
stat_boost: "The Shuckle's juice boosts its stats!",
|
||||
},
|
||||
},
|
||||
outro: "What a bizarre turn of events."
|
||||
};
|
|
@ -1107,18 +1107,19 @@ export class EncounterPhase extends BattlePhase {
|
|||
|
||||
if (showEncounterMessage) {
|
||||
const introDialogue = this.scene.currentBattle.mysteryEncounter.dialogue.intro;
|
||||
const FIRST_DIALOGUE_PROMPT_DELAY = 750;
|
||||
let i = 0;
|
||||
const showNextDialogue = () => {
|
||||
const nextAction = i === introDialogue.length - 1 ? doShowEncounterOptions : showNextDialogue;
|
||||
const dialogue = introDialogue[i];
|
||||
const title = getEncounterText(this.scene, dialogue.speaker);
|
||||
const text = getEncounterText(this.scene, dialogue.text);
|
||||
if (title) {
|
||||
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 750 : 0);
|
||||
} else {
|
||||
this.scene.ui.showText(text, null, nextAction, i === 0 ? 750 : 0, true);
|
||||
}
|
||||
i++;
|
||||
if (title) {
|
||||
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0);
|
||||
} else {
|
||||
this.scene.ui.showText(text, null, nextAction, i === 1 ? FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
|
||||
}
|
||||
};
|
||||
|
||||
if (introDialogue.length > 0) {
|
||||
|
@ -1686,6 +1687,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
|
|||
pokemon.cry(pokemon.getHpRatio() > 0.25 ? undefined : { rate: 0.85 });
|
||||
pokemon.getSprite().clearTint();
|
||||
pokemon.resetSummonData();
|
||||
this.scene.updateFieldScale();
|
||||
this.scene.time.delayedCall(1000, () => this.end());
|
||||
}
|
||||
});
|
||||
|
|
|
@ -26,10 +26,9 @@ import { BattlerTagLapseType } from "#app/data/battler-tags";
|
|||
* - Queuing of the MysteryEncounterOptionSelectedPhase
|
||||
*/
|
||||
export class MysteryEncounterPhase extends Phase {
|
||||
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 300;
|
||||
optionSelectSettings: OptionSelectSettings;
|
||||
|
||||
private FIRST_DIALOGUE_PROMPT_DELAY = 300;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param scene
|
||||
|
@ -108,12 +107,12 @@ export class MysteryEncounterPhase extends Phase {
|
|||
title = getEncounterText(this.scene, dialogue.speaker);
|
||||
}
|
||||
|
||||
if (title) {
|
||||
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
|
||||
} else {
|
||||
this.scene.ui.showText(text, null, nextAction, i === 0 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
|
||||
}
|
||||
i++;
|
||||
if (title) {
|
||||
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
|
||||
} else {
|
||||
this.scene.ui.showText(text, null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
|
||||
}
|
||||
};
|
||||
|
||||
showNextDialogue();
|
||||
|
@ -420,6 +419,7 @@ export class MysteryEncounterRewardsPhase extends Phase {
|
|||
* - Queuing of the next wave
|
||||
*/
|
||||
export class PostMysteryEncounterPhase extends Phase {
|
||||
private readonly FIRST_DIALOGUE_PROMPT_DELAY = 750;
|
||||
onPostOptionSelect: OptionPhaseCallback;
|
||||
|
||||
constructor(scene: BattleScene) {
|
||||
|
@ -462,13 +462,13 @@ export class PostMysteryEncounterPhase extends Phase {
|
|||
title = getEncounterText(this.scene, dialogue.speaker);
|
||||
}
|
||||
|
||||
i++;
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
if (title) {
|
||||
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 0 ? 750 : 0);
|
||||
this.scene.ui.showDialogue(text, title, null, nextAction, 0, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0);
|
||||
} else {
|
||||
this.scene.ui.showText(text, null, nextAction, i === 0 ? 750 : 0, true);
|
||||
this.scene.ui.showText(text, null, nextAction, i === 1 ? this.FIRST_DIALOGUE_PROMPT_DELAY : 0, true);
|
||||
}
|
||||
i++;
|
||||
};
|
||||
|
||||
showNextDialogue();
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
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 * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { runSelectMysteryEncounterOption } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { SelectModifierPhase } from "#app/phases";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { DepartmentStoreSaleEncounter } from "#app/data/mystery-encounters/encounters/department-store-sale-encounter";
|
||||
import { CIVILIZATION_ENCOUNTER_BIOMES } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
|
||||
const namespace = "mysteryEncounter:departmentStoreSale";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.PLAINS;
|
||||
const defaultWave = 37;
|
||||
|
||||
describe("Department Store Sale - 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);
|
||||
game.override.disableTrainerWaves(true);
|
||||
|
||||
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.VOLCANO, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||
]);
|
||||
CIVILIZATION_ENCOUNTER_BIOMES.forEach(biome => {
|
||||
biomeMap.set(biome, [MysteryEncounterType.DEPARTMENT_STORE_SALE]);
|
||||
});
|
||||
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.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
|
||||
expect(DepartmentStoreSaleEncounter.encounterType).toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE);
|
||||
expect(DepartmentStoreSaleEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
|
||||
expect(DepartmentStoreSaleEncounter.dialogue).toBeDefined();
|
||||
expect(DepartmentStoreSaleEncounter.dialogue.intro).toStrictEqual([
|
||||
{ text: `${namespace}:intro` },
|
||||
{
|
||||
speaker: `${namespace}:speaker`,
|
||||
text: `${namespace}:intro_dialogue`,
|
||||
}
|
||||
]);
|
||||
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
|
||||
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
|
||||
expect(DepartmentStoreSaleEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
|
||||
expect(DepartmentStoreSaleEncounter.options.length).toBe(4);
|
||||
});
|
||||
|
||||
it("should not spawn outside of CIVILIZATION_ENCOUNTER_BIOMES", async () => {
|
||||
game.override.startingBiome(Biome.VOLCANO);
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DEPARTMENT_STORE_SALE);
|
||||
});
|
||||
|
||||
it("should not run above wave 179", async () => {
|
||||
game.override.startingWave(181);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("Option 1 - TM Shop", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DepartmentStoreSaleEncounter.options[0];
|
||||
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:1:label`,
|
||||
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have shop with only TMs", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
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(4);
|
||||
for (const option of modifierSelectHandler.options) {
|
||||
expect(option.modifierTypeOption.type.id).toContain("TM_");
|
||||
}
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Vitamin Shop", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DepartmentStoreSaleEncounter.options[1];
|
||||
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:2:label`,
|
||||
buttonTooltip: `${namespace}:option:2:tooltip`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have shop with only Vitamins", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(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(3);
|
||||
for (const option of modifierSelectHandler.options) {
|
||||
expect(option.modifierTypeOption.type.id.includes("PP_UP") ||
|
||||
option.modifierTypeOption.type.id.includes("BASE_STAT_BOOSTER")).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 3 - X Item Shop", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DepartmentStoreSaleEncounter.options[2];
|
||||
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:3:label`,
|
||||
buttonTooltip: `${namespace}:option:3:tooltip`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have shop with only X Items", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
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(5);
|
||||
for (const option of modifierSelectHandler.options) {
|
||||
expect(option.modifierTypeOption.type.id.includes("DIRE_HIT") ||
|
||||
option.modifierTypeOption.type.id.includes("TEMP_STAT_BOOSTER")).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 3);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 4 - Pokeball Shop", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = DepartmentStoreSaleEncounter.options[3];
|
||||
expect(option.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:4:label`,
|
||||
buttonTooltip: `${namespace}:option:4:tooltip`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should have shop with only Pokeballs", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 4);
|
||||
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(4);
|
||||
for (const option of modifierSelectHandler.options) {
|
||||
expect(option.modifierTypeOption.type.id).toContain("BALL");
|
||||
}
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.DEPARTMENT_STORE_SALE, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 4);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:fieryFallout";
|
|||
/** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */
|
||||
const defaultParty = [Species.ARCANINE, Species.NINETALES, Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.VOLCANO;
|
||||
const defaultWave = 45;
|
||||
const defaultWave = 56;
|
||||
|
||||
describe("Fiery Fallout - Mystery Encounter", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
|
@ -42,7 +42,6 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
game.override.disableTrainerWave(true);
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
|
@ -54,10 +53,11 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.FIERY_FALLOUT);
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty);
|
||||
|
||||
expect(FieryFalloutEncounter.encounterType).toBe(MysteryEncounterType.FIERY_FALLOUT);
|
||||
|
@ -74,7 +74,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
game.override.startingBiome(Biome.MOUNTAIN);
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
|
||||
});
|
||||
|
||||
it("should not run below wave 41", async () => {
|
||||
|
@ -82,7 +82,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle.mysteryEncounter.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.FIERY_FALLOUT);
|
||||
});
|
||||
|
||||
it("should not run above wave 179", async () => {
|
||||
|
@ -96,7 +96,7 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
it("should initialize fully ", async () => {
|
||||
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: FieryFalloutEncounter } as Battle);
|
||||
const weatherSpy = vi.spyOn(scene.arena, "trySetWeather").mockReturnValue(true);
|
||||
const moveInitSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||
const moveInitSpy = vi.spyOn(BattleAnims, "initMoveAnim");
|
||||
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||
|
||||
const { onInit } = FieryFalloutEncounter;
|
||||
|
@ -130,10 +130,6 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
});
|
||||
|
||||
describe("Option 1 - Fight 2 Volcarona", () => {
|
||||
beforeEach(async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.FIERY_FALLOUT);
|
||||
});
|
||||
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = FieryFalloutEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
|
@ -180,14 +176,10 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
&& (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[];
|
||||
const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal");
|
||||
expect(charcoal).toBeDefined;
|
||||
}, 100000000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Suffer the weather", () => {
|
||||
beforeEach(async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.FIERY_FALLOUT);
|
||||
});
|
||||
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = FieryFalloutEncounter.options[1];
|
||||
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
|
@ -235,10 +227,6 @@ describe("Fiery Fallout - Mystery Encounter", () => {
|
|||
});
|
||||
|
||||
describe("Option 3 - use FIRE types", () => {
|
||||
beforeEach(async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.FIERY_FALLOUT);
|
||||
});
|
||||
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = FieryFalloutEncounter.options[2];
|
||||
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_SPECIAL);
|
||||
|
|
|
@ -32,7 +32,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
|||
game.override.mysteryEncounterChance(100);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
game.override.disableTrainerWave(true);
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
|
@ -44,10 +43,11 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
|||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.LOST_AT_SEA);
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.LOST_AT_SEA, defaultParty);
|
||||
|
||||
expect(LostAtSeaEncounter.encounterType).toBe(MysteryEncounterType.LOST_AT_SEA);
|
||||
|
@ -99,10 +99,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
|||
});
|
||||
|
||||
describe("Option 1 - Surf", () => {
|
||||
beforeEach(async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.LOST_AT_SEA);
|
||||
});
|
||||
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = LostAtSeaEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(EncounterOptionMode.DISABLED_OR_DEFAULT);
|
||||
|
@ -149,10 +145,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
|||
});
|
||||
|
||||
describe("Option 2 - Fly", () => {
|
||||
beforeEach(async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.LOST_AT_SEA);
|
||||
});
|
||||
|
||||
it("should have the correct properties", () => {
|
||||
const option2 = LostAtSeaEncounter.options[1];
|
||||
|
||||
|
@ -202,10 +194,6 @@ describe("Lost at Sea - Mystery Encounter", () => {
|
|||
});
|
||||
|
||||
describe("Option 3 - Wander aimlessy", () => {
|
||||
beforeEach(async () => {
|
||||
game.override.mysteryEncounter(MysteryEncounterType.LOST_AT_SEA);
|
||||
});
|
||||
|
||||
it("should have the correct properties", () => {
|
||||
const option3 = LostAtSeaEncounter.options[2];
|
||||
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
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 Battle from "#app/battle";
|
||||
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 { EncounterOptionMode } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { CommandPhase, MovePhase, SelectModifierPhase } from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import * as Modifiers from "#app/modifier/modifier";
|
||||
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter";
|
||||
import { Nature } from "#app/data/nature";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
|
||||
const namespace = "mysteryEncounter:theStrongStuff";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultBiome = Biome.CAVE;
|
||||
const defaultWave = 45;
|
||||
|
||||
describe("The Strong Stuff - 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.THE_STRONG_STUFF]],
|
||||
[Biome.MOUNTAIN, [MysteryEncounterType.MYSTERIOUS_CHALLENGERS]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
|
||||
expect(TheStrongStuffEncounter.encounterType).toBe(MysteryEncounterType.THE_STRONG_STUFF);
|
||||
expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
|
||||
expect(TheStrongStuffEncounter.dialogue).toBeDefined();
|
||||
expect(TheStrongStuffEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}:intro` }]);
|
||||
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}:title`);
|
||||
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}:description`);
|
||||
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}:query`);
|
||||
expect(TheStrongStuffEncounter.options.length).toBe(2);
|
||||
});
|
||||
|
||||
it("should not spawn outside of CAVE biome", async () => {
|
||||
game.override.startingBiome(Biome.MOUNTAIN);
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_STRONG_STUFF);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_STRONG_STUFF);
|
||||
});
|
||||
|
||||
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 () => {
|
||||
vi.spyOn(scene, "currentBattle", "get").mockReturnValue({ mysteryEncounter: TheStrongStuffEncounter } as Battle);
|
||||
const moveInitSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||
const moveLoadSpy = vi.spyOn(BattleAnims, "loadMoveAnimAssets");
|
||||
|
||||
const { onInit } = TheStrongStuffEncounter;
|
||||
|
||||
expect(TheStrongStuffEncounter.onInit).toBeDefined();
|
||||
|
||||
const onInitResult = onInit(scene);
|
||||
|
||||
expect(TheStrongStuffEncounter.enemyPartyConfigs).toEqual([
|
||||
{
|
||||
levelAdditiveMultiplier: 1,
|
||||
disableSwitch: true,
|
||||
pokemonConfigs: [
|
||||
{
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
spriteScale: 1.5,
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||
modifierTypes: expect.any(Array),
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: expect.any(Function)
|
||||
}
|
||||
],
|
||||
}
|
||||
]);
|
||||
await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled());
|
||||
await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled());
|
||||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
describe("Option 1 - Power Swap BSTs", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = TheStrongStuffEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:1:label`,
|
||||
buttonTooltip: `${namespace}:option:1:tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:1:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should lower stats of highest BST and raise stats for rest of party", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
|
||||
const bstsPrior = scene.getParty().map(p => p.getSpeciesForm().getBaseStatTotal());
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
|
||||
const bstsAfter = scene.getParty().map(p => {
|
||||
return p.getSpeciesForm().getBaseStatTotal();
|
||||
});
|
||||
|
||||
expect(bstsAfter[0]).toEqual(bstsPrior[0] - 20 * 6);
|
||||
expect(bstsAfter[1]).toEqual(bstsPrior[1] + 10 * 6);
|
||||
expect(bstsAfter[2]).toEqual(bstsPrior[2] + 10 * 6);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 1);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - battle the Shuckle", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = TheStrongStuffEncounter.options[1];
|
||||
expect(option1.optionMode).toBe(EncounterOptionMode.DEFAULT);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}:option:2:label`,
|
||||
buttonTooltip: `${namespace}:option:2:tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}:option:2:selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should start battle against Shuckle", async () => {
|
||||
const phaseSpy = vi.spyOn(scene, "pushPhase");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 2, true);
|
||||
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||
expect(enemyField.length).toBe(1);
|
||||
expect(enemyField[0].species.speciesId).toBe(Species.SHUCKLE);
|
||||
expect(enemyField[0].summonData.battleStats).toEqual([0, 2, 0, 2, 0, 0, 0]);
|
||||
const shuckleItems = scene.getModifiers(Modifiers.BerryModifier, false);
|
||||
expect(shuckleItems.length).toBe(4);
|
||||
expect(shuckleItems.find(m => m.berryType === BerryType.SITRUS)?.stackCount).toBe(1);
|
||||
expect(shuckleItems.find(m => m.berryType === BerryType.GANLON)?.stackCount).toBe(1);
|
||||
expect(shuckleItems.find(m => m.berryType === BerryType.APICOT)?.stackCount).toBe(1);
|
||||
expect(shuckleItems.find(m => m.berryType === BerryType.LUM)?.stackCount).toBe(2);
|
||||
expect(enemyField[0].moveset).toEqual([new PokemonMove(Moves.INFESTATION), new PokemonMove(Moves.SALT_CURE), new PokemonMove(Moves.GASTRO_ACID), new PokemonMove(Moves.HEAL_ORDER)]);
|
||||
|
||||
// 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(2);
|
||||
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.GASTRO_ACID).length).toBe(1);
|
||||
expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.STEALTH_ROCK).length).toBe(1);
|
||||
});
|
||||
|
||||
it("should have Soul Dew in rewards", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
|
||||
await runSelectMysteryEncounterOption(game, 2, true);
|
||||
await skipBattleRunMysteryEncounterRewardsPhase(game);
|
||||
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(3);
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("SOUL_DEW");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -243,7 +243,7 @@ describe("Mystery Encounter Utils", () => {
|
|||
arceus.hp = 100;
|
||||
expect(arceus.isAllowedInBattle()).toBe(true);
|
||||
|
||||
koPlayerPokemon(arceus);
|
||||
koPlayerPokemon(scene, arceus);
|
||||
expect(arceus.isAllowedInBattle()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,8 +23,6 @@ describe("Mystery Encounters", () => {
|
|||
game = new GameManager(phaserGame);
|
||||
game.override.startingWave(11);
|
||||
game.override.mysteryEncounterChance(100);
|
||||
game.override.mysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||
game.override.disableTrainerWave(true);
|
||||
});
|
||||
|
||||
it("Spawns a mystery encounter", async () => {
|
||||
|
|
|
@ -28,7 +28,6 @@ describe("Mystery Encounter Phases", () => {
|
|||
game = new GameManager(phaserGame);
|
||||
game.override.startingWave(11);
|
||||
game.override.mysteryEncounterChance(100);
|
||||
game.override.mysteryEncounter(MysteryEncounterType.MYSTERIOUS_CHALLENGERS);
|
||||
// Seed guarantees wild encounter to be replaced by ME
|
||||
game.override.seed("test");
|
||||
});
|
||||
|
|
|
@ -38,6 +38,7 @@ import {MysteryEncounterPhase} from "#app/phases/mystery-encounter-phases";
|
|||
import { OverridesHelper } from "./overridesHelper";
|
||||
import { expect } from "vitest";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
|
||||
/**
|
||||
* Class to manage the game state and transitions between phases.
|
||||
|
@ -151,6 +152,11 @@ export default class GameManager {
|
|||
* @returns A promise that resolves when the EncounterPhase ends.
|
||||
*/
|
||||
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) {
|
||||
if (!isNullOrUndefined(encounterType)) {
|
||||
this.override.disableTrainerWaves(true);
|
||||
this.override.mysteryEncounter(encounterType);
|
||||
}
|
||||
|
||||
await this.runToTitle();
|
||||
|
||||
this.onNextPrompt("TitlePhase", Mode.TITLE, () => {
|
||||
|
@ -167,7 +173,7 @@ export default class GameManager {
|
|||
}, () => this.isCurrentPhase(MysteryEncounterPhase), true);
|
||||
|
||||
await this.phaseInterceptor.run(EncounterPhase);
|
||||
if (encounterType) {
|
||||
if (!isNullOrUndefined(encounterType)) {
|
||||
expect(this.scene.currentBattle?.mysteryEncounter?.encounterType).toBe(encounterType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import GameManager from "#test/utils/gameManager";
|
|||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import * as overrides from "#app/overrides";
|
||||
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import * as GameMode from "#app/game-mode";
|
||||
import { GameModes, getGameMode } from "#app/game-mode";
|
||||
|
||||
/**
|
||||
* Helper to handle overrides in tests
|
||||
|
@ -77,8 +79,13 @@ export class OverridesHelper {
|
|||
* @returns spy instance
|
||||
* @param disable - true
|
||||
*/
|
||||
disableTrainerWave(disable: boolean): MockInstance {
|
||||
const spy = vi.spyOn(this.game.scene.gameMode, "isWaveTrainer").mockReturnValue(!disable);
|
||||
disableTrainerWaves(disable: boolean): MockInstance {
|
||||
const realFn = getGameMode;
|
||||
const spy = vi.spyOn(GameMode, "getGameMode").mockImplementation((gameMode: GameModes) => {
|
||||
const mode = realFn(gameMode);
|
||||
mode.hasTrainers = !disable;
|
||||
return mode;
|
||||
});
|
||||
this.log(`Standard trainer waves are ${disable? "disabled" : "enabled"}!`);
|
||||
return spy;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,8 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
|
|||
const charVarMap = new Map<integer, string>();
|
||||
const delayMap = new Map<integer, integer>();
|
||||
const soundMap = new Map<integer, string>();
|
||||
const actionPattern = /@(c|d|s)\{(.*?)\}/;
|
||||
const fadeMap = new Map<integer, integer>();
|
||||
const actionPattern = /@(c|d|s|f)\{(.*?)\}/;
|
||||
let actionMatch: RegExpExecArray;
|
||||
while ((actionMatch = actionPattern.exec(text))) {
|
||||
switch (actionMatch[1]) {
|
||||
|
@ -45,6 +46,9 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
|
|||
case "s":
|
||||
soundMap.set(actionMatch.index, actionMatch[2]);
|
||||
break;
|
||||
case "f":
|
||||
fadeMap.set(actionMatch.index, parseInt(actionMatch[2]));
|
||||
break;
|
||||
}
|
||||
text = text.slice(0, actionMatch.index) + text.slice(actionMatch.index + actionMatch[2].length + 4);
|
||||
}
|
||||
|
@ -103,6 +107,7 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
|
|||
const charVar = charVarMap.get(charIndex);
|
||||
const charSound = soundMap.get(charIndex);
|
||||
const charDelay = delayMap.get(charIndex);
|
||||
const charFade = fadeMap.get(charIndex);
|
||||
this.message.setText(text.slice(0, charIndex));
|
||||
const advance = () => {
|
||||
if (charVar) {
|
||||
|
@ -134,6 +139,19 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
|
|||
advance();
|
||||
}
|
||||
});
|
||||
} else if (charFade) {
|
||||
this.textTimer.paused = true;
|
||||
this.scene.time.delayedCall(150, () => {
|
||||
this.scene.ui.fadeOut(750).then(() => {
|
||||
const delay = Utils.getFrameMs(charFade);
|
||||
this.scene.time.delayedCall(delay, () => {
|
||||
this.scene.ui.fadeIn(500).then(() => {
|
||||
this.textTimer.paused = false;
|
||||
advance();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
advance();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { isNullOrUndefined } from "../utils";
|
|||
import { getPokeballAtlasKey } from "../data/pokeball";
|
||||
import { OptionSelectSettings } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
|
||||
export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
private cursorContainer: Phaser.GameObjects.Container;
|
||||
|
@ -135,7 +136,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
// TODO: If we need to handle cancel option? Maybe default logic to leave/run from encounter idk
|
||||
}
|
||||
} else {
|
||||
switch (this.optionsContainer.list.length) {
|
||||
switch (this.optionsContainer.getAll()?.length) {
|
||||
default:
|
||||
case 3:
|
||||
success = this.handleTwoOptionMoveInput(button);
|
||||
break;
|
||||
|
@ -284,7 +286,7 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
this.viewPartyIndex = this.optionsContainer.length - 1;
|
||||
this.viewPartyIndex = this.optionsContainer.getAll()?.length - 1;
|
||||
|
||||
if (!this.cursorObj) {
|
||||
this.cursorObj = this.scene.add.image(0, 0, "cursor");
|
||||
|
@ -293,11 +295,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
|
||||
if (cursor === this.viewPartyIndex) {
|
||||
this.cursorObj.setPosition(246, -17);
|
||||
} else if (this.optionsContainer.length === 3) { // 2 Options
|
||||
} else if (this.optionsContainer.getAll()?.length === 3) { // 2 Options
|
||||
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 15);
|
||||
} else if (this.optionsContainer.length === 4) { // 3 Options
|
||||
} else if (this.optionsContainer.getAll()?.length === 4) { // 3 Options
|
||||
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 7 + (cursor > 1 ? 16 : 0));
|
||||
} else if (this.optionsContainer.length === 5) { // 4 Options
|
||||
} else if (this.optionsContainer.getAll()?.length === 5) { // 4 Options
|
||||
this.cursorObj.setPosition(-10.5 + (cursor % 2 === 1 ? 100 : 0), 7 + (cursor > 1 ? 16 : 0));
|
||||
}
|
||||
|
||||
|
@ -368,7 +370,11 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
|||
titleTextObject.setPosition(72 - titleTextObject.displayWidth / 2, 5.5);
|
||||
|
||||
// Rarity of encounter
|
||||
const ballType = getPokeballAtlasKey(mysteryEncounter.encounterTier as number);
|
||||
const index = mysteryEncounter.encounterTier === MysteryEncounterTier.COMMON ? 0 :
|
||||
mysteryEncounter.encounterTier === MysteryEncounterTier.GREAT ? 1 :
|
||||
mysteryEncounter.encounterTier === MysteryEncounterTier.ULTRA ? 2 :
|
||||
mysteryEncounter.encounterTier === MysteryEncounterTier.ROGUE ? 3 : 4;
|
||||
const ballType = getPokeballAtlasKey(index);
|
||||
this.rarityBall.setTexture("pb", ballType);
|
||||
|
||||
const descriptionTextObject = addBBCodeTextObject(this.scene, 6, 25, descriptionText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } });
|
||||
|
|
Loading…
Reference in New Issue