first implementation pass at clowning around

This commit is contained in:
ImperialSympathizer 2024-08-05 16:14:05 -04:00
parent 6d2129a3f0
commit afe1015094
22 changed files with 2265 additions and 233 deletions

File diff suppressed because it is too large Load Diff

View File

@ -110,7 +110,8 @@ export enum CommonAnim {
*/ */
export enum EncounterAnim { export enum EncounterAnim {
MAGMA_BG, MAGMA_BG,
MAGMA_SPOUT MAGMA_SPOUT,
SMOKESCREEN
} }
export class AnimConfig { export class AnimConfig {
@ -533,16 +534,16 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> { export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise<void> {
const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim]; const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim];
const encounterAnimNames = Utils.getEnumKeys(EncounterAnim); const encounterAnimNames = Utils.getEnumKeys(EncounterAnim);
const encounterAnimIds = Utils.getEnumValues(EncounterAnim); // const encounterAnimIds = Utils.getEnumValues(EncounterAnim);
const encounterAnimFetches = []; const encounterAnimFetches = [];
for (const anim of anims) { for (const anim of anims) {
if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) { if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) {
continue; continue;
} }
const encounterAnimId = encounterAnimIds[anim]; // const encounterAnimId = encounterAnimIds[anim];
encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`) encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`)
.then(response => response.json()) .then(response => response.json())
.then(cas => encounterAnims.set(encounterAnimId, new AnimConfig(cas)))); .then(cas => encounterAnims.set(anim, new AnimConfig(cas))));
} }
await Promise.allSettled(encounterAnimFetches); await Promise.allSettled(encounterAnimFetches);
} }

View File

@ -62,7 +62,7 @@ export const AnOfferYouCantRefuseEncounter: IMysteryEncounter =
const pokemon = getHighestStatTotalPlayerPokemon(scene, false); const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
const price = scene.getWaveMoneyAmount(10); const price = scene.getWaveMoneyAmount(10);
encounter.setDialogueToken("strongestPokemon", pokemon.name); encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString()); encounter.setDialogueToken("price", price.toString());
// Store pokemon and price // Store pokemon and price

View File

@ -1,181 +0,0 @@
import {
EnemyPartyConfig,
initBattleWithEnemyConfig,
setEncounterRewards,
} 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 } 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 IMysteryEncounter, { 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";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:clowningAround";
/**
* 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: IMysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(10, 180) // waves 10 to 180
.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
},
])
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
text: `${namespace}.intro_dialogue`,
speaker: `${namespace}.speaker`
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Clown trainer is pulled from pool of boss trainers (gym leaders) for the biome
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
const clownTrainerType = TrainerType.HARLEQUIN;
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG));
const clownConfig = trainerConfigs[clownTrainerType].copy();
clownConfig.setPartyTemplates(clownPartyTemplate);
clownConfig.partyTemplateFunc = null; // Overrides party template func
encounter.enemyPartyConfigs.push({
trainerConfig: clownConfig,
pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon
{
species: getPokemonSpecies(Species.MR_MIME),
isBoss: false
},
{
species: getPokemonSpecies(Species.BLACEPHALON),
isBoss: true
},
]
});
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.selected`,
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn battle
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true });
await initBattleWithEnemyConfig(scene, config);
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn hard fight with ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1];
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 100);
return ret;
}
)
.withSimpleOption(
{
buttonLabel: `${namespace}.option.3.label`,
buttonTooltip: `${namespace}.option.3.tooltip`,
selected: [
{
text: `${namespace}.option.selected`,
},
],
},
async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter;
// Spawn brutal fight with ROGUE/ULTRA/GREAT reward (can improve with luck)
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2];
// To avoid player level snowballing from picking this option
encounter.expMultiplier = 0.9;
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true });
// Seed offsets to remove possibility of different trainers having exact same teams
let ret;
scene.executeWithSeedOffset(() => {
ret = initBattleWithEnemyConfig(scene, config);
}, scene.currentBattle.waveIndex * 1000);
return ret;
}
)
.withOutroDialogue([
{
text: `${namespace}.outro`,
},
])
.build();

View File

@ -0,0 +1,469 @@
import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import IMysteryEncounter, { 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: IMysteryEncounter =
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;
// Clown trainer is pulled from pool of boss trainers (gym leaders) for the biome
// They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons
const clownTrainerType = TrainerType.HARLEQUIN;
const clownPartyTemplate = new TrainerPartyCompoundTemplate(
new TrainerPartyTemplate(1, PartyMemberStrength.STRONG),
new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER));
const clownConfig = trainerConfigs[clownTrainerType].copy();
clownConfig.setPartyTemplates(clownPartyTemplate);
clownConfig.setDoubleOnly();
clownConfig.partyTemplateFunc = null; // Overrides party template func
// 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),
ability: ability,
mysteryEncounterData: new MysteryEncounterPokemonData(null, null, null, [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]);
// These have to be defined at runtime so that modifierTypes exist
encounter.misc.RANDOM_ULTRA_POOL = [
modifierTypes.REVIVER_SEED,
modifierTypes.GOLDEN_PUNCH,
modifierTypes.ATTACK_TYPE_BOOSTER,
modifierTypes.QUICK_CLAW,
modifierTypes.WIDE_LENS,
modifierTypes.WHITE_HERB
];
encounter.misc.RANDOM_ROGUE_POOL = [
modifierTypes.LEFTOVERS,
modifierTypes.SHELL_BELL,
modifierTypes.SOUL_DEW,
modifierTypes.SOOTHE_BELL,
modifierTypes.SCOPE_LENS,
modifierTypes.BATON,
modifierTypes.FOCUS_BAND,
modifierTypes.KINGS_ROCK,
modifierTypes.GRIP_CLAW
];
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
new MysteryEncounterOptionBuilder()
.withOptionMode(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, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], 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 scene.ui.setMode(Mode.MESSAGE);
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(
new MysteryEncounterOptionBuilder()
.withOptionMode(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)
const berries = items.filter(m => m instanceof BerryModifier);
berries.forEach(berry => {
const stackCount = berry.stackCount;
scene.removeModifier(berry);
const newBerry = generateModifierTypeOption(scene, modifierTypes.BERRY, [randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType]).type as BerryModifierType;
for (let i = 0; i < stackCount; i++) {
applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newBerry);
}
});
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
const transferableItems = items.filter(m => m.isTransferrable && !(m instanceof BerryModifier));
transferableItems.forEach(transferableItem => {
const stackCount = transferableItem.stackCount;
transferableItem.type.withTierFromPool();
// Lucky Eggs and other items that do not appear in item pools are treated as Ultra rarity
const tier = transferableItem.type.tier ?? ModifierTier.ULTRA;
if (tier === ModifierTier.ULTRA) {
scene.removeModifier(transferableItem);
for (let i = 0; i < stackCount; i++) {
const newItemType = encounter.misc.RANDOM_ULTRA_POOL[randSeedInt(encounter.misc.RANDOM_ULTRA_POOL.length)];
const newMod = generateModifierTypeOption(scene, newItemType).type as PokemonHeldItemModifierType;
applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newMod);
}
} else if (tier === ModifierTier.ROGUE) {
scene.removeModifier(transferableItem);
for (let i = 0; i < stackCount; i++) {
const newItemType = encounter.misc.RANDOM_ROGUE_POOL[randSeedInt(encounter.misc.RANDOM_ROGUE_POOL.length)];
const newMod = generateModifierTypeOption(scene, newItemType).type as PokemonHeldItemModifierType;
applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newMod);
}
}
});
})
.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(
new MysteryEncounterOptionBuilder()
.withOptionMode(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 => !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(null, null, null, 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(null, Abilities.AERILATE);
}
pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability;
scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
resolve(true);
};
const onPokemonNotSelected = () => {
scene.ui.setMode(Mode.MESSAGE).then(() => {
displayYesNoOptions(scene, resolve);
});
};
selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected);
}

View File

@ -127,7 +127,7 @@ export const DarkDealEncounter: IMysteryEncounter =
const removedPokemon = getRandomPlayerPokemon(scene, false, true); const removedPokemon = getRandomPlayerPokemon(scene, false, true);
scene.removePokemonFromPlayerParty(removedPokemon); scene.removePokemonFromPlayerParty(removedPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.name); scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.getNameToRender());
// Store removed pokemon types // Store removed pokemon types
scene.currentBattle.mysteryEncounter.misc = [ scene.currentBattle.mysteryEncounter.misc = [

View File

@ -95,7 +95,7 @@ export const FieldTripEncounter: IMysteryEncounter =
]; ];
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50); setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
} else { } else {
encounter.setDialogueToken("pokeName", pokemon.name); encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName()); encounter.setDialogueToken("move", move.getName());
encounter.options[0].dialogue.selected = [ encounter.options[0].dialogue.selected = [
{ {
@ -187,7 +187,7 @@ export const FieldTripEncounter: IMysteryEncounter =
]; ];
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50); setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
} else { } else {
encounter.setDialogueToken("pokeName", pokemon.name); encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName()); encounter.setDialogueToken("move", move.getName());
encounter.options[1].dialogue.selected = [ encounter.options[1].dialogue.selected = [
{ {
@ -273,7 +273,7 @@ export const FieldTripEncounter: IMysteryEncounter =
]; ];
setEncounterExp(scene, scene.getParty().map((p) => p.id), 50); setEncounterExp(scene, scene.getParty().map((p) => p.id), 50);
} else { } else {
encounter.setDialogueToken("pokeName", pokemon.name); encounter.setDialogueToken("pokeName", pokemon.getNameToRender());
encounter.setDialogueToken("move", move.getName()); encounter.setDialogueToken("move", move.getName());
encounter.options[2].dialogue.selected = [ encounter.options[2].dialogue.selected = [
{ {

View File

@ -191,7 +191,7 @@ export const FieryFalloutEncounter: IMysteryEncounter =
const chosenPokemon = burnable[roll]; const chosenPokemon = burnable[roll];
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
// Burn applied // Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.name); encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}.option.2.target_burned`); queueEncounterMessage(scene, `${namespace}.option.2.target_burned`);
} }
} }
@ -245,7 +245,7 @@ function giveLeadPokemonCharcoal(scene: BattleScene) {
if (leadPokemon) { if (leadPokemon) {
const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]).type as AttackTypeBoosterModifierType; const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]).type as AttackTypeBoosterModifierType;
applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal); applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal);
scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.name); scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
queueEncounterMessage(scene, `${namespace}.found_charcoal`); queueEncounterMessage(scene, `${namespace}.found_charcoal`);
} }
} }

View File

@ -113,7 +113,7 @@ export const MysteriousChestEncounter: IMysteryEncounter =
); );
koPlayerPokemon(scene, highestLevelPokemon); koPlayerPokemon(scene, highestLevelPokemon);
scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.name); scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
// Show which Pokemon was KOed, then leave encounter with no rewards // Show which Pokemon was KOed, then leave encounter with no rewards
// Does this synchronously so that game over doesn't happen over result message // Does this synchronously so that game over doesn't happen over result message
await showEncounterText(scene, `${namespace}.option.1.bad`).then(() => { await showEncounterText(scene, `${namespace}.option.1.bad`).then(() => {

View File

@ -92,7 +92,7 @@ export const ThePokemonSalesmanEncounter: IMysteryEncounter =
encounter.options[0].dialogue.buttonTooltip = `${namespace}.option.1.tooltip_shiny`; encounter.options[0].dialogue.buttonTooltip = `${namespace}.option.1.tooltip_shiny`;
} }
const price = scene.getWaveMoneyAmount(priceMultiplier); const price = scene.getWaveMoneyAmount(priceMultiplier);
encounter.setDialogueToken("purchasePokemon", pokemon.name); encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString()); encounter.setDialogueToken("price", price.toString());
encounter.misc = { encounter.misc = {
price: price, price: price,

View File

@ -16,6 +16,7 @@ import { BattleStat } from "#app/data/battle-stat";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:theStrongStuff"; const namespace = "mysteryEncounter:theStrongStuff";
@ -70,7 +71,7 @@ export const TheStrongStuffEncounter: IMysteryEncounter =
species: getPokemonSpecies(Species.SHUCKLE), species: getPokemonSpecies(Species.SHUCKLE),
isBoss: true, isBoss: true,
bossSegments: 5, bossSegments: 5,
spriteScale: 1.5, mysteryEncounterData: new MysteryEncounterPokemonData(1.5),
nature: Nature.BOLD, nature: Nature.BOLD,
moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER], moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER],
modifierTypes: [ modifierTypes: [
@ -147,7 +148,7 @@ export const TheStrongStuffEncounter: IMysteryEncounter =
modifyPlayerPokemonBST(pokemon, 10); modifyPlayerPokemonBST(pokemon, 10);
} }
encounter.setDialogueToken("highBstPokemon", highestBst.name); encounter.setDialogueToken("highBstPokemon", highestBst.getNameToRender());
await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, true); await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, true);
setEncounterRewards(scene, { fillRemaining: true }); setEncounterRewards(scene, { fillRemaining: true });

View File

@ -0,0 +1,16 @@
import { Abilities } from "#enums/abilities";
import { Type } from "#app/data/type";
export class MysteryEncounterPokemonData {
public spriteScale: number;
public ability: Abilities;
public passive: Abilities;
public types: Type[] = [];
constructor(spriteScale?: number, ability?: Abilities, passive?: Abilities, types?: Type[]) {
this.spriteScale = spriteScale;
this.ability = ability;
this.passive = passive;
this.types = types;
}
}

View File

@ -305,7 +305,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
} }
} }
if (this.primaryPokemon?.length > 0) { if (this.primaryPokemon?.length > 0) {
this.setDialogueToken("primaryName", this.primaryPokemon.name); this.setDialogueToken("primaryName", this.primaryPokemon.getNameToRender());
for (const req of this.primaryPokemonRequirements) { for (const req of this.primaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.primaryPokemon); const value = req.getDialogueToken(scene, this.primaryPokemon);
@ -316,7 +316,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
} }
} }
if (this.secondaryPokemonRequirements?.length > 0 && this.secondaryPokemon?.length > 0) { if (this.secondaryPokemonRequirements?.length > 0 && this.secondaryPokemon?.length > 0) {
this.setDialogueToken("secondaryName", this.secondaryPokemon[0].name); this.setDialogueToken("secondaryName", this.secondaryPokemon[0].getNameToRender());
for (const req of this.secondaryPokemonRequirements) { for (const req of this.secondaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, this.secondaryPokemon[0]); const value = req.getDialogueToken(scene, this.secondaryPokemon[0]);
@ -342,7 +342,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
} }
} }
if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) { if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) {
this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.name); this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.getNameToRender());
for (const req of opt.primaryPokemonRequirements) { for (const req of opt.primaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.primaryPokemon); const value = req.getDialogueToken(scene, opt.primaryPokemon);
@ -353,7 +353,7 @@ export default class IMysteryEncounter implements IMysteryEncounter {
} }
} }
if (opt.secondaryPokemonRequirements?.length > 0 && opt.secondaryPokemon?.length > 0) { if (opt.secondaryPokemonRequirements?.length > 0 && opt.secondaryPokemon?.length > 0) {
this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].name); this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].getNameToRender());
for (const req of opt.secondaryPokemonRequirements) { for (const req of opt.secondaryPokemonRequirements) {
if (!req.invertQuery) { if (!req.invertQuery) {
const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]); const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]);

View File

@ -21,7 +21,7 @@ import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounter
import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter"; import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter";
import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter"; import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter";
import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter"; import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter";
import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowing-around-encounter"; import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter";
// Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / 256 // 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; export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1;

View File

@ -30,6 +30,8 @@ import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-co
import PokemonSpecies from "#app/data/pokemon-species"; import PokemonSpecies from "#app/data/pokemon-species";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { Egg, IEggOptions } from "#app/data/egg"; import { Egg, IEggOptions } from "#app/data/egg";
import { Abilities } from "#enums/abilities";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
/** /**
* Animates exclamation sprite over trainer's head at start of encounter * Animates exclamation sprite over trainer's head at start of encounter
@ -63,7 +65,7 @@ export interface EnemyPokemonConfig {
isBoss: boolean; isBoss: boolean;
bossSegments?: number; bossSegments?: number;
bossSegmentModifier?: number; // Additive to the determined segment number bossSegmentModifier?: number; // Additive to the determined segment number
spriteScale?: number; mysteryEncounterData?: MysteryEncounterPokemonData;
formIndex?: number; formIndex?: number;
level?: number; level?: number;
gender?: Gender; gender?: Gender;
@ -71,6 +73,8 @@ export interface EnemyPokemonConfig {
moveSet?: Moves[]; moveSet?: Moves[];
nature?: Nature; nature?: Nature;
ivs?: [integer, integer, integer, integer, integer, integer]; ivs?: [integer, integer, integer, integer, integer, integer];
ability?: Abilities;
shiny?: boolean;
/** Can set just the status, or pass a timer on the status turns */ /** Can set just the status, or pass a timer on the status turns */
status?: StatusEffect | [StatusEffect, number]; status?: StatusEffect | [StatusEffect, number];
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
@ -210,11 +214,14 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemyPokemon.formIndex = config.formIndex; enemyPokemon.formIndex = config.formIndex;
} }
// Set scale // Set shiny
if (!isNullOrUndefined(config.spriteScale)) { if (!isNullOrUndefined(config.shiny)) {
enemyPokemon.mysteryEncounterData = { enemyPokemon.shiny = config.shiny;
spriteScale: config.spriteScale }
};
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
if (!isNullOrUndefined(config.mysteryEncounterData)) {
enemyPokemon.mysteryEncounterData = config.mysteryEncounterData;
} }
// Set Boss // Set Boss
@ -252,6 +259,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// Set summon data fields // Set summon data fields
// Set ability
if (!isNullOrUndefined(config.ability)) {
enemyPokemon.summonData.ability = config.ability;
}
// Set gender // Set gender
if (!isNullOrUndefined(config.gender)) { if (!isNullOrUndefined(config.gender)) {
enemyPokemon.gender = config.gender; enemyPokemon.gender = config.gender;
@ -381,7 +393,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
const pokemon = scene.getParty()[slotIndex]; const pokemon = scene.getParty()[slotIndex];
const secondaryOptions = onPokemonSelected(pokemon); const secondaryOptions = onPokemonSelected(pokemon);
if (!secondaryOptions) { if (!secondaryOptions) {
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.name); scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
resolve(true); resolve(true);
return; return;
} }
@ -395,7 +407,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p
const onSelect = option.handler; const onSelect = option.handler;
option.handler = () => { option.handler = () => {
onSelect(); onSelect();
scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.name); scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender());
resolve(true); resolve(true);
return true; return true;
}; };

View File

@ -20,10 +20,6 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
export interface MysteryEncounterPokemonData {
spriteScale?: number
}
export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): { spriteKey: string, fileRoot: string } { 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); const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0); const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0);
@ -447,7 +443,7 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number,
scene.currentBattle.lastUsedPokeball = pokeballType; scene.currentBattle.lastUsedPokeball = pokeballType;
removePb(scene, pokeball); removePb(scene, pokeball);
scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.name }), null, () => resolve(), null, true); scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.getNameToRender() }), null, () => resolve(), null, true);
}); });
} }
@ -516,7 +512,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => { Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => {
if (scene.getParty().length === 6) { if (scene.getParty().length === 6) {
const promptRelease = () => { const promptRelease = () => {
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => { scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => {
scene.pokemonInfoContainer.makeRoomForConfirmUi(); scene.pokemonInfoContainer.makeRoomForConfirmUi();
scene.ui.setMode(Mode.CONFIRM, () => { scene.ui.setMode(Mode.CONFIRM, () => {
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => { scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
@ -544,7 +540,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
}; };
if (showCatchObtainMessage) { if (showCatchObtainMessage) {
scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.name }), null, doPokemonCatchMenu, 0, true); scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.getNameToRender() }), null, doPokemonCatchMenu, 0, true);
} else { } else {
doPokemonCatchMenu(); doPokemonCatchMenu();
} }
@ -581,7 +577,7 @@ export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon):
onComplete: () => { onComplete: () => {
pokemon.setVisible(false); pokemon.setVisible(false);
scene.field.remove(pokemon, true); scene.field.remove(pokemon, true);
showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.name }), 600, false) showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), 600, false)
.then(() => { .then(() => {
resolve(); resolve();
}); });
@ -604,7 +600,7 @@ export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise
onComplete: () => { onComplete: () => {
pokemon.setVisible(false); pokemon.setVisible(false);
scene.field.remove(pokemon, true); scene.field.remove(pokemon, true);
showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.name }), 600, false) showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), 600, false)
.then(() => { .then(() => {
resolve(); resolve();
}); });

View File

@ -64,5 +64,5 @@ export enum BattlerTagType {
STOCKPILING = "STOCKPILING", STOCKPILING = "STOCKPILING",
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE", RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
ALWAYS_GET_HIT = "ALWAYS_GET_HIT", ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON" // Provides effects on post-summon for MEs MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", // Provides effects on post-summon for MEs
} }

View File

@ -51,7 +51,7 @@ import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages.js"; import { getPokemonNameWithAffix } from "#app/messages.js";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
export enum FieldPosition { export enum FieldPosition {
CENTER, CENTER,
@ -187,6 +187,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionVariant = dataSource.fusionVariant || 0; this.fusionVariant = dataSource.fusionVariant || 0;
this.fusionGender = dataSource.fusionGender; this.fusionGender = dataSource.fusionGender;
this.fusionLuck = dataSource.fusionLuck; this.fusionLuck = dataSource.fusionLuck;
this.mysteryEncounterData = dataSource.mysteryEncounterData;
} else { } else {
this.id = Utils.randSeedInt(4294967296); this.id = Utils.randSeedInt(4294967296);
this.ivs = ivs || Utils.getIvsFromId(this.id); this.ivs = ivs || Utils.getIvsFromId(this.id);
@ -233,6 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0);
this.fusionLuck = this.luck; this.fusionLuck = this.luck;
this.mysteryEncounterData = new MysteryEncounterPokemonData();
} }
this.generateName(); this.generateName();
@ -927,7 +929,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
if (!types.length || !includeTeraType) { if (!types.length || !includeTeraType) {
if (!ignoreOverride && this.summonData?.types) { if (this.mysteryEncounterData?.types?.length > 0) {
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
this.mysteryEncounterData.types.forEach(t => types.push(t));
} else if (!ignoreOverride && this.summonData?.types) {
this.summonData.types.forEach(t => types.push(t)); this.summonData.types.forEach(t => types.push(t));
} else { } else {
const speciesForm = this.getSpeciesForm(ignoreOverride); const speciesForm = this.getSpeciesForm(ignoreOverride);
@ -994,6 +999,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) { if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) {
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
} }
if (this.mysteryEncounterData?.ability) {
return allAbilities[this.mysteryEncounterData.ability];
}
if (this.isFusion()) { if (this.isFusion()) {
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
} }
@ -1018,6 +1026,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) {
return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE];
} }
if (this.mysteryEncounterData?.passive) {
return allAbilities[this.mysteryEncounterData.passive];
}
let starterSpeciesId = this.species.speciesId; let starterSpeciesId = this.species.speciesId;
while (pokemonPrevolutions.hasOwnProperty(starterSpeciesId)) { while (pokemonPrevolutions.hasOwnProperty(starterSpeciesId)) {
@ -4059,6 +4070,7 @@ export class PokemonSummonData {
public speciesForm: PokemonSpeciesForm; public speciesForm: PokemonSpeciesForm;
public fusionSpeciesForm: PokemonSpeciesForm; public fusionSpeciesForm: PokemonSpeciesForm;
public ability: Abilities = Abilities.NONE; public ability: Abilities = Abilities.NONE;
public passiveAbility: Abilities = Abilities.NONE;
public gender: Gender; public gender: Gender;
public fusionGender: Gender; public fusionGender: Gender;
public stats: integer[]; public stats: integer[];

View File

@ -1,31 +1,32 @@
export const clowningAroundDialogue = { export const clowningAroundDialogue = {
intro: "It's...@d{64} a clown?", intro: "It's...@d{64} a clown?",
speaker: "Clown", speaker: "Clown",
intro_dialogue: `Bumbling buffoon,\nbrace for a brilliant battle! intro_dialogue: "Bumbling buffoon, brace for a brilliant battle!\nYoull be beaten by this brawling busker!",
$Youll be beaten by this brawling busker!\nBring it!`,
title: "Clowning Around", title: "Clowning Around",
description: "The clown seems eager to goad you into a battle, but to what end?\n\nSomething is off about this encounter.", 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?", query: "What will you do?",
option: { option: {
1: { 1: {
label: "Battle the Clown", label: "Battle the Clown",
tooltip: "(-) Strange Battle\n(?) Affects Pokémon Abilities", tooltip: "(-) Strange Battle\n(?) Affects Pokémon Abilities",
selected: "Your pitiful Pokémon are poised for a pathetic performance!" 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: { 2: {
label: "Remain Unprovoked", label: "Remain Unprovoked",
tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Items", tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Items",
selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!", selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
selected_2: `The clown's Blacephalon uses Trick! selected_2: "The clown's Blacephalon uses Trick!\nAll of your {{switchPokemon}}'s items were randomly swapped!",
All of your {{switchPokemon}}'s items were randomly swapped!`,
selected_3: "Flustered fool, fall for my flawless deception!", selected_3: "Flustered fool, fall for my flawless deception!",
}, },
3: { 3: {
label: "Return the Insults", label: "Return the Insults",
tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Types", tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Types",
selected: "I'm appalled at your absurd antics!\nTaste my temper!", selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
selected_2: `The clown's Blacephalon uses\na move you've never seen before! selected_2: "The clown's Blacephalon uses a strange move!\nAll of your team's types were randomly swapped!",
All of your team's types were randomly swapped!`,
selected_3: "Flustered fool, fall for my flawless deception!", selected_3: "Flustered fool, fall for my flawless deception!",
}, },
}, },

View File

@ -127,9 +127,9 @@ class DefaultOverrides {
// ------------------------- // -------------------------
// 1 to 256, set to null to ignore // 1 to 256, set to null to ignore
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256; readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null;
readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null;
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.CLOWNING_AROUND; readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null;
// ------------------------- // -------------------------
// MODIFIER / ITEM OVERRIDES // MODIFIER / ITEM OVERRIDES

View File

@ -1369,7 +1369,7 @@ export class PostSummonPhase extends PokemonPhase {
} }
this.scene.arena.applyTags(ArenaTrapTag, pokemon); this.scene.arena.applyTags(ArenaTrapTag, pokemon);
// If this is fight or flight mystery encounter and is enemy pokemon summon phase, add enraged tag // If this is mystery encounter and has post summon phase tag, apply post summon effects
if (pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag)) { if (pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag)) {
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
} }

View File

@ -12,6 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
export default class PokemonData { export default class PokemonData {
public id: integer; public id: integer;
@ -54,6 +55,7 @@ export default class PokemonData {
public bossSegments?: integer; public bossSegments?: integer;
public summonData: PokemonSummonData; public summonData: PokemonSummonData;
public mysteryEncounterData: MysteryEncounterPokemonData;
constructor(source: Pokemon | any, forHistory: boolean = false) { constructor(source: Pokemon | any, forHistory: boolean = false) {
const sourcePokemon = source instanceof Pokemon ? source : null; const sourcePokemon = source instanceof Pokemon ? source : null;
@ -108,6 +110,7 @@ export default class PokemonData {
this.status = sourcePokemon.status; this.status = sourcePokemon.status;
if (this.player) { if (this.player) {
this.summonData = sourcePokemon.summonData; this.summonData = sourcePokemon.summonData;
this.mysteryEncounterData = sourcePokemon.mysteryEncounterData;
} }
} }
} else { } else {
@ -137,6 +140,14 @@ export default class PokemonData {
this.summonData.tags = []; this.summonData.tags = [];
} }
} }
this.mysteryEncounterData = new MysteryEncounterPokemonData();
if (!forHistory && source.mysteryEncounterData) {
this.mysteryEncounterData.spriteScale = source.mysteryEncounterData.spriteScale;
this.mysteryEncounterData.ability = source.mysteryEncounterData.ability;
this.mysteryEncounterData.passive = source.mysteryEncounterData.passive;
this.mysteryEncounterData.types = source.mysteryEncounterData.types;
}
} }
} }