test patch fix
This commit is contained in:
parent
c9d80a3a42
commit
0b81f6fd77
|
@ -0,0 +1,496 @@
|
||||||
|
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
|
||||||
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
|
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||||
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
import { Type } from "#app/data/type";
|
||||||
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
import { randSeedInt, randSeedShuffle } from "#app/utils";
|
||||||
|
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
import { Mode } from "#app/ui/ui";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
|
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
|
import { Ability } from "#app/data/ability";
|
||||||
|
import { BerryModifier } from "#app/modifier/modifier";
|
||||||
|
import { BerryType } from "#enums/berry-type";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims";
|
||||||
|
import { MoveCategory } from "#app/data/move";
|
||||||
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
|
||||||
|
/** the i18n namespace for the encounter */
|
||||||
|
const namespace = "mysteryEncounter:clowningAround";
|
||||||
|
|
||||||
|
const RANDOM_ABILITY_POOL = [
|
||||||
|
Abilities.STURDY,
|
||||||
|
Abilities.PICKUP,
|
||||||
|
Abilities.INTIMIDATE,
|
||||||
|
Abilities.GUTS,
|
||||||
|
Abilities.DROUGHT,
|
||||||
|
Abilities.DRIZZLE,
|
||||||
|
Abilities.SNOW_WARNING,
|
||||||
|
Abilities.SAND_STREAM,
|
||||||
|
Abilities.ELECTRIC_SURGE,
|
||||||
|
Abilities.PSYCHIC_SURGE,
|
||||||
|
Abilities.GRASSY_SURGE,
|
||||||
|
Abilities.MISTY_SURGE,
|
||||||
|
Abilities.MAGICIAN,
|
||||||
|
Abilities.SHEER_FORCE,
|
||||||
|
Abilities.PRANKSTER
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clowning Around encounter.
|
||||||
|
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69}
|
||||||
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||||
|
*/
|
||||||
|
export const ClowningAroundEncounter: MysteryEncounter =
|
||||||
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
|
||||||
|
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||||
|
.withSceneWaveRangeRequirement(80, 180)
|
||||||
|
.withAnimations(EncounterAnim.SMOKESCREEN)
|
||||||
|
.withAutoHideIntroVisuals(false)
|
||||||
|
.withIntroSpriteConfigs([
|
||||||
|
{
|
||||||
|
spriteKey: Species.MR_MIME.toString(),
|
||||||
|
fileRoot: "pokemon",
|
||||||
|
hasShadow: true,
|
||||||
|
repeat: true,
|
||||||
|
x: -25,
|
||||||
|
tint: 0.3,
|
||||||
|
y: -3,
|
||||||
|
yShadow: -3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spriteKey: Species.BLACEPHALON.toString(),
|
||||||
|
fileRoot: "pokemon/exp",
|
||||||
|
hasShadow: true,
|
||||||
|
repeat: true,
|
||||||
|
x: 25,
|
||||||
|
tint: 0.3,
|
||||||
|
y: -3,
|
||||||
|
yShadow: -3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spriteKey: "harlequin",
|
||||||
|
fileRoot: "trainer",
|
||||||
|
hasShadow: true,
|
||||||
|
x: 0,
|
||||||
|
y: 2,
|
||||||
|
yShadow: 2
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withIntroDialogue([
|
||||||
|
{
|
||||||
|
text: `${namespace}.intro`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.intro_dialogue`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withOnInit((scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
|
||||||
|
const clownTrainerType = TrainerType.HARLEQUIN;
|
||||||
|
const clownConfig = trainerConfigs[clownTrainerType].copy();
|
||||||
|
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
|
||||||
|
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
|
||||||
|
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
|
||||||
|
clownConfig.setPartyTemplates(clownPartyTemplate);
|
||||||
|
clownConfig.setDoubleOnly();
|
||||||
|
// @ts-ignore
|
||||||
|
clownConfig.partyTemplateFunc = null; // Overrides party template func if it exists
|
||||||
|
|
||||||
|
// Generate random ability for Blacephalon from pool
|
||||||
|
const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)];
|
||||||
|
encounter.setDialogueToken("ability", new Ability(ability, 3).name);
|
||||||
|
encounter.misc = { ability };
|
||||||
|
|
||||||
|
encounter.enemyPartyConfigs.push({
|
||||||
|
trainerConfig: clownConfig,
|
||||||
|
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon
|
||||||
|
{
|
||||||
|
species: getPokemonSpecies(Species.MR_MIME),
|
||||||
|
isBoss: true,
|
||||||
|
moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC]
|
||||||
|
},
|
||||||
|
{ // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter
|
||||||
|
species: getPokemonSpecies(Species.BLACEPHALON),
|
||||||
|
mysteryEncounterData: new MysteryEncounterPokemonData(undefined, ability, undefined, [randSeedInt(18), randSeedInt(18)]),
|
||||||
|
isBoss: true,
|
||||||
|
moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
doubleBattle: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load animations/sfx for start of fight moves
|
||||||
|
loadCustomMovesForEncounter(scene, [Moves.ROLE_PLAY, Moves.TAUNT]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.withTitle(`${namespace}.title`)
|
||||||
|
.withDescription(`${namespace}.description`)
|
||||||
|
.withQuery(`${namespace}.query`)
|
||||||
|
.withOption(
|
||||||
|
MysteryEncounterOptionBuilder
|
||||||
|
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}.option.1.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.1.selected`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
// Spawn battle
|
||||||
|
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||||
|
|
||||||
|
setEncounterRewards(scene, { fillRemaining: true });
|
||||||
|
|
||||||
|
// TODO: when Magic Room and Wonder Room are implemented, add those to start of battle
|
||||||
|
encounter.startOfBattleEffects.push(
|
||||||
|
{ // Mr. Mime copies the Blacephalon's random ability
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
|
targets: [BattlerIndex.ENEMY_2],
|
||||||
|
move: new PokemonMove(Moves.ROLE_PLAY),
|
||||||
|
ignorePp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||||
|
targets: [BattlerIndex.PLAYER],
|
||||||
|
move: new PokemonMove(Moves.TAUNT),
|
||||||
|
ignorePp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY_2,
|
||||||
|
targets: [BattlerIndex.PLAYER_2],
|
||||||
|
move: new PokemonMove(Moves.TAUNT),
|
||||||
|
ignorePp: true
|
||||||
|
});
|
||||||
|
|
||||||
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
|
await initBattleWithEnemyConfig(scene, config);
|
||||||
|
})
|
||||||
|
.withPostOptionPhase(async (scene: BattleScene): Promise<boolean> => {
|
||||||
|
// After the battle, offer the player the opportunity to permanently swap ability
|
||||||
|
const abilityWasSwapped = await handleSwapAbility(scene);
|
||||||
|
if (abilityWasSwapped) {
|
||||||
|
await showEncounterText(scene, `${namespace}.option.1.ability_gained`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play animations once ability swap is complete
|
||||||
|
// Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals
|
||||||
|
scene.tweens.add({
|
||||||
|
targets: scene.currentBattle.trainer,
|
||||||
|
x: "+=16",
|
||||||
|
y: "-=16",
|
||||||
|
alpha: 0,
|
||||||
|
ease: "Sine.easeInOut",
|
||||||
|
duration: 250
|
||||||
|
});
|
||||||
|
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||||
|
background.playWithoutTargets(scene, 230, 40, 2);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withOption(
|
||||||
|
MysteryEncounterOptionBuilder
|
||||||
|
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}.option.2.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected_2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected_3`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Swap player's items on pokemon with the most items
|
||||||
|
// Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items
|
||||||
|
// So Vitamins, form change items, etc. are not included
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
|
||||||
|
const party = scene.getParty();
|
||||||
|
let mostHeldItemsPokemon = party[0];
|
||||||
|
let count = mostHeldItemsPokemon.getHeldItems()
|
||||||
|
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||||
|
.reduce((v, m) => v + m.stackCount, 0);
|
||||||
|
|
||||||
|
party.forEach(pokemon => {
|
||||||
|
const nextCount = pokemon.getHeldItems()
|
||||||
|
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||||
|
.reduce((v, m) => v + m.stackCount, 0);
|
||||||
|
if (nextCount > count) {
|
||||||
|
mostHeldItemsPokemon = pokemon;
|
||||||
|
count = nextCount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender());
|
||||||
|
|
||||||
|
const items = mostHeldItemsPokemon.getHeldItems();
|
||||||
|
|
||||||
|
// Shuffles Berries (if they have any)
|
||||||
|
let numBerries = 0;
|
||||||
|
items.filter(m => m instanceof BerryModifier)
|
||||||
|
.forEach(m => {
|
||||||
|
numBerries += m.stackCount;
|
||||||
|
scene.removeModifier(m);
|
||||||
|
});
|
||||||
|
|
||||||
|
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
|
||||||
|
|
||||||
|
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||||
|
let numUltra = 0;
|
||||||
|
let numRogue = 0;
|
||||||
|
items.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
|
||||||
|
.forEach(m => {
|
||||||
|
const type = m.type.withTierFromPool();
|
||||||
|
const tier = type.tier ?? ModifierTier.ULTRA;
|
||||||
|
if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) {
|
||||||
|
numUltra += m.stackCount;
|
||||||
|
scene.removeModifier(m);
|
||||||
|
} else if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||||
|
numRogue += m.stackCount;
|
||||||
|
scene.removeModifier(m);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
generateItemsOfTier(scene, mostHeldItemsPokemon, numUltra, ModifierTier.ULTRA);
|
||||||
|
generateItemsOfTier(scene, mostHeldItemsPokemon, numRogue, ModifierTier.ROGUE);
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
})
|
||||||
|
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Play animations
|
||||||
|
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||||
|
background.playWithoutTargets(scene, 230, 40, 2);
|
||||||
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withOption(
|
||||||
|
MysteryEncounterOptionBuilder
|
||||||
|
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||||
|
.withDialogue({
|
||||||
|
buttonLabel: `${namespace}.option.3.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.3.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.3.selected`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.3.selected_2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.3.selected_3`,
|
||||||
|
speaker: `${namespace}.speaker`
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Swap player's types on all party pokemon
|
||||||
|
// If a Pokemon had a single type prior, they will still have a single type after
|
||||||
|
for (const pokemon of scene.getParty()) {
|
||||||
|
const originalTypes = pokemon.getTypes(false, false, true);
|
||||||
|
|
||||||
|
// If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type
|
||||||
|
// Makes the "randomness" of the shuffle slightly less punishing
|
||||||
|
let priorityTypes = pokemon.moveset
|
||||||
|
.filter(move => move && !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS)
|
||||||
|
.map(move => move!.getMove().type);
|
||||||
|
if (priorityTypes?.length > 0) {
|
||||||
|
priorityTypes = [...new Set(priorityTypes)];
|
||||||
|
randSeedShuffle(priorityTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
let newTypes;
|
||||||
|
if (!originalTypes || originalTypes.length < 1) {
|
||||||
|
newTypes = priorityTypes?.length > 0 ? [priorityTypes.pop()] : [(randSeedInt(18) as Type)];
|
||||||
|
} else {
|
||||||
|
newTypes = originalTypes.map(m => {
|
||||||
|
if (priorityTypes?.length > 0) {
|
||||||
|
const ret = priorityTypes.pop();
|
||||||
|
randSeedShuffle(priorityTypes);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return randSeedInt(18) as Type;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pokemon.mysteryEncounterData) {
|
||||||
|
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, undefined, undefined, newTypes);
|
||||||
|
} else {
|
||||||
|
pokemon.mysteryEncounterData.types = newTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
})
|
||||||
|
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||||
|
// Play animations
|
||||||
|
const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon()!, scene.getPlayerPokemon());
|
||||||
|
background.playWithoutTargets(scene, 230, 40, 2);
|
||||||
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withOutroDialogue([
|
||||||
|
{
|
||||||
|
text: `${namespace}.outro`,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
async function handleSwapAbility(scene: BattleScene) {
|
||||||
|
return new Promise<boolean>(async resolve => {
|
||||||
|
await showEncounterDialogue(scene, `${namespace}.option.1.apply_ability_dialogue`, `${namespace}.speaker`);
|
||||||
|
await showEncounterText(scene, `${namespace}.option.1.apply_ability_message`);
|
||||||
|
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
|
displayYesNoOptions(scene, resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayYesNoOptions(scene: BattleScene, resolve) {
|
||||||
|
showEncounterText(scene, `${namespace}.option.1.ability_prompt`, 500, false);
|
||||||
|
const fullOptions = [
|
||||||
|
{
|
||||||
|
label: i18next.t("menu:yes"),
|
||||||
|
handler: () => {
|
||||||
|
onYesAbilitySwap(scene, resolve);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18next.t("menu:no"),
|
||||||
|
handler: () => {
|
||||||
|
resolve(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const config: OptionSelectConfig = {
|
||||||
|
options: fullOptions,
|
||||||
|
maxOptions: 7,
|
||||||
|
yOffset: 0
|
||||||
|
};
|
||||||
|
scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onYesAbilitySwap(scene: BattleScene, resolve) {
|
||||||
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||||
|
// Do ability swap
|
||||||
|
if (!pokemon.mysteryEncounterData) {
|
||||||
|
pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(undefined, Abilities.AERILATE);
|
||||||
|
}
|
||||||
|
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
|
||||||
|
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPokemonNotSelected = () => {
|
||||||
|
scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||||
|
displayYesNoOptions(scene, resolve);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItems: integer, tier: ModifierTier | "Berries") {
|
||||||
|
// These pools have to be defined at runtime so that modifierTypes exist
|
||||||
|
// Pools have instances of the modifier type equal to the max stacks that modifier can be applied to any one pokemon
|
||||||
|
// This is to prevent "over-generating" a random item of a certain type during item swaps
|
||||||
|
const ultraPool = [
|
||||||
|
[modifierTypes.REVIVER_SEED, 1],
|
||||||
|
[modifierTypes.GOLDEN_PUNCH, 5],
|
||||||
|
[modifierTypes.ATTACK_TYPE_BOOSTER, 99],
|
||||||
|
[modifierTypes.QUICK_CLAW, 3],
|
||||||
|
[modifierTypes.WIDE_LENS, 3]
|
||||||
|
];
|
||||||
|
|
||||||
|
const roguePool = [
|
||||||
|
[modifierTypes.LEFTOVERS, 4],
|
||||||
|
[modifierTypes.SHELL_BELL, 4],
|
||||||
|
[modifierTypes.SOUL_DEW, 10],
|
||||||
|
[modifierTypes.SOOTHE_BELL, 3],
|
||||||
|
[modifierTypes.SCOPE_LENS, 1],
|
||||||
|
[modifierTypes.BATON, 1],
|
||||||
|
[modifierTypes.FOCUS_BAND, 5],
|
||||||
|
[modifierTypes.KINGS_ROCK, 3],
|
||||||
|
[modifierTypes.GRIP_CLAW, 5]
|
||||||
|
];
|
||||||
|
|
||||||
|
const berryPool = [
|
||||||
|
[BerryType.APICOT, 3],
|
||||||
|
[BerryType.ENIGMA, 2],
|
||||||
|
[BerryType.GANLON, 3],
|
||||||
|
[BerryType.LANSAT, 3],
|
||||||
|
[BerryType.LEPPA, 2],
|
||||||
|
[BerryType.LIECHI, 3],
|
||||||
|
[BerryType.LUM, 2],
|
||||||
|
[BerryType.PETAYA, 3],
|
||||||
|
[BerryType.SALAC, 2],
|
||||||
|
[BerryType.SITRUS, 2],
|
||||||
|
[BerryType.STARF, 3]
|
||||||
|
];
|
||||||
|
|
||||||
|
let pool: any[];
|
||||||
|
if (tier === "Berries") {
|
||||||
|
pool = berryPool;
|
||||||
|
} else {
|
||||||
|
pool = tier === ModifierTier.ULTRA ? ultraPool : roguePool;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < numItems; i++) {
|
||||||
|
const randIndex = randSeedInt(pool.length);
|
||||||
|
const newItemType = pool[randIndex];
|
||||||
|
let newMod;
|
||||||
|
if (tier === "Berries") {
|
||||||
|
newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType;
|
||||||
|
} else {
|
||||||
|
newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType;
|
||||||
|
}
|
||||||
|
applyModifierTypeToPlayerPokemon(scene, pokemon, newMod);
|
||||||
|
// Decrement max stacks and remove from pool if at max
|
||||||
|
newItemType[1]--;
|
||||||
|
if (newItemType[1] <= 0) {
|
||||||
|
pool.splice(randIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||||
|
import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
|
||||||
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { Nature } from "#app/data/nature";
|
||||||
|
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
|
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
|
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import { BattleStat } from "#app/data/battle-stat";
|
||||||
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
|
import { BerryType } from "#enums/berry-type";
|
||||||
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
import { StatChangePhase } from "#app/phases/stat-change-phase";
|
||||||
|
|
||||||
|
/** the i18n namespace for the encounter */
|
||||||
|
const namespace = "mysteryEncounter:theStrongStuff";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Strong Stuff encounter.
|
||||||
|
* @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/54 | GitHub Issue #54}
|
||||||
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
||||||
|
*/
|
||||||
|
export const TheStrongStuffEncounter: MysteryEncounter =
|
||||||
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
||||||
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||||
|
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
|
||||||
|
.withHideWildIntroMessage(true)
|
||||||
|
.withAutoHideIntroVisuals(false)
|
||||||
|
.withIntroSpriteConfigs([
|
||||||
|
{
|
||||||
|
spriteKey: "berry_juice",
|
||||||
|
fileRoot: "items",
|
||||||
|
hasShadow: true,
|
||||||
|
isItem: true,
|
||||||
|
scale: 1.5,
|
||||||
|
x: -15,
|
||||||
|
y: 3,
|
||||||
|
disableAnimation: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
spriteKey: Species.SHUCKLE.toString(),
|
||||||
|
fileRoot: "pokemon",
|
||||||
|
hasShadow: true,
|
||||||
|
repeat: true,
|
||||||
|
scale: 1.5,
|
||||||
|
x: 20,
|
||||||
|
y: 10,
|
||||||
|
yShadow: 7
|
||||||
|
},
|
||||||
|
]) // Set in onInit()
|
||||||
|
.withIntroDialogue([
|
||||||
|
{
|
||||||
|
text: `${namespace}.intro`,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.withOnInit((scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
|
||||||
|
// Calculate boss mon
|
||||||
|
const config: EnemyPartyConfig = {
|
||||||
|
levelAdditiveMultiplier: 1,
|
||||||
|
disableSwitch: true,
|
||||||
|
pokemonConfigs: [
|
||||||
|
{
|
||||||
|
species: getPokemonSpecies(Species.SHUCKLE),
|
||||||
|
isBoss: true,
|
||||||
|
bossSegments: 5,
|
||||||
|
mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
|
||||||
|
nature: Nature.BOLD,
|
||||||
|
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
|
||||||
|
modifierConfigs: [
|
||||||
|
{
|
||||||
|
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
|
||||||
|
stackCount: 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||||
|
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||||
|
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.stat_boost`);
|
||||||
|
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
encounter.enemyPartyConfigs = [config];
|
||||||
|
|
||||||
|
loadCustomMovesForEncounter(scene, [Moves.GASTRO_ACID, Moves.STEALTH_ROCK]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.withTitle(`${namespace}.title`)
|
||||||
|
.withDescription(`${namespace}.description`)
|
||||||
|
.withQuery(`${namespace}.query`)
|
||||||
|
.withSimpleOption(
|
||||||
|
{
|
||||||
|
buttonLabel: `${namespace}.option.1.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.1.selected`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
async (scene: BattleScene) => {
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
// Do blackout and hide intro visuals during blackout
|
||||||
|
scene.time.delayedCall(750, () => {
|
||||||
|
transitionMysteryEncounterIntroVisuals(scene, true, true, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -20 to all base stats of highest BST, +10 to all base stats of rest of party
|
||||||
|
// Get highest BST mon
|
||||||
|
const party = scene.getParty();
|
||||||
|
let highestBst: PlayerPokemon | null = null;
|
||||||
|
let statTotal = 0;
|
||||||
|
for (const pokemon of party) {
|
||||||
|
if (!highestBst) {
|
||||||
|
highestBst = pokemon;
|
||||||
|
statTotal = pokemon.getSpeciesForm().getBaseStatTotal();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = pokemon.getSpeciesForm().getBaseStatTotal();
|
||||||
|
if (total > statTotal) {
|
||||||
|
highestBst = pokemon;
|
||||||
|
statTotal = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!highestBst) {
|
||||||
|
highestBst = party[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyPlayerPokemonBST(highestBst, -20);
|
||||||
|
for (const pokemon of party) {
|
||||||
|
if (highestBst.id === pokemon.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyPlayerPokemonBST(pokemon, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
encounter.setDialogueToken("highBstPokemon", highestBst.getNameToRender());
|
||||||
|
await showEncounterText(scene, `${namespace}.option.1.selected_2`, undefined, true);
|
||||||
|
|
||||||
|
setEncounterRewards(scene, { fillRemaining: true });
|
||||||
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.withSimpleOption(
|
||||||
|
{
|
||||||
|
buttonLabel: `${namespace}.option.2.label`,
|
||||||
|
buttonTooltip: `${namespace}.option.2.tooltip`,
|
||||||
|
selected: [
|
||||||
|
{
|
||||||
|
text: `${namespace}.option.2.selected`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
async (scene: BattleScene) => {
|
||||||
|
// Pick battle
|
||||||
|
const encounter = scene.currentBattle.mysteryEncounter;
|
||||||
|
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SOUL_DEW], fillRemaining: true });
|
||||||
|
encounter.startOfBattleEffects.push(
|
||||||
|
{
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
|
targets: [BattlerIndex.PLAYER],
|
||||||
|
move: new PokemonMove(Moves.GASTRO_ACID),
|
||||||
|
ignorePp: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceBattlerIndex: BattlerIndex.ENEMY,
|
||||||
|
targets: [BattlerIndex.PLAYER],
|
||||||
|
move: new PokemonMove(Moves.STEALTH_ROCK),
|
||||||
|
ignorePp: true
|
||||||
|
});
|
||||||
|
|
||||||
|
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||||
|
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build();
|
|
@ -0,0 +1,34 @@
|
||||||
|
export const clowningAroundDialogue = {
|
||||||
|
intro: "It's...@d{64} a clown?",
|
||||||
|
speaker: "Clown",
|
||||||
|
intro_dialogue: "Bumbling buffoon, brace for a brilliant battle!\nYou'll be beaten by this brawling busker!",
|
||||||
|
title: "Clowning Around",
|
||||||
|
description: "Something is off about this encounter. The clown seems eager to goad you into a battle, but to what end?\n\nThe Blacephalon is especially strange, like it has @[TOOLTIP_TITLE]{weird types and ability.}",
|
||||||
|
query: "What will you do?",
|
||||||
|
option: {
|
||||||
|
1: {
|
||||||
|
label: "Battle the Clown",
|
||||||
|
tooltip: "(-) Strange Battle\n(?) Affects Pokémon Abilities",
|
||||||
|
selected: "Your pitiful Pokémon are poised for a pathetic performance!",
|
||||||
|
apply_ability_dialogue: "A sensational showcase!\nYour savvy suits a sensational skill as spoils!",
|
||||||
|
apply_ability_message: "The clown is offering to permanently Skill Swap one of your Pokémon's ability to {{ability}}!",
|
||||||
|
ability_prompt: "Would you like to permanently teach a Pokémon the {{ability}} ability?",
|
||||||
|
ability_gained: "@s{level_up_fanfare}{{chosenPokemon}} gained the {{ability}} ability!"
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
label: "Remain Unprovoked",
|
||||||
|
tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Items",
|
||||||
|
selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
|
||||||
|
selected_2: "The clown's Blacephalon uses Trick!\nAll of your {{switchPokemon}}'s items were randomly swapped!",
|
||||||
|
selected_3: "Flustered fool, fall for my flawless deception!",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
label: "Return the Insults",
|
||||||
|
tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Types",
|
||||||
|
selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
|
||||||
|
selected_2: "The clown's Blacephalon uses a strange move!\nAll of your team's types were randomly swapped!",
|
||||||
|
selected_3: "Flustered fool, fall for my flawless deception!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outro: "The clown and his cohorts\ndisappear in a puff of smoke."
|
||||||
|
};
|
Loading…
Reference in New Issue