Merge pull request #145 from AsdarDevelops/weird-dream
Add Weird Dream encounter and slight reworks to Berries Abound/Fight or Flight
BIN
public/audio/bgm/mystery_encounter_weird_dream.mp3
Normal file
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 55 KiB |
BIN
public/images/items/old_gateau.png
Normal file
After Width: | Height: | Size: 240 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
41
public/images/mystery-encounters/girawitch.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "girawitch.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 46,
|
||||
"h": 76
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "0001.png",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 46,
|
||||
"h": 76
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 46,
|
||||
"h": 76
|
||||
},
|
||||
"frame": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 46,
|
||||
"h": 76
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "https://www.codeandweb.com/texturepacker",
|
||||
"version": "3.0",
|
||||
"smartupdate": "$TexturePacker:SmartUpdate:e68bbc186f511d505c53b2beec3c3741:7108795fc29d953a1d3729ad93d70936:1661aeeeb2f0e4561c644aff254770b3$"
|
||||
}
|
||||
}
|
BIN
public/images/mystery-encounters/girawitch.png
Normal file
After Width: | Height: | Size: 1021 B |
BIN
public/images/mystery-encounters/starry_background.png
Normal file
After Width: | Height: | Size: 10 KiB |
@ -6,8 +6,7 @@ import {
|
||||
leaveEncounterWithoutBattle, setEncounterExp,
|
||||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import {
|
||||
BerryModifierType,
|
||||
getPartyLuckValue,
|
||||
@ -21,17 +20,17 @@ import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { applyModifierTypeToPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { applyModifierTypeToPlayerPokemon, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Stat } from "#enums/stat";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:berriesAbound";
|
||||
@ -148,6 +147,7 @@ export const BerriesAboundEncounter: IMysteryEncounter =
|
||||
// Calculate boss mon
|
||||
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, scene.currentBattle.waveIndex, 0, getPartyLuckValue(scene.getParty()), true);
|
||||
const bossPokemon = new EnemyPokemon(scene, bossSpecies, scene.currentBattle.waveIndex, TrainerSlot.NONE, true, null);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveMultiplier: 1,
|
||||
pokemonConfigs: [{
|
||||
@ -159,11 +159,11 @@ export const BerriesAboundEncounter: IMysteryEncounter =
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Calculate the number of extra berries that player receives
|
||||
// 10-60 GREAT, 60-110 ULTRA, 110-160 ROGUE, 160-180 MASTER
|
||||
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
|
||||
const numBerries =
|
||||
scene.currentBattle.waveIndex > 160 ? 7
|
||||
: scene.currentBattle.waveIndex > 110 ? 5
|
||||
: scene.currentBattle.waveIndex > 60 ? 4 : 2;
|
||||
: scene.currentBattle.waveIndex > 120 ? 5
|
||||
: scene.currentBattle.waveIndex > 40 ? 4 : 2;
|
||||
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
|
||||
encounter.misc = { numBerries };
|
||||
|
||||
@ -178,15 +178,11 @@ export const BerriesAboundEncounter: IMysteryEncounter =
|
||||
isPokemon: true
|
||||
});
|
||||
|
||||
// If player has a stealing move, they succeed automatically
|
||||
encounter.options[1].meetsRequirements(scene);
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon;
|
||||
if (primaryPokemon) {
|
||||
// Use primaryPokemon to execute the thievery
|
||||
encounter.options[1].dialogue.buttonTooltip = `${namespace}.option.2.tooltip_special`;
|
||||
} else {
|
||||
encounter.options[1].dialogue.buttonTooltip = `${namespace}.option.2.tooltip`;
|
||||
}
|
||||
// Get fastest party pokemon for option 2
|
||||
const fastestPokemon = getHighestStatPlayerPokemon(scene, Stat.SPD, true);
|
||||
encounter.misc.fastestPokemon = fastestPokemon;
|
||||
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
||||
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
||||
|
||||
return true;
|
||||
})
|
||||
@ -232,17 +228,27 @@ export const BerriesAboundEncounter: IMysteryEncounter =
|
||||
)
|
||||
.withOption(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick steal
|
||||
// Pick race for berries
|
||||
const encounter = scene.currentBattle.mysteryEncounter;
|
||||
const fastestPokemon = encounter.misc.fastestPokemon;
|
||||
const enemySpeed = encounter.misc.enemySpeed;
|
||||
const speedDiff = fastestPokemon.getStat(Stat.SPD) / enemySpeed;
|
||||
const numBerries = encounter.misc.numBerries;
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
shopOptions.push(generateModifierTypeOption(scene, modifierTypes.BERRY));
|
||||
}
|
||||
|
||||
if (speedDiff < 1) {
|
||||
// Caught and attacked by boss, gets +1 to all stats at start of fight
|
||||
const doBerryRewards = async () => {
|
||||
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
@ -255,40 +261,35 @@ export const BerriesAboundEncounter: IMysteryEncounter =
|
||||
}
|
||||
};
|
||||
|
||||
const shopOptions: ModifierTypeOption[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// Generate shop berries
|
||||
shopOptions.push(generateModifierTypeOption(scene, modifierTypes.BERRY));
|
||||
}
|
||||
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, null, doBerryRewards);
|
||||
|
||||
// If player has a stealing move, they succeed automatically
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon;
|
||||
if (primaryPokemon) {
|
||||
// Use primaryPokemon to execute the thievery
|
||||
await showEncounterText(scene, `${namespace}.option.2.special_result`);
|
||||
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp, true);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
const roll = randSeedInt(16);
|
||||
if (roll > 6) {
|
||||
// Noticed and attacked by boss, gets +1 to all stats at start of fight (62.5%)
|
||||
const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
|
||||
config.pokemonConfigs[0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
config.pokemonConfigs[0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||
pokemon.scene.currentBattle.mysteryEncounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(pokemon));
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}option.2.boss_enraged`);
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
|
||||
};
|
||||
await showEncounterText(scene, `${namespace}.option.2.bad_result`);
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, null, doBerryRewards);
|
||||
await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
return;
|
||||
} else {
|
||||
// Steal item (37.5%)
|
||||
// Display result message then proceed to rewards
|
||||
await showEncounterText(scene, `${namespace}.option.2.good_result`);
|
||||
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 1
|
||||
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1)/0.1), numBerries), 1);
|
||||
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
||||
const doFasterBerryRewards = async () => {
|
||||
const berryText = numBerriesGrabbed + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
queueEncounterMessage(scene, i18next.t("battle:rewardGain", { modifierName: berryText }));
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
|
||||
for (let i = 0; i < numBerriesGrabbed; i++) {
|
||||
await tryGiveBerry(scene, fastestPokemon);
|
||||
}
|
||||
};
|
||||
|
||||
setEncounterExp(scene, fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp);
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, null, doFasterBerryRewards);
|
||||
await showEncounterText(scene, `${namespace}.option.2.selected`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
})
|
||||
@ -312,12 +313,23 @@ export const BerriesAboundEncounter: IMysteryEncounter =
|
||||
)
|
||||
.build();
|
||||
|
||||
async function tryGiveBerry(scene: BattleScene) {
|
||||
async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
|
||||
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
|
||||
const berry = generateModifierTypeOption(scene, modifierTypes.BERRY, [berryType]).type as BerryModifierType;
|
||||
|
||||
const party = scene.getParty();
|
||||
|
||||
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
|
||||
if (prioritizedPokemon) {
|
||||
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
|
||||
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the party until berry was successfully given
|
||||
for (const pokemon of party) {
|
||||
const heldBerriesOfType = scene.findModifier(m => m instanceof BerryModifier
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import {
|
||||
EnemyPartyConfig,
|
||||
@ -7,7 +6,7 @@ import {
|
||||
setEncounterRewards
|
||||
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
|
||||
import { EnemyPokemon } from "#app/field/pokemon";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import {
|
||||
getPartyLuckValue,
|
||||
@ -16,15 +15,10 @@ import {
|
||||
ModifierTypeOption,
|
||||
regenerateModifierPoolThresholds,
|
||||
} from "#app/modifier/modifier-type";
|
||||
import { StatChangePhase } from "#app/phases";
|
||||
import { randSeedInt } from "#app/utils";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MoveRequirement } from "../mystery-encounter-requirements";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
@ -68,17 +62,21 @@ export const FightOrFlightEncounter: IMysteryEncounter =
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
// Calculate item
|
||||
// 10-60 GREAT, 60-110 ULTRA, 110-160 ROGUE, 160-180 MASTER
|
||||
// 10-40 GREAT, 60-120 ULTRA, 120-160 ROGUE, 160-180 MASTER
|
||||
const tier =
|
||||
scene.currentBattle.waveIndex > 160
|
||||
? ModifierTier.MASTER
|
||||
: scene.currentBattle.waveIndex > 110
|
||||
: scene.currentBattle.waveIndex > 120
|
||||
? ModifierTier.ROGUE
|
||||
: scene.currentBattle.waveIndex > 60
|
||||
: scene.currentBattle.waveIndex > 40
|
||||
? ModifierTier.ULTRA
|
||||
: ModifierTier.GREAT;
|
||||
regenerateModifierPoolThresholds(scene.getParty(), ModifierPoolType.PLAYER, 0);
|
||||
const item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier] })[0];
|
||||
let item: ModifierTypeOption;
|
||||
// TMs excluded from possible rewards as they're too swingy in value for a singular item reward
|
||||
while (!item || item.type.id.includes("TM_")) {
|
||||
item = getPlayerModifierTypeOptions(1, scene.getParty(), [], { guaranteedModifierTiers: [tier], allowLuckUpgrades: false })[0];
|
||||
}
|
||||
encounter.setDialogueToken("itemName", item.type.name);
|
||||
encounter.misc = item;
|
||||
|
||||
@ -105,16 +103,6 @@ export const FightOrFlightEncounter: IMysteryEncounter =
|
||||
},
|
||||
];
|
||||
|
||||
// If player has a stealing move, they succeed automatically
|
||||
encounter.options[1].meetsRequirements(scene);
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon;
|
||||
if (primaryPokemon) {
|
||||
// Use primaryPokemon to execute the thievery
|
||||
encounter.options[1].dialogue.buttonTooltip = `${namespace}.option.2.tooltip_special`;
|
||||
} else {
|
||||
encounter.options[1].dialogue.buttonTooltip = `${namespace}.option.2.tooltip`;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
@ -140,11 +128,17 @@ export const FightOrFlightEncounter: IMysteryEncounter =
|
||||
)
|
||||
.withOption(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL)
|
||||
.withOptionMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
|
||||
.withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`
|
||||
}
|
||||
]
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Pick steal
|
||||
@ -152,34 +146,10 @@ export const FightOrFlightEncounter: IMysteryEncounter =
|
||||
const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption;
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false });
|
||||
|
||||
// If player has a stealing move, they succeed automatically
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon;
|
||||
if (primaryPokemon) {
|
||||
// Use primaryPokemon to execute the thievery
|
||||
await showEncounterText(scene, `${namespace}.option.2.special_result`);
|
||||
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp, true);
|
||||
const primaryPokemon = encounter.options[1].primaryPokemon;
|
||||
setEncounterExp(scene, primaryPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs[0].species.baseExp);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
const roll = randSeedInt(16);
|
||||
if (roll > 6) {
|
||||
// Noticed and attacked by boss, gets +1 to all stats at start of fight (62.5%)
|
||||
const config = scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
|
||||
config.pokemonConfigs[0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
config.pokemonConfigs[0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||
pokemon.scene.currentBattle.mysteryEncounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(pokemon));
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}option.2.boss_enraged`);
|
||||
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1));
|
||||
};
|
||||
await showEncounterText(scene, `${namespace}.option.2.bad_result`);
|
||||
await initBattleWithEnemyConfig(scene, config);
|
||||
} else {
|
||||
// Steal item (37.5%)
|
||||
// Display result message then proceed to rewards
|
||||
await showEncounterText(scene, `${namespace}.option.2.good_result`);
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
}
|
||||
})
|
||||
.build()
|
||||
)
|
||||
|
541
src/data/mystery-encounters/encounters/weird-dream-encounter.ts
Normal file
@ -0,0 +1,541 @@
|
||||
import { Type } from "#app/data/type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import { Species } from "#enums/species";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
|
||||
import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { IntegerHolder, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonBaseStatTotalModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { achvs } from "#app/system/achv";
|
||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { Stat } from "#app/data/pokemon-stat";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||
import { getLevelTotalExp } from "#app/data/exp";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
const namespace = "mysteryEncounter:weirdDream";
|
||||
|
||||
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, Urshifu, the Poison Chain trio, Ogerpon */
|
||||
const excludedPokemon = [
|
||||
Species.ETERNATUS,
|
||||
/** UBs */
|
||||
Species.NIHILEGO,
|
||||
Species.BUZZWOLE,
|
||||
Species.PHEROMOSA,
|
||||
Species.XURKITREE,
|
||||
Species.CELESTEELA,
|
||||
Species.KARTANA,
|
||||
Species.GUZZLORD,
|
||||
Species.POIPOLE,
|
||||
Species.NAGANADEL,
|
||||
Species.STAKATAKA,
|
||||
Species.BLACEPHALON,
|
||||
/** Paradox */
|
||||
Species.GREAT_TUSK,
|
||||
Species.SCREAM_TAIL,
|
||||
Species.BRUTE_BONNET,
|
||||
Species.FLUTTER_MANE,
|
||||
Species.SLITHER_WING,
|
||||
Species.SANDY_SHOCKS,
|
||||
Species.ROARING_MOON,
|
||||
Species.WALKING_WAKE,
|
||||
Species.GOUGING_FIRE,
|
||||
Species.RAGING_BOLT,
|
||||
Species.IRON_TREADS,
|
||||
Species.IRON_BUNDLE,
|
||||
Species.IRON_HANDS,
|
||||
Species.IRON_JUGULIS,
|
||||
Species.IRON_MOTH,
|
||||
Species.IRON_THORNS,
|
||||
Species.IRON_VALIANT,
|
||||
Species.IRON_LEAVES,
|
||||
Species.IRON_BOULDER,
|
||||
Species.IRON_CROWN,
|
||||
/** These are banned so they don't appear in the < 570 BST pool */
|
||||
Species.URSHIFU,
|
||||
Species.CUBCHOO,
|
||||
Species.OKIDOGI,
|
||||
Species.MUNKIDORI,
|
||||
Species.FEZANDIPITI,
|
||||
Species.OGERPON,
|
||||
Species.CALYREX,
|
||||
Species.TYPE_NULL,
|
||||
Species.TERAPAGOS,
|
||||
Species.COSMOG,
|
||||
Species.COSMOEM,
|
||||
];
|
||||
|
||||
/**
|
||||
* Weird Dream encounter.
|
||||
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/137 | GitHub Issue #137}
|
||||
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||
*/
|
||||
export const WeirdDreamEncounter: IMysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "girawitch",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 4
|
||||
},
|
||||
])
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
},
|
||||
])
|
||||
.withSceneWaveRangeRequirement(10, 180)
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3");
|
||||
return true;
|
||||
})
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
// Change the bgm
|
||||
scene.fadeOutBgm(3000, false);
|
||||
scene.time.delayedCall(3000, () => {
|
||||
scene.playBgm("mystery_encounter_weird_dream");
|
||||
});
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
new MysteryEncounterOptionBuilder()
|
||||
.withOptionMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
.withHasDexProgress(true)
|
||||
.withDialogue({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
}
|
||||
],
|
||||
})
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Play the animation as the player goes through the dialogue
|
||||
scene.time.delayedCall(1000, () => {
|
||||
doShowDreamBackground(scene);
|
||||
});
|
||||
|
||||
// Calculate all the newly transformed Pokemon and begin asset load
|
||||
const teamTransformations = getTeamTransformations(scene);
|
||||
const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets());
|
||||
scene.currentBattle.mysteryEncounter.misc = {
|
||||
teamTransformations,
|
||||
loadAssets
|
||||
};
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue
|
||||
const cutsceneDialoguePromise = showEncounterText(scene, `${namespace}.option.1.cutscene`);
|
||||
|
||||
// Change the entire player's party
|
||||
// Wait for all new Pokemon assets to be loaded before showing transformation animations
|
||||
await Promise.all(scene.currentBattle.mysteryEncounter.misc.loadAssets);
|
||||
const transformations = scene.currentBattle.mysteryEncounter.misc.teamTransformations;
|
||||
|
||||
// If there are 1-3 transformations, do them centered back to back
|
||||
// Otherwise, the first 3 transformations are executed side-by-side, then any remaining 1-3 transformations occur in those same respective positions
|
||||
if (transformations.length <= 3) {
|
||||
for (const transformation of transformations) {
|
||||
const pokemon1 = transformation.previousPokemon;
|
||||
const pokemon2 = transformation.newPokemon;
|
||||
|
||||
await doPokemonTransformationSequence(scene, pokemon1, pokemon2, TransformationScreenPosition.CENTER);
|
||||
}
|
||||
} else {
|
||||
await doSideBySideTransformations(scene, transformations);
|
||||
}
|
||||
|
||||
// Make sure player has finished cutscene dialogue
|
||||
await cutsceneDialoguePromise;
|
||||
|
||||
doHideDreamBackground(scene);
|
||||
await showEncounterText(scene, `${namespace}.option.1.dream_complete`);
|
||||
|
||||
await doNewTeamPostProcess(scene, transformations);
|
||||
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT]});
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withSimpleOption(
|
||||
{
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
},
|
||||
async (scene: BattleScene) => {
|
||||
// Reduce party levels by 20%
|
||||
for (const pokemon of scene.getParty()) {
|
||||
pokemon.level = Math.max(Math.ceil(0.8 * pokemon.level), 1);
|
||||
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
||||
pokemon.levelExp = 0;
|
||||
|
||||
pokemon.calculateStats();
|
||||
pokemon.updateInfo();
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
return true;
|
||||
}
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
text: `${namespace}.outro`
|
||||
}
|
||||
])
|
||||
.build();
|
||||
|
||||
interface PokemonTransformation {
|
||||
previousPokemon: PlayerPokemon;
|
||||
newSpecies: PokemonSpecies;
|
||||
newPokemon: PlayerPokemon;
|
||||
heldItems: PokemonHeldItemModifier[];
|
||||
}
|
||||
|
||||
function getTeamTransformations(scene: BattleScene): PokemonTransformation[] {
|
||||
const party = scene.getParty();
|
||||
// Removes all pokemon from the party
|
||||
const alreadyUsedSpecies: PokemonSpecies[] = [];
|
||||
const pokemonTransformations: PokemonTransformation[] = party.map(p => {
|
||||
return {
|
||||
previousPokemon: p
|
||||
} as PokemonTransformation;
|
||||
});
|
||||
|
||||
// Only 1 Pokemon can be transformed into BST higher than 600
|
||||
let hasPokemonBstHigherThan600 = false;
|
||||
// Only 1 other Pokemon can be transformed into BST between 570-600
|
||||
let hasPokemonBstBetween570And600 = false;
|
||||
|
||||
// First, roll 2 of the party members to new Pokemon at a +90 to +110 BST difference
|
||||
// Then, roll the remainder of the party members at a +40 to +50 BST difference
|
||||
const numPokemon = party.length;
|
||||
for (let i = 0; i < numPokemon; i++) {
|
||||
const removed = party[randSeedInt(party.length)];
|
||||
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
|
||||
pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
scene.removePokemonFromPlayerParty(removed, false);
|
||||
|
||||
const bst = getOriginalBst(scene, removed);
|
||||
let newBstRange;
|
||||
if (i < 2) {
|
||||
newBstRange = [90, 110];
|
||||
} else {
|
||||
newBstRange = [40, 50];
|
||||
}
|
||||
|
||||
const newSpecies = getTransformedSpecies(bst, newBstRange, hasPokemonBstHigherThan600, hasPokemonBstBetween570And600, alreadyUsedSpecies);
|
||||
|
||||
const newSpeciesBst = newSpecies.getBaseStatTotal();
|
||||
if (newSpeciesBst > 600) {
|
||||
hasPokemonBstHigherThan600 = true;
|
||||
}
|
||||
if (newSpeciesBst <= 600 && newSpeciesBst >= 570) {
|
||||
hasPokemonBstBetween570And600 = true;
|
||||
}
|
||||
|
||||
|
||||
pokemonTransformations[index].newSpecies = newSpecies;
|
||||
alreadyUsedSpecies.push(newSpecies);
|
||||
}
|
||||
|
||||
for (const transformation of pokemonTransformations) {
|
||||
const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount());
|
||||
const newPlayerPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined);
|
||||
transformation.newPokemon = newPlayerPokemon;
|
||||
scene.getParty().push(newPlayerPokemon);
|
||||
}
|
||||
|
||||
return pokemonTransformations;
|
||||
}
|
||||
|
||||
async function doNewTeamPostProcess(scene: BattleScene, transformations: PokemonTransformation[]) {
|
||||
let atLeastOneNewStarter = false;
|
||||
for (const transformation of transformations) {
|
||||
const previousPokemon = transformation.previousPokemon;
|
||||
const newPokemon = transformation.newPokemon;
|
||||
const speciesRootForm = newPokemon.species.getRootSpeciesId();
|
||||
|
||||
// Roll HA a second time
|
||||
if (newPokemon.species.abilityHidden) {
|
||||
const hiddenIndex = newPokemon.species.ability2 ? 2 : 1;
|
||||
if (newPokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new IntegerHolder(256);
|
||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
if (hasHiddenAbility) {
|
||||
newPokemon.abilityIndex = hiddenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Roll IVs a second time
|
||||
newPokemon.ivs = newPokemon.ivs.map(iv => {
|
||||
const newValue = randSeedInt(31);
|
||||
return newValue > iv ? newValue : iv;
|
||||
});
|
||||
|
||||
|
||||
// For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it
|
||||
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= 570 || newPokemon.isShiny()) {
|
||||
if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) {
|
||||
scene.validateAchv(achvs.HIDDEN_ABILITY);
|
||||
}
|
||||
|
||||
if (newPokemon.species.subLegendary) {
|
||||
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
|
||||
}
|
||||
|
||||
if (newPokemon.species.legendary) {
|
||||
scene.validateAchv(achvs.CATCH_LEGENDARY);
|
||||
}
|
||||
|
||||
if (newPokemon.species.mythical) {
|
||||
scene.validateAchv(achvs.CATCH_MYTHICAL);
|
||||
}
|
||||
|
||||
scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs);
|
||||
const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false);
|
||||
if (newStarterUnlocked) {
|
||||
atLeastOneNewStarter = true;
|
||||
queueEncounterMessage(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
|
||||
}
|
||||
}
|
||||
|
||||
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
|
||||
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
|
||||
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
|
||||
});
|
||||
|
||||
// For pokemon that the player owns (including ones just caught), gain a candy
|
||||
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
|
||||
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
|
||||
}
|
||||
|
||||
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move of the new species
|
||||
newPokemon.moveset = previousPokemon.moveset;
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves = speciesEggMoves[speciesRootForm];
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove = eggMoves[eggMoveIndex];
|
||||
if (newPokemon.moveset.length < 4) {
|
||||
newPokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
newPokemon.moveset[randSeedInt(4)] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
// For pokemon that the player owns (including ones just caught), unlock the egg move
|
||||
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
|
||||
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), eggMoveIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Randomize the second type of the pokemon
|
||||
// If the pokemon does not normally have a second type, it will gain 1
|
||||
const newTypes = [newPokemon.getTypes()[0]];
|
||||
let newType = randSeedInt(18) as Type;
|
||||
while (newType === newTypes[0]) {
|
||||
newType = randSeedInt(18) as Type;
|
||||
}
|
||||
newTypes.push(newType);
|
||||
if (!newPokemon.mysteryEncounterData) {
|
||||
newPokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, null, null, newTypes);
|
||||
} else {
|
||||
newPokemon.mysteryEncounterData.types = newTypes;
|
||||
}
|
||||
|
||||
for (const item of transformation.heldItems) {
|
||||
item.pokemonId = newPokemon.id;
|
||||
scene.addModifier(item, false, false, false, true);
|
||||
}
|
||||
|
||||
// Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP, lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||
if (newPokemon.getSpeciesForm().getBaseStatTotal() <= 600) {
|
||||
const stats: Stat[] = [Stat.HP];
|
||||
const baseStats = newPokemon.getSpeciesForm().baseStats.slice(0);
|
||||
// Attack or SpAtk
|
||||
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
||||
// Def or SpDef
|
||||
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
||||
// const mod = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().newModifier(newPokemon, 20, stats);
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(null, [20, stats]);
|
||||
const modifier = modType.newModifier(newPokemon);
|
||||
scene.addModifier(modifier);
|
||||
}
|
||||
|
||||
// Enable passive if previous had it
|
||||
newPokemon.passive = previousPokemon.passive;
|
||||
|
||||
newPokemon.calculateStats();
|
||||
newPokemon.initBattleInfo();
|
||||
}
|
||||
|
||||
// One random pokemon will get its passive unlocked
|
||||
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
|
||||
if (passiveDisabledPokemon?.length > 0) {
|
||||
passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)].passive = true;
|
||||
}
|
||||
|
||||
// If at least one new starter was unlocked, play 1 fanfare
|
||||
if (atLeastOneNewStarter) {
|
||||
scene.playSound("level_up_fanfare");
|
||||
}
|
||||
}
|
||||
|
||||
function getOriginalBst(scene: BattleScene, pokemon: Pokemon) {
|
||||
const baseStats = pokemon.getSpeciesForm().baseStats.slice(0);
|
||||
scene.applyModifiers(PokemonBaseStatTotalModifier, true, pokemon, baseStats);
|
||||
if (pokemon.fusionSpecies) {
|
||||
const fusionBaseStats = pokemon.getFusionSpeciesForm().baseStats;
|
||||
for (let s = 0; s < pokemon.stats.length; s++) {
|
||||
baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2);
|
||||
}
|
||||
} else if (scene.gameMode.isSplicedOnly) {
|
||||
for (let s = 0; s < pokemon.stats.length; s++) {
|
||||
baseStats[s] = Math.ceil(baseStats[s] / 2);
|
||||
}
|
||||
}
|
||||
return baseStats.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies {
|
||||
let newSpecies: PokemonSpecies;
|
||||
while (!newSpecies) {
|
||||
const bstCap = originalBst + bstSearchRange[1];
|
||||
const bstMin = Math.max(originalBst + bstSearchRange[0], 0);
|
||||
|
||||
// Get any/all species that fall within the Bst range requirements
|
||||
let validSpecies = allSpecies
|
||||
.filter(s => {
|
||||
const speciesBst = s.getBaseStatTotal();
|
||||
const bstInRange = speciesBst >= bstMin && speciesBst <= bstCap;
|
||||
// Checks that a Pokemon has not already been added in the +600 or 570-600 slots;
|
||||
const validBst = (!hasPokemonBstBetween570And600 || (speciesBst < 570 || speciesBst > 600)) &&
|
||||
(!hasPokemonBstHigherThan600 || speciesBst <= 600);
|
||||
return bstInRange && validBst && !excludedPokemon.includes(s.speciesId);
|
||||
});
|
||||
|
||||
// There must be at least 20 species available before it will choose one
|
||||
if (validSpecies?.length > 20) {
|
||||
validSpecies = randSeedShuffle(validSpecies);
|
||||
newSpecies = validSpecies.pop();
|
||||
while (alreadyUsedSpecies.includes(newSpecies)) {
|
||||
newSpecies = validSpecies.pop();
|
||||
}
|
||||
} else {
|
||||
// Expands search rand until a Pokemon is found
|
||||
bstSearchRange[0] -= 10;
|
||||
bstSearchRange[1] += 10;
|
||||
}
|
||||
}
|
||||
|
||||
return newSpecies;
|
||||
}
|
||||
|
||||
function doShowDreamBackground(scene: BattleScene) {
|
||||
const transformationContainer = scene.add.container(0, -scene.game.canvas.height / 6);
|
||||
transformationContainer.name = "Dream Background";
|
||||
|
||||
// In case it takes a bit for video to load
|
||||
const transformationStaticBg = scene.add.rectangle(0, 0, scene.game.canvas.width / 6, scene.game.canvas.height / 6, 0);
|
||||
transformationStaticBg.setName("Black Background");
|
||||
transformationStaticBg.setOrigin(0, 0);
|
||||
transformationContainer.add(transformationStaticBg);
|
||||
transformationStaticBg.setVisible(true);
|
||||
|
||||
const transformationVideoBg: Phaser.GameObjects.Video = scene.add.video(0, 0, "evo_bg").stop();
|
||||
transformationVideoBg.setLoop(true);
|
||||
transformationVideoBg.setOrigin(0, 0);
|
||||
transformationVideoBg.setScale(0.4359673025);
|
||||
transformationContainer.add(transformationVideoBg);
|
||||
|
||||
scene.fieldUI.add(transformationContainer);
|
||||
scene.fieldUI.bringToTop(transformationContainer);
|
||||
transformationVideoBg.play();
|
||||
|
||||
transformationContainer.setVisible(true);
|
||||
transformationContainer.alpha = 0;
|
||||
|
||||
scene.tweens.add({
|
||||
targets: transformationContainer,
|
||||
alpha: 1,
|
||||
duration: 3000,
|
||||
ease: "Sine.easeInOut"
|
||||
});
|
||||
}
|
||||
|
||||
function doHideDreamBackground(scene: BattleScene) {
|
||||
const transformationContainer = scene.fieldUI.getByName("Dream Background");
|
||||
|
||||
scene.tweens.add({
|
||||
targets: transformationContainer,
|
||||
alpha: 0,
|
||||
duration: 3000,
|
||||
ease: "Sine.easeInOut",
|
||||
onComplete: () => {
|
||||
scene.fieldUI.remove(transformationContainer, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doSideBySideTransformations(scene: BattleScene, transformations: PokemonTransformation[]) {
|
||||
return new Promise<void>(resolve => {
|
||||
const allTransformationPromises = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const delay = i * 4000;
|
||||
scene.time.delayedCall(delay, () => {
|
||||
const transformation = transformations[i];
|
||||
const pokemon1 = transformation.previousPokemon;
|
||||
const pokemon2 = transformation.newPokemon;
|
||||
const screenPosition = i as TransformationScreenPosition;
|
||||
|
||||
const transformationPromise = doPokemonTransformationSequence(scene, pokemon1, pokemon2, screenPosition)
|
||||
.then(() => {
|
||||
if (transformations.length > i + 3) {
|
||||
const nextTransformationAtPosition = transformations[i + 3];
|
||||
const nextPokemon1 = nextTransformationAtPosition.previousPokemon;
|
||||
const nextPokemon2 = nextTransformationAtPosition.newPokemon;
|
||||
|
||||
allTransformationPromises.push(doPokemonTransformationSequence(scene, nextPokemon1, nextPokemon2, screenPosition));
|
||||
}
|
||||
});
|
||||
allTransformationPromises.push(transformationPromise);
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for all transformations to be loaded into promise array
|
||||
const id = setInterval(checkAllPromisesExist, 500);
|
||||
async function checkAllPromisesExist() {
|
||||
if (allTransformationPromises.length === transformations.length) {
|
||||
clearInterval(id);
|
||||
await Promise.all(allTransformationPromises);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -24,6 +24,7 @@ import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/
|
||||
import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter";
|
||||
import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter";
|
||||
import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter";
|
||||
import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-encounter";
|
||||
|
||||
// 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;
|
||||
@ -165,7 +166,8 @@ const anyBiomeEncounters: MysteryEncounterType[] = [
|
||||
MysteryEncounterType.A_TRAINERS_TEST,
|
||||
MysteryEncounterType.TRASH_TO_TREASURE,
|
||||
MysteryEncounterType.BERRIES_ABOUND,
|
||||
MysteryEncounterType.CLOWNING_AROUND
|
||||
MysteryEncounterType.CLOWNING_AROUND,
|
||||
MysteryEncounterType.WEIRD_DREAM
|
||||
];
|
||||
|
||||
/**
|
||||
@ -267,6 +269,7 @@ export function initMysteryEncounters() {
|
||||
allMysteryEncounters[MysteryEncounterType.CLOWNING_AROUND] = ClowningAroundEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.PART_TIMER] = PartTimerEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.DANCING_LESSONS] = DancingLessonsEncounter;
|
||||
allMysteryEncounters[MysteryEncounterType.WEIRD_DREAM] = WeirdDreamEncounter;
|
||||
|
||||
// Add extreme encounters to biome map
|
||||
extremeBiomeEncounters.forEach(encounter => {
|
||||
|
@ -19,6 +19,7 @@ import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-enco
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { Gender } from "#app/data/gender";
|
||||
import { Stat } from "#enums/stat";
|
||||
|
||||
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): { spriteKey: string, fileRoot: string } {
|
||||
const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
|
||||
@ -84,6 +85,28 @@ export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: bool
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param stat - stat to search for
|
||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: Stat, unfainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon;
|
||||
party.every(p => {
|
||||
if (unfainted && p.isFainted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pokemon = pokemon ? pokemon.getStat(stat) < p?.getStat(stat) ? p : pokemon : p;
|
||||
return true;
|
||||
});
|
||||
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
|
@ -0,0 +1,330 @@
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getFrameMs } from "#app/utils";
|
||||
import { cos, sin } from "#app/field/anims";
|
||||
import { getTypeRgb } from "#app/data/type";
|
||||
|
||||
export enum TransformationScreenPosition {
|
||||
CENTER,
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates an "evolution-like" animation to transform a pokemon (presumably from the player's party) into a new one, not necessarily an evolution species.
|
||||
* @param scene
|
||||
* @param pokemon
|
||||
* @param transformedPokemon
|
||||
* @param screenPosition
|
||||
*/
|
||||
export function doPokemonTransformationSequence(scene: BattleScene, pokemon: PlayerPokemon, transformedPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) {
|
||||
return new Promise<void>(resolve => {
|
||||
const transformationContainer = scene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container;
|
||||
const transformationBaseBg = scene.add.image(0, 0, "default_bg");
|
||||
transformationBaseBg.setOrigin(0, 0);
|
||||
transformationBaseBg.setVisible(false);
|
||||
transformationContainer.add(transformationBaseBg);
|
||||
|
||||
let pokemonSprite: Phaser.GameObjects.Sprite;
|
||||
let pokemonTintSprite: Phaser.GameObjects.Sprite;
|
||||
let pokemonEvoSprite: Phaser.GameObjects.Sprite;
|
||||
let pokemonEvoTintSprite: Phaser.GameObjects.Sprite;
|
||||
|
||||
const xOffset = screenPosition === TransformationScreenPosition.CENTER ? 0 :
|
||||
screenPosition === TransformationScreenPosition.RIGHT ? 100 : -100;
|
||||
// Centered transformations occur at a lower y Position
|
||||
const yOffset = screenPosition !== TransformationScreenPosition.CENTER ? -15 : 0;
|
||||
|
||||
const getPokemonSprite = () => {
|
||||
const ret = scene.addPokemonSprite(pokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub");
|
||||
ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
|
||||
return ret;
|
||||
};
|
||||
|
||||
transformationContainer.add((pokemonSprite = getPokemonSprite()));
|
||||
transformationContainer.add((pokemonTintSprite = getPokemonSprite()));
|
||||
transformationContainer.add((pokemonEvoSprite = getPokemonSprite()));
|
||||
transformationContainer.add((pokemonEvoTintSprite = getPokemonSprite()));
|
||||
|
||||
pokemonSprite.setAlpha(0);
|
||||
pokemonTintSprite.setAlpha(0);
|
||||
pokemonTintSprite.setTintFill(0xFFFFFF);
|
||||
pokemonEvoSprite.setVisible(false);
|
||||
pokemonEvoTintSprite.setVisible(false);
|
||||
pokemonEvoTintSprite.setTintFill(0xFFFFFF);
|
||||
|
||||
[ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||
sprite.play(pokemon.getSpriteKey(true));
|
||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(pokemon.getTeraType()) });
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", pokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", pokemon.shiny);
|
||||
sprite.setPipelineData("variant", pokemon.variant);
|
||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||
if (pokemon.summonData?.speciesForm) {
|
||||
k += "Base";
|
||||
}
|
||||
sprite.pipelineData[k] = pokemon.getSprite().pipelineData[k];
|
||||
});
|
||||
});
|
||||
|
||||
[ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||
sprite.play(transformedPokemon.getSpriteKey(true));
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", transformedPokemon.shiny);
|
||||
sprite.setPipelineData("variant", transformedPokemon.variant);
|
||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||
if (transformedPokemon.summonData?.speciesForm) {
|
||||
k += "Base";
|
||||
}
|
||||
sprite.pipelineData[k] = transformedPokemon.getSprite().pipelineData[k];
|
||||
});
|
||||
});
|
||||
|
||||
scene.tweens.add({
|
||||
targets: pokemonSprite,
|
||||
alpha: 1,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: 2000,
|
||||
onComplete: () => {
|
||||
doSpiralUpward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
scene.tweens.addCounter({
|
||||
from: 0,
|
||||
to: 1,
|
||||
duration: 1000,
|
||||
onUpdate: t => {
|
||||
pokemonTintSprite.setAlpha(t.getValue());
|
||||
},
|
||||
onComplete: () => {
|
||||
pokemonSprite.setVisible(false);
|
||||
scene.time.delayedCall(700, () => {
|
||||
doArcDownward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
scene.time.delayedCall(1000, () => {
|
||||
pokemonEvoTintSprite.setScale(0.25);
|
||||
pokemonEvoTintSprite.setVisible(true);
|
||||
doCycle(scene, 2, 6, pokemonTintSprite, pokemonEvoTintSprite).then(success => {
|
||||
pokemonEvoSprite.setVisible(true);
|
||||
doCircleInward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
|
||||
scene.time.delayedCall(900, () => {
|
||||
scene.tweens.add({
|
||||
targets: pokemonEvoTintSprite,
|
||||
alpha: 0,
|
||||
duration: 1500,
|
||||
delay: 150,
|
||||
easing: "Sine.easeIn",
|
||||
onComplete: () => {
|
||||
scene.time.delayedCall(2500, () => {
|
||||
resolve();
|
||||
scene.tweens.add({
|
||||
targets: pokemonEvoSprite,
|
||||
alpha: 0,
|
||||
duration: 2000,
|
||||
delay: 150,
|
||||
easing: "Sine.easeIn",
|
||||
// onComplete: () => {
|
||||
// transformedPokemon.destroy();
|
||||
// }
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doSpiralUpward(scene: BattleScene, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
let f = 0;
|
||||
|
||||
scene.tweens.addCounter({
|
||||
repeat: 64,
|
||||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
if (f < 64) {
|
||||
if (!(f & 7)) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
doSpiralUpwardParticle(scene, (f & 120) * 2 + i * 64, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
}
|
||||
}
|
||||
f++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doArcDownward(scene: BattleScene, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
let f = 0;
|
||||
|
||||
scene.tweens.addCounter({
|
||||
repeat: 96,
|
||||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
if (f < 96) {
|
||||
if (f < 6) {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
doArcDownParticle(scene, i * 16, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
}
|
||||
}
|
||||
f++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doCycle(scene: BattleScene, l: number, lastCycle: integer, pokemonTintSprite, pokemonEvoTintSprite): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const isLastCycle = l === lastCycle;
|
||||
scene.tweens.add({
|
||||
targets: pokemonTintSprite,
|
||||
scale: 0.25,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: 500 / l,
|
||||
yoyo: !isLastCycle
|
||||
});
|
||||
scene.tweens.add({
|
||||
targets: pokemonEvoTintSprite,
|
||||
scale: 1,
|
||||
ease: "Cubic.easeInOut",
|
||||
duration: 500 / l,
|
||||
yoyo: !isLastCycle,
|
||||
onComplete: () => {
|
||||
if (l < lastCycle) {
|
||||
doCycle(scene, l + 0.5, lastCycle, pokemonTintSprite, pokemonEvoTintSprite).then(success => resolve(success));
|
||||
} else {
|
||||
pokemonTintSprite.setVisible(false);
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doCircleInward(scene: BattleScene, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
let f = 0;
|
||||
|
||||
scene.tweens.addCounter({
|
||||
repeat: 48,
|
||||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
if (!f) {
|
||||
for (let i = 0; i < 16; i++) {
|
||||
doCircleInwardParticle(scene, i * 16, 4, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
}
|
||||
} else if (f === 32) {
|
||||
for (let i = 0; i < 16; i++) {
|
||||
doCircleInwardParticle(scene, i * 16, 8, transformationBaseBg, transformationContainer, xOffset, yOffset);
|
||||
}
|
||||
}
|
||||
f++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doSpiralUpwardParticle(scene: BattleScene, trigIndex: integer, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const particle = scene.add.image(initialX, 0, "evo_sparkle");
|
||||
transformationContainer.add(particle);
|
||||
|
||||
let f = 0;
|
||||
let amp = 48;
|
||||
|
||||
const particleTimer = scene.tweens.addCounter({
|
||||
repeat: -1,
|
||||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
});
|
||||
|
||||
const updateParticle = () => {
|
||||
if (!f || particle.y > 8) {
|
||||
particle.setPosition(initialX, 88 - (f * f) / 80 + yOffset);
|
||||
particle.y += sin(trigIndex, amp) / 4;
|
||||
particle.x += cos(trigIndex, amp);
|
||||
particle.setScale(1 - (f / 80));
|
||||
trigIndex += 4;
|
||||
if (f & 1) {
|
||||
amp--;
|
||||
}
|
||||
f++;
|
||||
} else {
|
||||
particle.destroy();
|
||||
particleTimer.remove();
|
||||
}
|
||||
};
|
||||
|
||||
updateParticle();
|
||||
}
|
||||
|
||||
function doArcDownParticle(scene: BattleScene, trigIndex: integer, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const particle = scene.add.image(initialX, 0, "evo_sparkle");
|
||||
particle.setScale(0.5);
|
||||
transformationContainer.add(particle);
|
||||
|
||||
let f = 0;
|
||||
let amp = 8;
|
||||
|
||||
const particleTimer = scene.tweens.addCounter({
|
||||
repeat: -1,
|
||||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
});
|
||||
|
||||
const updateParticle = () => {
|
||||
if (!f || particle.y < 88) {
|
||||
particle.setPosition(initialX, 8 + (f * f) / 5 + yOffset);
|
||||
particle.y += sin(trigIndex, amp) / 4;
|
||||
particle.x += cos(trigIndex, amp);
|
||||
amp = 8 + sin(f * 4, 40);
|
||||
f++;
|
||||
} else {
|
||||
particle.destroy();
|
||||
particleTimer.remove();
|
||||
}
|
||||
};
|
||||
|
||||
updateParticle();
|
||||
}
|
||||
|
||||
function doCircleInwardParticle(scene: BattleScene, trigIndex: integer, speed: integer, transformationBaseBg, transformationContainer, xOffset: number, yOffset: number) {
|
||||
const initialX = transformationBaseBg.displayWidth / 2 + xOffset;
|
||||
const initialY = transformationBaseBg.displayHeight / 2 + yOffset;
|
||||
const particle = scene.add.image(initialX, initialY, "evo_sparkle");
|
||||
transformationContainer.add(particle);
|
||||
|
||||
let amp = 120;
|
||||
|
||||
const particleTimer = scene.tweens.addCounter({
|
||||
repeat: -1,
|
||||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
});
|
||||
|
||||
const updateParticle = () => {
|
||||
if (amp > 8) {
|
||||
particle.setPosition(initialX, initialY);
|
||||
particle.y += sin(trigIndex, amp);
|
||||
particle.x += cos(trigIndex, amp);
|
||||
amp -= speed;
|
||||
trigIndex += 4;
|
||||
} else {
|
||||
particle.destroy();
|
||||
particleTimer.remove();
|
||||
}
|
||||
};
|
||||
|
||||
updateParticle();
|
||||
}
|
@ -21,5 +21,6 @@ export enum MysteryEncounterType {
|
||||
BERRIES_ABOUND,
|
||||
CLOWNING_AROUND,
|
||||
PART_TIMER,
|
||||
DANCING_LESSONS
|
||||
DANCING_LESSONS,
|
||||
WEIRD_DREAM
|
||||
}
|
||||
|
@ -76,6 +76,10 @@ export const modifierType: ModifierTypeTranslationEntries = {
|
||||
"cursed": "cursed"
|
||||
},
|
||||
},
|
||||
"PokemonBaseStatFlatModifierType": {
|
||||
name: "Old Gateau",
|
||||
description: "Increases the holder's {{stats}} base stats by {{statValue}}. Found after a strange dream.",
|
||||
},
|
||||
"AllPokemonFullHpRestoreModifierType": {
|
||||
description: "Restores 100% HP for all Pokémon.",
|
||||
},
|
||||
@ -258,6 +262,7 @@ export const modifierType: ModifierTypeTranslationEntries = {
|
||||
|
||||
"MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { name: "Shuckle Juice" },
|
||||
"MYSTERY_ENCOUNTER_BLACK_SLUDGE": { name: "Black Sludge", description: "The stench is so powerful that healing items are no longer available to purchase in shops." },
|
||||
"MYSTERY_ENCOUNTER_OLD_GATEAU": { name: "Old Gateau", description: "Increases the holder's {{stats}} stats by {{statValue}}." },
|
||||
},
|
||||
SpeciesBoosterItem: {
|
||||
"LIGHT_BALL": { name: "Light Ball", description: "It's a mysterious orb that boosts Pikachu's Attack and Sp. Atk stats." },
|
||||
|
@ -21,6 +21,7 @@ import { berriesAboundDialogue } from "#app/locales/en/mystery-encounters/berrie
|
||||
import { clowningAroundDialogue } from "#app/locales/en/mystery-encounters/clowning-around-dialogue";
|
||||
import { partTimerDialogue } from "#app/locales/en/mystery-encounters/part-timer-dialogue";
|
||||
import { dancingLessonsDialogue } from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue";
|
||||
import { weirdDreamDialogue } from "#app/locales/en/mystery-encounters/weird-dream-dialogue";
|
||||
|
||||
/**
|
||||
* Injection patterns that can be used:
|
||||
@ -69,5 +70,6 @@ export const mysteryEncounter = {
|
||||
berriesAbound: berriesAboundDialogue,
|
||||
clowningAround: clowningAroundDialogue,
|
||||
partTimer: partTimerDialogue,
|
||||
dancingLessons: dancingLessonsDialogue
|
||||
dancingLessons: dancingLessonsDialogue,
|
||||
weirdDream: weirdDreamDialogue
|
||||
} as const;
|
||||
|
@ -1,26 +1,23 @@
|
||||
export const berriesAboundDialogue = {
|
||||
intro: "There's a huge berry bush\nnear that Pokémon!",
|
||||
title: "Berries Abound",
|
||||
description: "It looks like there's a strong Pokémon guarding a berry bush. Battling is the straightforward approach, but this Pokémon looks strong. You could also try to sneak around, though the Pokémon might catch you.",
|
||||
description: "It looks like there's a strong Pokémon guarding a berry bush. Battling is the straightforward approach, but this Pokémon looks strong. Maybe a fast Pokémon would be able to grab some without getting caught?",
|
||||
query: "What will you do?",
|
||||
berries: "Berries!",
|
||||
option: {
|
||||
1: {
|
||||
label: "Battle the Pokémon",
|
||||
tooltip: "(-) Hard Battle\n(+) New Item",
|
||||
tooltip: "(-) Hard Battle\n(+) Gain Berries",
|
||||
selected: "You approach the\nPokémon without fear.",
|
||||
},
|
||||
2: {
|
||||
label: "Steal the Berries",
|
||||
tooltip: "@[SUMMARY_GREEN]{(35%) Steal Item}\n@[SUMMARY_BLUE]{(65%) Harder Battle}",
|
||||
tooltip_special: "(+) {{option2PrimaryName}} uses {{option2PrimaryMove}}",
|
||||
good_result: `.@d{32}.@d{32}.@d{32}
|
||||
$You manage to sneak your way\npast and grab the berries!`,
|
||||
special_result: `.@d{32}.@d{32}.@d{32}
|
||||
$Your {{option2PrimaryName}} helps you out and uses {{option2PrimaryMove}}!
|
||||
$You nabbed the berries!`,
|
||||
bad_result: `.@d{32}.@d{32}.@d{32}
|
||||
$The Pokémon catches you\nas you try to sneak around!`,
|
||||
label: "Race to the Bush",
|
||||
tooltip: "(-) {{fastestPokemon}} Uses its Speed\n(+) Gain Berries",
|
||||
selected: `Your {{fastestPokemon}} races for the berry bush!
|
||||
$It manages to nab {{numBerries}} before the {{enemyPokemon}} can react!
|
||||
$You quickly retreat with your newfound prize.`,
|
||||
selected_bad: `Your {{fastestPokemon}} races for the berry bush!
|
||||
$Oh no! The {{enemyPokemon}} was faster and blocked off the approach!`,
|
||||
boss_enraged: "The opposing {{enemyPokemon}} has become enraged!"
|
||||
},
|
||||
3: {
|
||||
|
@ -11,16 +11,11 @@ export const fightOrFlightDialogue = {
|
||||
},
|
||||
2: {
|
||||
label: "Steal the Item",
|
||||
tooltip: "@[SUMMARY_GREEN]{(35%) Steal Item}\n@[SUMMARY_BLUE]{(65%) Harder Battle}",
|
||||
tooltip_special: "(+) {{option2PrimaryName}} uses {{option2PrimaryMove}}",
|
||||
good_result: `.@d{32}.@d{32}.@d{32}
|
||||
$You manage to sneak your way\npast and grab the item!`,
|
||||
special_result: `.@d{32}.@d{32}.@d{32}
|
||||
disabled_tooltip: "Your Pokémon need to know certain moves to choose this",
|
||||
tooltip: "(+) {{option2PrimaryName}} uses {{option2PrimaryMove}}",
|
||||
selected: `.@d{32}.@d{32}.@d{32}
|
||||
$Your {{option2PrimaryName}} helps you out and uses {{option2PrimaryMove}}!
|
||||
$You nabbed the item!`,
|
||||
bad_result: `.@d{32}.@d{32}.@d{32}
|
||||
$The Pokémon catches you\nas you try to sneak around!`,
|
||||
boss_enraged: "The opposing {{enemyPokemon}} has become enraged!"
|
||||
},
|
||||
3: {
|
||||
label: "Leave",
|
||||
|
29
src/locales/en/mystery-encounters/weird-dream-dialogue.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export const weirdDreamDialogue = {
|
||||
intro: "A shadowy woman blocks your path.\nSomething about her is unsettling...",
|
||||
speaker: "Woman",
|
||||
intro_dialogue: `I have seen your futures, your pasts...
|
||||
$Child, do you see them too?`,
|
||||
title: "???",
|
||||
description: "The woman's words echo in your head. It wasn't just a singular voice, but a vast multitude, from all timelines and realities. You begin to feel dizzy, the question lingering on your mind...\n\n@[TOOLTIP_TITLE]{\"I have seen your futures, your pasts... Child, do you see them too?\"}",
|
||||
query: "What will you do?",
|
||||
option: {
|
||||
1: {
|
||||
label: "\"I See Them\"",
|
||||
tooltip: "@[SUMMARY_GREEN]{(?) Affects your Pokémon}",
|
||||
selected: `Her hand reaches out to touch you,\nand everything goes black.
|
||||
$Then...@d{64} You see everything.\nEvery timeline, all your different selves,\n past and future.
|
||||
$Everything that has made you,\neverything you will become...@d{64}`,
|
||||
cutscene: "You see your Pokémon,@d{32} converging from\nevery reality to become something new...@d{64}",
|
||||
dream_complete: `When you awaken, the woman - was it a woman or a ghost? - is gone...
|
||||
$.@d{32}.@d{32}.@d{32}
|
||||
$Your Pokémon team has changed...\nOr is it the same team you've always had?`
|
||||
},
|
||||
2: {
|
||||
label: "Quickly Leave",
|
||||
tooltip: "(-) Affects your Pokémon",
|
||||
selected: `You tear your mind from a numbing grip, and hastily depart.
|
||||
$When you finally stop to collect yourself, you check the Pokémon in your team.
|
||||
$For some reason, all of their levels have decreased!`,
|
||||
}
|
||||
},
|
||||
};
|
@ -1,27 +1,27 @@
|
||||
import * as Modifiers from "./modifier";
|
||||
import { AttackMove, allMoves, selfStatLowerMoves } from "../data/move";
|
||||
import { MAX_PER_TYPE_POKEBALLS, PokeballType, getPokeballCatchMultiplier, getPokeballName } from "../data/pokeball";
|
||||
import { MoneyMultiplierModifier } from "./modifier";
|
||||
import { allMoves, AttackMove, selfStatLowerMoves } from "../data/move";
|
||||
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "../field/pokemon";
|
||||
import { EvolutionItem, pokemonEvolutions } from "../data/pokemon-evolutions";
|
||||
import { Stat, getStatName } from "../data/pokemon-stat";
|
||||
import { getStatName, Stat } from "../data/pokemon-stat";
|
||||
import { tmPoolTiers, tmSpecies } from "../data/tms";
|
||||
import { Type } from "../data/type";
|
||||
import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "../ui/party-ui-handler";
|
||||
import * as Utils from "../utils";
|
||||
import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from "../data/temp-battle-stat";
|
||||
import { getTempBattleStatBoosterItemName, getTempBattleStatName, TempBattleStat } from "../data/temp-battle-stat";
|
||||
import { getBerryEffectDescription, getBerryName } from "../data/berry";
|
||||
import { Unlockables } from "../system/unlockables";
|
||||
import { StatusEffect, getStatusEffectDescriptor } from "../data/status-effect";
|
||||
import { getStatusEffectDescriptor, StatusEffect } from "../data/status-effect";
|
||||
import { SpeciesFormKey } from "../data/pokemon-species";
|
||||
import BattleScene from "../battle-scene";
|
||||
import { VoucherType, getVoucherTypeIcon, getVoucherTypeName } from "../system/voucher";
|
||||
import { FormChangeItem, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger, pokemonFormChanges } from "../data/pokemon-forms";
|
||||
import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "../system/voucher";
|
||||
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "../data/pokemon-forms";
|
||||
import { ModifierTier } from "./modifier-tier";
|
||||
import { Nature, getNatureName, getNatureStatMultiplier } from "#app/data/nature";
|
||||
import { getNatureName, getNatureStatMultiplier, Nature } from "#app/data/nature";
|
||||
import i18next from "i18next";
|
||||
import { getModifierTierTextTint } from "#app/ui/text";
|
||||
import Overrides from "#app/overrides";
|
||||
import { MoneyMultiplierModifier } from "./modifier";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
@ -680,6 +680,28 @@ export class PokemonBaseStatTotalModifierType extends PokemonHeldItemModifierTyp
|
||||
}
|
||||
}
|
||||
|
||||
export class PokemonBaseStatFlatModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
|
||||
private readonly statModifier: integer;
|
||||
private readonly stats: Stat[];
|
||||
|
||||
constructor(statModifier: integer, stats: Stat[]) {
|
||||
super("modifierType:ModifierType.MYSTERY_ENCOUNTER_OLD_GATEAU", "old_gateau", (_type, args) => new Modifiers.PokemonBaseStatFlatModifier(this, (args[0] as Pokemon).id, this.statModifier, this.stats));
|
||||
this.statModifier = statModifier;
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
getDescription(scene: BattleScene): string {
|
||||
return i18next.t("modifierType:ModifierType.PokemonBaseStatFlatModifierType.description", {
|
||||
stats: this.stats.map(stat => getStatName(stat)).join("/"),
|
||||
statValue: this.statModifier,
|
||||
});
|
||||
}
|
||||
|
||||
getPregenArgs(): any[] {
|
||||
return [ this.statModifier, this.stats ];
|
||||
}
|
||||
}
|
||||
|
||||
class AllPokemonFullHpRestoreModifierType extends ModifierType {
|
||||
private descriptionKey: string;
|
||||
|
||||
@ -1477,7 +1499,12 @@ export const modifierTypes = {
|
||||
}
|
||||
return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20));
|
||||
}),
|
||||
|
||||
MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs) {
|
||||
return new PokemonBaseStatFlatModifierType(pregenArgs[0] as integer, pregenArgs[1] as Stat[]);
|
||||
}
|
||||
return new PokemonBaseStatFlatModifierType(Utils.randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]);
|
||||
}),
|
||||
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.RemoveHealShopModifier(type)),
|
||||
};
|
||||
|
||||
@ -1948,6 +1975,7 @@ export interface CustomModifierSettings {
|
||||
guaranteedModifierTypeFuncs?: ModifierTypeFunc[];
|
||||
fillRemaining?: boolean;
|
||||
rerollMultiplier?: number;
|
||||
allowLuckUpgrades?: boolean;
|
||||
}
|
||||
|
||||
export function getModifierTypeFuncById(id: string): ModifierTypeFunc {
|
||||
@ -1968,6 +1996,7 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc {
|
||||
* - The first item in the shop will be `GREAT` tier, and the remaining 3 items will be generated normally.
|
||||
* - If `fillRemaining = false` in the same scenario, only 1 `GREAT` tier item will appear in the shop (regardless of `count` value).
|
||||
* - `rerollMultiplier?: number` - If specified, can adjust the amount of money required for a shop reroll. If set to 0, the shop will not allow rerolls at all.
|
||||
* - `allowLuckUpgrades?: boolean` - Default true, if false will prevent set item tiers from upgrading via luck
|
||||
*/
|
||||
export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings): ModifierTypeOption[] {
|
||||
const options: ModifierTypeOption[] = [];
|
||||
@ -2001,8 +2030,9 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
|
||||
|
||||
// Guaranteed tiers third
|
||||
if (customModifierSettings?.guaranteedModifierTiers?.length > 0) {
|
||||
customModifierSettings?.guaranteedModifierTiers.forEach((tier) => {
|
||||
options.push(getModifierTypeOptionWithRetry(options, retryCount, party, tier));
|
||||
const allowLuckUpgrades = customModifierSettings.allowLuckUpgrades ?? true;
|
||||
customModifierSettings.guaranteedModifierTiers.forEach((tier) => {
|
||||
options.push(getModifierTypeOptionWithRetry(options, retryCount, party, tier, allowLuckUpgrades));
|
||||
});
|
||||
}
|
||||
|
||||
@ -2028,11 +2058,12 @@ export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemo
|
||||
return options;
|
||||
}
|
||||
|
||||
function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], retryCount: integer, party: PlayerPokemon[], tier?: ModifierTier): ModifierTypeOption {
|
||||
let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier);
|
||||
function getModifierTypeOptionWithRetry(existingOptions: ModifierTypeOption[], retryCount: integer, party: PlayerPokemon[], tier?: ModifierTier, allowLuckUpgrades?: boolean): ModifierTypeOption {
|
||||
allowLuckUpgrades = allowLuckUpgrades ?? true;
|
||||
let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, tier, undefined, 0, allowLuckUpgrades);
|
||||
let r = 0;
|
||||
while (existingOptions.length && ++r < retryCount && existingOptions.filter(o => o.type.name === candidate.type.name || o.type.group === candidate.type.group).length) {
|
||||
candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount);
|
||||
candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount, 0, allowLuckUpgrades);
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
@ -2112,7 +2143,7 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0): ModifierTypeOption {
|
||||
function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0, allowLuckUpgrades: boolean = true): ModifierTypeOption {
|
||||
const player = !poolType;
|
||||
const pool = getModifierPoolForType(poolType);
|
||||
let thresholds: object;
|
||||
@ -2138,7 +2169,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType,
|
||||
if (!upgradeCount) {
|
||||
upgradeCount = 0;
|
||||
}
|
||||
if (player && tierValue) {
|
||||
if (player && tierValue && allowLuckUpgrades) {
|
||||
const partyLuckValue = getPartyLuckValue(party);
|
||||
const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4));
|
||||
let upgraded = false;
|
||||
@ -2163,7 +2194,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType,
|
||||
}
|
||||
} else if (upgradeCount === undefined && player) {
|
||||
upgradeCount = 0;
|
||||
if (tier < ModifierTier.MASTER) {
|
||||
if (tier < ModifierTier.MASTER && allowLuckUpgrades) {
|
||||
const partyShinyCount = party.filter(p => p.isShiny() && !p.isFainted()).length;
|
||||
const upgradeOdds = Math.floor(32 / ((partyShinyCount + 2) / 2));
|
||||
while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) {
|
||||
|
@ -702,6 +702,7 @@ export class PokemonBaseStatModifier extends PokemonHeldItemModifier {
|
||||
|
||||
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
private statModifier: integer;
|
||||
readonly isTransferrable: boolean = false;
|
||||
|
||||
constructor(type: ModifierTypes.PokemonBaseStatTotalModifierType, pokemonId: integer, statModifier: integer, stackCount?: integer) {
|
||||
super(type, pokemonId, stackCount);
|
||||
@ -709,10 +710,7 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
matchType(modifier: Modifier): boolean {
|
||||
if (modifier instanceof PokemonBaseStatTotalModifier) {
|
||||
return (modifier as PokemonBaseStatTotalModifier).statModifier === this.statModifier;
|
||||
}
|
||||
return false;
|
||||
return modifier instanceof PokemonBaseStatTotalModifier;
|
||||
}
|
||||
|
||||
clone(): PersistentModifier {
|
||||
@ -728,6 +726,7 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
apply(args: any[]): boolean {
|
||||
// Modifies the passed in baseStats[] array
|
||||
args[1].forEach((v, i) => {
|
||||
const newVal = Math.floor(v + this.statModifier);
|
||||
args[1][i] = Math.min(Math.max(newVal, 1), 999999);
|
||||
@ -736,10 +735,6 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
getTransferrable(_withinParty: boolean): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
getScoreMultiplier(): number {
|
||||
return 1.2;
|
||||
}
|
||||
@ -749,6 +744,55 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
}
|
||||
|
||||
export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
private statModifier: integer;
|
||||
private stats: Stat[];
|
||||
readonly isTransferrable: boolean = false;
|
||||
|
||||
constructor (type: ModifierType, pokemonId: integer, statModifier: integer, stats: Stat[], stackCount?: integer) {
|
||||
super(type, pokemonId, stackCount);
|
||||
|
||||
this.statModifier = statModifier;
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof PokemonBaseStatFlatModifier;
|
||||
}
|
||||
|
||||
clone(): PersistentModifier {
|
||||
return new PokemonBaseStatFlatModifier(this.type, this.pokemonId, this.statModifier, this.stats, this.stackCount);
|
||||
}
|
||||
|
||||
getArgs(): any[] {
|
||||
return super.getArgs().concat(this.statModifier, this.stats);
|
||||
}
|
||||
|
||||
shouldApply(args: any[]): boolean {
|
||||
return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array;
|
||||
}
|
||||
|
||||
apply(args: any[]): boolean {
|
||||
// Modifies the passed in baseStats[] array by a flat value, only if the stat is specified in this.stats
|
||||
args[1].forEach((v, i) => {
|
||||
if (this.stats.includes(i)) {
|
||||
const newVal = Math.floor(v + this.statModifier);
|
||||
args[1][i] = Math.min(Math.max(newVal, 1), 999999);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getScoreMultiplier(): number {
|
||||
return 1.1;
|
||||
}
|
||||
|
||||
getMaxHeldItemCount(pokemon: Pokemon): integer {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifier used for held items that apply {@linkcode Stat} boost(s)
|
||||
* using a multiplier.
|
||||
|
@ -1444,12 +1444,20 @@ export class GameData {
|
||||
}
|
||||
}
|
||||
|
||||
setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> {
|
||||
return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg);
|
||||
/**
|
||||
*
|
||||
* @param pokemon
|
||||
* @param incrementCount
|
||||
* @param fromEgg
|
||||
* @param showNewStarterMessage
|
||||
* @returns - true if Pokemon catch unlocked a new starter, false if Pokemon catch did not unlock a starter
|
||||
*/
|
||||
setPokemonCaught(pokemon: Pokemon, incrementCount: boolean = true, fromEgg: boolean = false, showNewStarterMessage: boolean = true): Promise<boolean> {
|
||||
return this.setPokemonSpeciesCaught(pokemon, pokemon.species, incrementCount, fromEgg, showNewStarterMessage);
|
||||
}
|
||||
|
||||
setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
setPokemonSpeciesCaught(pokemon: Pokemon, species: PokemonSpecies, incrementCount: boolean = true, fromEgg: boolean = false, showNewStarterMessage: boolean = true): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
const dexEntry = this.dexData[species.speciesId];
|
||||
const caughtAttr = dexEntry.caughtAttr;
|
||||
const formIndex = pokemon.formIndex;
|
||||
@ -1504,20 +1512,20 @@ export class GameData {
|
||||
}
|
||||
}
|
||||
|
||||
const checkPrevolution = () => {
|
||||
const checkPrevolution = (newStarter: boolean) => {
|
||||
if (hasPrevolution) {
|
||||
const prevolutionSpecies = pokemonPrevolutions[species.speciesId];
|
||||
return this.setPokemonSpeciesCaught(pokemon, getPokemonSpecies(prevolutionSpecies), incrementCount, fromEgg).then(() => resolve());
|
||||
return this.setPokemonSpeciesCaught(pokemon, getPokemonSpecies(prevolutionSpecies), incrementCount, fromEgg, showNewStarterMessage).then(result => resolve(result));
|
||||
} else {
|
||||
resolve();
|
||||
resolve(newStarter);
|
||||
}
|
||||
};
|
||||
|
||||
if (newCatch && speciesStarters.hasOwnProperty(species.speciesId)) {
|
||||
if (newCatch && speciesStarters.hasOwnProperty(species.speciesId) && showNewStarterMessage) {
|
||||
this.scene.playSound("level_up_fanfare");
|
||||
this.scene.ui.showText(i18next.t("battle:addedAsAStarter", { pokemonName: species.name }), null, () => checkPrevolution(), null, true);
|
||||
this.scene.ui.showText(i18next.t("battle:addedAsAStarter", { pokemonName: species.name }), null, () => checkPrevolution(true), null, true);
|
||||
} else {
|
||||
checkPrevolution();
|
||||
checkPrevolution(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1559,7 +1567,7 @@ export class GameData {
|
||||
this.starterData[species.speciesId].candyCount += count;
|
||||
}
|
||||
|
||||
setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer): Promise<boolean> {
|
||||
setEggMoveUnlocked(species: PokemonSpecies, eggMoveIndex: integer, prependSpeciesToMessage: boolean = false): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
const speciesId = species.speciesId;
|
||||
if (!speciesEggMoves.hasOwnProperty(speciesId) || !speciesEggMoves[speciesId][eggMoveIndex]) {
|
||||
@ -1583,7 +1591,10 @@ export class GameData {
|
||||
this.scene.playSound("level_up_fanfare");
|
||||
|
||||
const moveName = allMoves[speciesEggMoves[speciesId][eggMoveIndex]].name;
|
||||
this.scene.ui.showText(eggMoveIndex === 3 ? i18next.t("egg:rareEggMoveUnlock", { moveName: moveName }) : i18next.t("egg:eggMoveUnlock", { moveName: moveName }), null, () => resolve(true), null, true);
|
||||
let message = prependSpeciesToMessage ? species.getName() + " " : "";
|
||||
message += eggMoveIndex === 3 ? i18next.t("egg:rareEggMoveUnlock", { moveName: moveName }) : i18next.t("egg:eggMoveUnlock", { moveName: moveName });
|
||||
|
||||
this.scene.ui.showText(message, null, () => resolve(true), null, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,7 @@ import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { CommandPhase, SelectModifierPhase } from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
@ -16,13 +14,11 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter";
|
||||
import * as Utils from "utils";
|
||||
import { isNullOrUndefined } from "utils";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import * as EncounterDialogueUtils from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
|
||||
const namespace = "mysteryEncounter:berriesAbound";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
const defaultParty = [Species.PYUKUMUKU];
|
||||
const defaultBiome = Biome.CAVE;
|
||||
const defaultWave = 45;
|
||||
|
||||
@ -105,10 +101,10 @@ describe("Berries Abound - Mystery Encounter", () => {
|
||||
|
||||
describe("Option 1 - Fight", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = BerriesAboundEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
const option = BerriesAboundEncounter.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: [
|
||||
@ -166,32 +162,25 @@ describe("Berries Abound - Mystery Encounter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Attempt to Steal", () => {
|
||||
describe("Option 2 - Race to the Bush", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = BerriesAboundEncounter.options[1];
|
||||
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
const option = BerriesAboundEncounter.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`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should start battle on failing to steal", async () => {
|
||||
it("should start battle if fastest pokemon is slower than boss", async () => {
|
||||
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
|
||||
|
||||
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
|
||||
const speciesToSpawn = config.pokemonConfigs[0].species.speciesId;
|
||||
|
||||
const realFn = Utils.randSeedInt;
|
||||
vi.spyOn(Utils, "randSeedInt").mockImplementation((range, min) => {
|
||||
if (range === 16 && isNullOrUndefined(min)) {
|
||||
// Mock the steal roll
|
||||
return 12;
|
||||
} else {
|
||||
return realFn(range, min);
|
||||
}
|
||||
});
|
||||
// Setting enemy's level arbitrarily high to outspeed
|
||||
config.pokemonConfigs[0].dataSource.level = 1000;
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2, null, true);
|
||||
|
||||
@ -202,60 +191,33 @@ describe("Berries Abound - Mystery Encounter", () => {
|
||||
|
||||
// Should be enraged
|
||||
expect(enemyField[0].summonData.battleStats).toEqual([1, 1, 1, 1, 1, 0, 0]);
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.selected_bad`);
|
||||
});
|
||||
|
||||
it("Should skip battle when succeed on steal", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
|
||||
|
||||
const realFn = Utils.randSeedInt;
|
||||
vi.spyOn(Utils, "randSeedInt").mockImplementation((range, min) => {
|
||||
if (range === 16 && isNullOrUndefined(min)) {
|
||||
// Mock the steal roll
|
||||
return 6;
|
||||
} else {
|
||||
return realFn(range, min);
|
||||
}
|
||||
});
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
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(5);
|
||||
for (const option of modifierSelectHandler.options) {
|
||||
expect(option.modifierTypeOption.type.id).toContain("BERRY");
|
||||
}
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it("Should skip fight when special requirements are met", async () => {
|
||||
it("Should skip battle when fastest pokemon is faster than boss", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
|
||||
|
||||
// Mock moveset
|
||||
scene.getParty()[0].moveset = [new PokemonMove(Moves.KNOCK_OFF)];
|
||||
// Setting party pokemon's level arbitrarily high to outspeed
|
||||
const fastestPokemon = scene.getParty()[0];
|
||||
fastestPokemon.level = 1000;
|
||||
fastestPokemon.calculateStats();
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
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);
|
||||
|
||||
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).toContain("BERRY");
|
||||
}
|
||||
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.special_result`);
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.selected`);
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#app/enums/mystery-encounter-type";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { CommandPhase, SelectModifierPhase } from "#app/phases";
|
||||
import { Moves } from "#enums/moves";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
@ -14,11 +14,9 @@ import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import * as Utils from "utils";
|
||||
import { isNullOrUndefined } from "utils";
|
||||
import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import * as EncounterDialogueUtils from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { FightOrFlightEncounter } from "#app/data/mystery-encounters/encounters/fight-or-flight-encounter";
|
||||
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
|
||||
const namespace = "mysteryEncounter:fightOrFlight";
|
||||
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
|
||||
@ -104,10 +102,10 @@ describe("Fight or Flight - Mystery Encounter", () => {
|
||||
|
||||
describe("Option 1 - Fight", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = FightOrFlightEncounter.options[0];
|
||||
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
const option = FightOrFlightEncounter.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: [
|
||||
@ -152,74 +150,43 @@ describe("Fight or Flight - Mystery Encounter", () => {
|
||||
|
||||
describe("Option 2 - Attempt to Steal", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option1 = FightOrFlightEncounter.options[1];
|
||||
expect(option1.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT_OR_SPECIAL);
|
||||
expect(option1.dialogue).toBeDefined();
|
||||
expect(option1.dialogue).toStrictEqual({
|
||||
const option = FightOrFlightEncounter.options[1];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.2.label`,
|
||||
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||
});
|
||||
});
|
||||
|
||||
it("should start battle on failing to steal", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
|
||||
|
||||
const config = game.scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0];
|
||||
const speciesToSpawn = config.pokemonConfigs[0].species.speciesId;
|
||||
|
||||
const realFn = Utils.randSeedInt;
|
||||
vi.spyOn(Utils, "randSeedInt").mockImplementation((range, min) => {
|
||||
if (range === 16 && isNullOrUndefined(min)) {
|
||||
// Mock the steal roll
|
||||
return 12;
|
||||
} else {
|
||||
return realFn(range, min);
|
||||
disabledButtonTooltip: `${namespace}.option.2.disabled_tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
}
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2, null, true);
|
||||
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name);
|
||||
expect(enemyField.length).toBe(1);
|
||||
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
|
||||
|
||||
// Should be enraged
|
||||
expect(enemyField[0].summonData.battleStats).toEqual([1, 1, 1, 1, 1, 0, 0]);
|
||||
});
|
||||
|
||||
it("Should skip battle when succeed on steal", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
it("should NOT be selectable if the player doesn't have a Stealing move", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
|
||||
scene.getParty().forEach(p => p.moveset = []);
|
||||
await game.phaseInterceptor.to(MysteryEncounterPhase, false);
|
||||
|
||||
const item = game.scene.currentBattle.mysteryEncounter.misc;
|
||||
const realFn = Utils.randSeedInt;
|
||||
vi.spyOn(Utils, "randSeedInt").mockImplementation((range, min) => {
|
||||
if (range === 16 && isNullOrUndefined(min)) {
|
||||
// Mock the steal roll
|
||||
return 6;
|
||||
} else {
|
||||
return realFn(range, min);
|
||||
}
|
||||
const encounterPhase = scene.getCurrentPhase();
|
||||
expect(encounterPhase.constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
const mysteryEncounterPhase = encounterPhase as MysteryEncounterPhase;
|
||||
vi.spyOn(mysteryEncounterPhase, "continueEncounter");
|
||||
vi.spyOn(mysteryEncounterPhase, "handleOptionSelect");
|
||||
vi.spyOn(scene.ui, "playError");
|
||||
|
||||
await runSelectMysteryEncounterOption(game, 2);
|
||||
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(MysteryEncounterPhase.name);
|
||||
expect(scene.ui.playError).not.toHaveBeenCalled(); // No error sfx, option is disabled
|
||||
expect(mysteryEncounterPhase.handleOptionSelect).not.toHaveBeenCalled();
|
||||
expect(mysteryEncounterPhase.continueEncounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||
await game.phaseInterceptor.run(SelectModifierPhase);
|
||||
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
|
||||
|
||||
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
|
||||
expect(modifierSelectHandler.options.length).toEqual(1);
|
||||
expect(item.type.name).toBe(modifierSelectHandler.options[0].modifierTypeOption.type.name);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it("Should skip fight when special requirements are met", async () => {
|
||||
it("Should skip fight when player meets requirements", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.FIGHT_OR_FLIGHT, defaultParty);
|
||||
|
||||
@ -237,7 +204,6 @@ describe("Fight or Flight - Mystery Encounter", () => {
|
||||
expect(modifierSelectHandler.options.length).toEqual(1);
|
||||
expect(item.type.name).toBe(modifierSelectHandler.options[0].modifierTypeOption.type.name);
|
||||
|
||||
expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.special_result`);
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,219 @@
|
||||
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 { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils";
|
||||
import { SelectModifierPhase } from "#app/phases";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils";
|
||||
import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-encounter";
|
||||
import * as EncounterTransformationSequence from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||
|
||||
const namespace = "mysteryEncounter:weirdDream";
|
||||
const defaultParty = [Species.MAGBY, Species.HAUNTER, Species.ABRA];
|
||||
const defaultBiome = Biome.CAVE;
|
||||
const defaultWave = 45;
|
||||
|
||||
describe("Weird Dream - Mystery Encounter", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
let scene: BattleScene;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
game = new GameManager(phaserGame);
|
||||
scene = game.scene;
|
||||
game.override.mysteryEncounterChance(100);
|
||||
game.override.startingWave(defaultWave);
|
||||
game.override.startingBiome(defaultBiome);
|
||||
game.override.disableTrainerWaves(true);
|
||||
vi.spyOn(EncounterTransformationSequence, "doPokemonTransformationSequence").mockImplementation(() => new Promise<void>(resolve => resolve()));
|
||||
|
||||
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
|
||||
new Map<Biome, MysteryEncounterType[]>([
|
||||
[Biome.CAVE, [MysteryEncounterType.WEIRD_DREAM]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it("should have the correct properties", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
|
||||
|
||||
expect(WeirdDreamEncounter.encounterType).toBe(MysteryEncounterType.WEIRD_DREAM);
|
||||
expect(WeirdDreamEncounter.encounterTier).toBe(MysteryEncounterTier.ROGUE);
|
||||
expect(WeirdDreamEncounter.dialogue).toBeDefined();
|
||||
expect(WeirdDreamEncounter.dialogue.intro).toStrictEqual([
|
||||
{
|
||||
text: `${namespace}.intro`
|
||||
},
|
||||
{
|
||||
speaker: `${namespace}.speaker`,
|
||||
text: `${namespace}.intro_dialogue`,
|
||||
},
|
||||
]);
|
||||
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`);
|
||||
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`);
|
||||
expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`);
|
||||
expect(WeirdDreamEncounter.options.length).toBe(2);
|
||||
});
|
||||
|
||||
it("should not run below wave 10", async () => {
|
||||
game.override.startingWave(9);
|
||||
|
||||
await game.runToMysteryEncounter();
|
||||
|
||||
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.WEIRD_DREAM);
|
||||
});
|
||||
|
||||
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 = WeirdDreamEncounter;
|
||||
const loadBgmSpy = vi.spyOn(scene, "loadBgm");
|
||||
|
||||
const { onInit } = WeirdDreamEncounter;
|
||||
|
||||
expect(WeirdDreamEncounter.onInit).toBeDefined();
|
||||
|
||||
WeirdDreamEncounter.populateDialogueTokensFromRequirements(scene);
|
||||
const onInitResult = onInit(scene);
|
||||
|
||||
expect(loadBgmSpy).toHaveBeenCalled();
|
||||
expect(onInitResult).toBe(true);
|
||||
});
|
||||
|
||||
describe("Option 1 - Accept Transformation", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = WeirdDreamEncounter.options[0];
|
||||
expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT);
|
||||
expect(option.dialogue).toBeDefined();
|
||||
expect(option.dialogue).toStrictEqual({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
selected: [
|
||||
{
|
||||
text: `${namespace}.option.1.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should transform the new party into new species, 2 at +90/+110, the rest at +40/50 BST", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
|
||||
|
||||
const pokemonPrior = scene.getParty().map(pokemon => pokemon);
|
||||
const bstsPrior = pokemonPrior.map(species => species.getSpeciesForm().getBaseStatTotal());
|
||||
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
await game.phaseInterceptor.to(SelectModifierPhase, false);
|
||||
expect(scene.getCurrentPhase().constructor.name).toBe(SelectModifierPhase.name);
|
||||
|
||||
const pokemonAfter = scene.getParty();
|
||||
const bstsAfter = pokemonAfter.map(pokemon => pokemon.getSpeciesForm().getBaseStatTotal());
|
||||
const bstDiff = bstsAfter.map((bst, index) => bst - bstsPrior[index]);
|
||||
|
||||
for (let i = 0; i < pokemonAfter.length; i++) {
|
||||
const newPokemon = pokemonAfter[i];
|
||||
expect(newPokemon.getSpeciesForm().speciesId).not.toBe(pokemonPrior[i].getSpeciesForm().speciesId);
|
||||
expect(newPokemon.mysteryEncounterData.types.length).toBe(2);
|
||||
}
|
||||
|
||||
const plus90To110 = bstDiff.filter(bst => bst > 80);
|
||||
const plus40To50 = bstDiff.filter(bst => bst < 80);
|
||||
|
||||
expect(plus90To110.length).toBe(2);
|
||||
expect(plus40To50.length).toBe(1);
|
||||
});
|
||||
|
||||
it("should have 1 Memory Mushroom, 5 Rogue Balls, and 2 Mints in rewards", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
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(4);
|
||||
expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM");
|
||||
expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("ROGUE_BALL");
|
||||
expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("MINT");
|
||||
expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("MINT");
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Option 2 - Leave", () => {
|
||||
it("should have the correct properties", () => {
|
||||
const option = WeirdDreamEncounter.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: [
|
||||
{
|
||||
text: `${namespace}.option.2.selected`,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("should reduce party levels by 20%", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
|
||||
const levelsPrior = scene.getParty().map(p => p.level);
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
|
||||
const levelsAfter = scene.getParty().map(p => p.level);
|
||||
|
||||
for (let i = 0; i < levelsPrior.length; i++) {
|
||||
expect(Math.max(Math.ceil(0.8 * levelsPrior[i]), 1)).toBe(levelsAfter[i]);
|
||||
expect(scene.getParty()[i].levelExp).toBe(0);
|
||||
}
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 2);
|
||||
|
||||
expect(leaveEncounterWithoutBattleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -5,6 +5,7 @@ import MockNineslice from "#app/test/utils/mocks/mocksContainer/mockNineslice";
|
||||
import MockImage from "#app/test/utils/mocks/mocksContainer/mockImage";
|
||||
import MockText from "#app/test/utils/mocks/mocksContainer/mockText";
|
||||
import MockPolygon from "#app/test/utils/mocks/mocksContainer/mockPolygon";
|
||||
import MockVideo from "#test/utils/mocks/mocksContainer/mockVideo";
|
||||
|
||||
|
||||
export default class MockTextureManager {
|
||||
@ -30,6 +31,7 @@ export default class MockTextureManager {
|
||||
text: this.text.bind(this),
|
||||
bitmapText: this.text.bind(this),
|
||||
displayList: this.displayList,
|
||||
video: this.video.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
@ -82,4 +84,10 @@ export default class MockTextureManager {
|
||||
this.list.push(polygon);
|
||||
return polygon;
|
||||
}
|
||||
|
||||
video(x: number, y: number, key?: string) {
|
||||
const video = new MockVideo(this, x, y, key);
|
||||
this.list.push(video);
|
||||
return video;
|
||||
}
|
||||
}
|
||||
|
@ -212,4 +212,8 @@ export default class MockContainer {
|
||||
return this.list;
|
||||
}
|
||||
|
||||
getByName(key: string) {
|
||||
return this.list.find(v => v.name === key) ?? new MockContainer(this.textureManager, 0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
26
src/test/utils/mocks/mocksContainer/mockVideo.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import MockContainer from "#test/utils/mocks/mocksContainer/mockContainer";
|
||||
|
||||
export default class MockVideo extends MockContainer {
|
||||
private video: HTMLVideoElement | null;
|
||||
private videoTexture: Phaser.Textures.Texture | null;
|
||||
private videoTextureSource: Phaser.Textures.TextureSource | null;
|
||||
|
||||
constructor(textureManager, x: number, y: number, key?: string) {
|
||||
super(textureManager, x, y);
|
||||
this.video = null;
|
||||
this.videoTexture = null;
|
||||
this.videoTextureSource = null;
|
||||
}
|
||||
|
||||
stop(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
play(): this {
|
||||
return this;
|
||||
}
|
||||
|
||||
setLoop(value?: boolean): this {
|
||||
return this;
|
||||
}
|
||||
}
|
@ -85,12 +85,6 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
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", () => {
|
||||
(this.scene as BattleScene).ui.showTooltip(null, i18next.t("mysteryEncounter:affects_pokedex"), true);
|
||||
});
|
||||
this.dexProgressContainer.on("pointerout", () => {
|
||||
(this.scene as BattleScene).ui.hideTooltip();
|
||||
});
|
||||
}
|
||||
|
||||
show(args: any[]): boolean {
|
||||
@ -555,7 +549,15 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
targets: this.dexProgressContainer,
|
||||
y: -63,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750
|
||||
duration: 750,
|
||||
onComplete: () => {
|
||||
this.dexProgressContainer.on("pointerover", () => {
|
||||
(this.scene as BattleScene).ui.showTooltip(null, i18next.t("mysteryEncounter:affects_pokedex"), true);
|
||||
});
|
||||
this.dexProgressContainer.on("pointerout", () => {
|
||||
(this.scene as BattleScene).ui.hideTooltip();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (!show && this.showDexProgress) {
|
||||
this.showDexProgress = false;
|
||||
@ -565,6 +567,10 @@ export default class MysteryEncounterUiHandler extends UiHandler {
|
||||
y: -43,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750,
|
||||
onComplete: () => {
|
||||
this.dexProgressContainer.off("pointerover");
|
||||
this.dexProgressContainer.off("pointerout");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|