pokerogue/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts
2025-06-15 20:51:31 +02:00

316 lines
11 KiB
TypeScript

import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initBattleWithEnemyConfig,
loadCustomMovesForEncounter,
leaveEncounterWithoutBattle,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import {
AbilityRequirement,
CombinationPokemonRequirement,
TypeRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender";
import { PokemonType } from "#enums/pokemon-type";
import { BattlerIndex } from "#enums/battler-index";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { MoveId } from "#enums/move-id";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { StatusEffect } from "#enums/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import {
applyAbilityOverrideToPokemon,
applyDamageToPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Stat } from "#enums/stat";
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
import { getNewHeldItemFromCategory } from "#app/items/held-item-pool";
import { allHeldItems } from "#app/items/all-held-items";
import { allAbilities } from "#app/data/data-lists";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/fieryFallout";
/**
* Damage percentage taken when suffering the heat.
* Can be a number between `0` - `100`.
* The higher the more damage taken (100% = instant KO).
*/
const DAMAGE_PERCENTAGE: number = 20;
/**
* Fiery Fallout encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3814 | GitHub Issue #3814}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.FIERY_FALLOUT,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(40, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withCatchAllowed(true)
.withIntroSpriteConfigs([]) // Set in onInit()
.withAnimations(EncounterAnim.MAGMA_BG, EncounterAnim.MAGMA_SPOUT)
.withAutoHideIntroVisuals(false)
.withFleeAllowed(false)
.withIntroDialogue([
{
text: `${namespace}:intro`,
},
])
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mons
const volcaronaSpecies = getPokemonSpecies(SpeciesId.VOLCARONA);
const config: EnemyPartyConfig = {
pokemonConfigs: [
{
species: volcaronaSpecies,
isBoss: false,
gender: Gender.MALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.SPD],
1,
);
},
},
{
species: volcaronaSpecies,
isBoss: false,
gender: Gender.FEMALE,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
[Stat.SPDEF, Stat.SPD],
1,
);
},
},
],
doubleBattle: true,
disableSwitch: true,
};
encounter.enemyPartyConfigs = [config];
// Load hidden Volcarona sprites
encounter.spriteConfigs = [
{
spriteKey: "",
fileRoot: "",
species: SpeciesId.VOLCARONA,
repeat: true,
hidden: true,
hasShadow: true,
x: -20,
startFrame: 20,
},
{
spriteKey: "",
fileRoot: "",
species: SpeciesId.VOLCARONA,
repeat: true,
hidden: true,
hasShadow: true,
x: 20,
},
];
// Load animations/sfx for Volcarona moves
loadCustomMovesForEncounter([MoveId.FIRE_SPIN, MoveId.QUIVER_DANCE]);
const pokemon = globalScene.getEnemyPokemon();
globalScene.arena.trySetWeather(WeatherType.SUNNY, pokemon);
encounter.setDialogueToken("volcaronaName", getPokemonSpecies(SpeciesId.VOLCARONA).getName());
return true;
})
.withOnVisualsStart(() => {
// Play animations
const background = new EncounterBattleAnim(
EncounterAnim.MAGMA_BG,
globalScene.getPlayerPokemon()!,
globalScene.getPlayerPokemon(),
);
background.playWithoutTargets(200, 70, 2, 3);
const animation = new EncounterBattleAnim(
EncounterAnim.MAGMA_SPOUT,
globalScene.getPlayerPokemon()!,
globalScene.getPlayerPokemon(),
);
animation.playWithoutTargets(80, 100, 2);
globalScene.time.delayedCall(600, () => {
animation.playWithoutTargets(-20, 100, 2);
});
globalScene.time.delayedCall(1200, () => {
animation.playWithoutTargets(140, 150, 2);
});
return true;
})
.setLocalizationKey(`${namespace}`)
.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 () => {
// Pick battle
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({ fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem());
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.FIRE_SPIN),
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY_2,
targets: [BattlerIndex.PLAYER_2],
move: new PokemonMove(MoveId.FIRE_SPIN),
ignorePp: true,
},
);
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
},
)
.withSimpleOption(
{
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`,
},
],
},
async () => {
// Damage non-fire types and burn 1 random non-fire type member + give it Heatproof
const encounter = globalScene.currentBattle.mysteryEncounter!;
const nonFireTypes = globalScene
.getPlayerParty()
.filter(p => p.isAllowedInBattle() && !p.getTypes().includes(PokemonType.FIRE));
for (const pkm of nonFireTypes) {
const percentage = DAMAGE_PERCENTAGE / 100;
const damage = Math.floor(pkm.getMaxHp() * percentage);
applyDamageToPokemon(pkm, damage);
}
// Burn random member
const burnable = nonFireTypes.filter(
p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE,
);
if (burnable?.length > 0) {
const roll = randSeedInt(burnable.length);
const chosenPokemon = burnable[roll];
if (chosenPokemon.trySetStatus(StatusEffect.BURN)) {
// Burn applied
encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender());
encounter.setDialogueToken("abilityName", allAbilities[AbilityId.HEATPROOF].name);
queueEncounterMessage(`${namespace}:option.2.target_burned`);
// Also permanently change the burned Pokemon's ability to Heatproof
applyAbilityOverrideToPokemon(chosenPokemon, AbilityId.HEATPROOF);
}
}
// No rewards
leaveEncounterWithoutBattle(true);
},
)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPrimaryPokemonRequirement(
CombinationPokemonRequirement.Some(
new TypeRequirement(PokemonType.FIRE, true, 1),
new AbilityRequirement(FIRE_RESISTANT_ABILITIES, true),
),
) // Will set option3PrimaryName dialogue token automatically
.withDialogue({
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
disabledButtonTooltip: `${namespace}:option.3.disabled_tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`,
},
],
})
.withPreOptionPhase(async () => {
// Do NOT await this, to prevent player from repeatedly pressing options
transitionMysteryEncounterIntroVisuals(false, false, 2000);
})
.withOptionPhase(async () => {
// Fire types help calm the Volcarona
const encounter = globalScene.currentBattle.mysteryEncounter!;
await transitionMysteryEncounterIntroVisuals();
setEncounterRewards({ fillRemaining: true }, undefined, () => {
giveLeadPokemonAttackTypeBoostItem();
});
const primary = encounter.options[2].primaryPokemon!;
setEncounterExp([primary.id], getPokemonSpecies(SpeciesId.VOLCARONA).baseExp * 2);
leaveEncounterWithoutBattle();
})
.build(),
)
.build();
function giveLeadPokemonAttackTypeBoostItem() {
// Give first party pokemon attack type boost item for free at end of battle
const leadPokemon = globalScene.getPlayerParty()?.[0];
if (leadPokemon) {
// Generate type booster held item, default to Charcoal if item fails to generate
let item = getNewHeldItemFromCategory(HeldItemCategoryId.TYPE_ATTACK_BOOSTER, leadPokemon);
if (!item) {
item = HeldItemId.CHARCOAL;
}
leadPokemon.heldItemManager.add(item);
const encounter = globalScene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("itemName", allHeldItems[item].name);
encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender());
queueEncounterMessage(`${namespace}:found_item`);
}
}