pokerogue/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts
Sirz Benjie 1029afcdbf
[Refactor] Remove circular dependencies (part 4) (#5964)
* Add abilityAttr.is methods

* [WIP] move modifier stuff around

* Untangle circular deps from modifiers

* Move unlockables to own file

* Untangle all circular deps outside of MEs

* Move constants in MEs to their own files

* Re-add missing import to battle.ts

* Add necessary overload for getTag

* Add missing type import in weather.ts

* Init modifier types and pools in loading-scene

* Remove stray commented code

* Apply kev's suggestions from code review

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-06-11 19:42:04 -07:00

293 lines
11 KiB
TypeScript

import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierTypeOption,
initBattleWithEnemyConfig,
setEncounterExp,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { randSeedInt } from "#app/utils/common";
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 { MoneyRequirement, WaveModulusRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon } from "#app/field/pokemon";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BiomeId } from "#enums/biome-id";
import { getBiomeKey } from "#app/field/arena";
import { PokemonType } from "#enums/pokemon-type";
import { getPartyLuckValue } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/data/data-lists";
import { TrainerSlot } from "#enums/trainer-slot";
import { BattlerTagType } from "#enums/battler-tag-type";
import { getPokemonNameWithAffix } from "#app/messages";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import {
getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/teleportingHijinks";
const MONEY_COST_MULTIPLIER = 1.75;
const BIOME_CANDIDATES = [
BiomeId.SPACE,
BiomeId.FAIRY_CAVE,
BiomeId.LABORATORY,
BiomeId.ISLAND,
BiomeId.WASTELAND,
BiomeId.DOJO,
];
const MACHINE_INTERFACING_TYPES = [PokemonType.ELECTRIC, PokemonType.STEEL];
/**
* Teleporting Hijinks encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3817 | GitHub Issue #3817}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TeleportingHijinksEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.TELEPORTING_HIJINKS,
)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
.withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
.withAutoHideIntroVisuals(false)
.withCatchAllowed(true)
.withFleeAllowed(false)
.withIntroSpriteConfigs([
{
spriteKey: "teleporting_hijinks_teleporter",
fileRoot: "mystery-encounters",
hasShadow: true,
x: 4,
y: 4,
yShadow: 1,
},
])
.withIntroDialogue([
{
text: `${namespace}:intro`,
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
const price = globalScene.getWaveMoneyAmount(MONEY_COST_MULTIPLIER);
encounter.setDialogueToken("price", price.toString());
encounter.misc = {
price,
};
return true;
})
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected`,
},
],
})
.withPreOptionPhase(async () => {
// Update money
updatePlayerMoney(-globalScene.currentBattle.mysteryEncounter!.misc.price, true, false);
})
.withOptionPhase(async () => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
setEncounterRewards({ fillRemaining: true });
await initBattleWithEnemyConfig(config);
})
.build(),
)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withPokemonTypeRequirement(MACHINE_INTERFACING_TYPES, true, 1) // Must have Steel or Electric type
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
disabledButtonTooltip: `${namespace}:option.2.disabled_tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`,
},
],
})
.withOptionPhase(async () => {
const config: EnemyPartyConfig = await doBiomeTransitionDialogueAndBattleInit();
setEncounterRewards({ fillRemaining: true });
setEncounterExp(globalScene.currentBattle.mysteryEncounter!.selectedOption!.primaryPokemon!.id, 100);
await initBattleWithEnemyConfig(config);
})
.build(),
)
.withSimpleOption(
{
buttonLabel: `${namespace}:option.3.label`,
buttonTooltip: `${namespace}:option.3.tooltip`,
selected: [
{
text: `${namespace}:option.3.selected`,
},
],
},
async () => {
// Inspect the Machine
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Init enemy
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(
globalScene.currentBattle.waveIndex,
level,
0,
getPartyLuckValue(globalScene.getPlayerParty()),
true,
);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
pokemonConfigs: [
{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
isBoss: true,
},
],
};
const magnet = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.STEEL])!;
const metalCoat = generateModifierTypeOption(modifierTypes.ATTACK_TYPE_BOOSTER, [PokemonType.ELECTRIC])!;
setEncounterRewards({
guaranteedModifierTypeOptions: [magnet, metalCoat],
fillRemaining: true,
});
await transitionMysteryEncounterIntroVisuals(true, true);
await initBattleWithEnemyConfig(config);
},
)
.build();
async function doBiomeTransitionDialogueAndBattleInit() {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate new biome (cannot be current biome)
const filteredBiomes = BIOME_CANDIDATES.filter(b => globalScene.arena.biomeType !== b);
const newBiome = filteredBiomes[randSeedInt(filteredBiomes.length)];
// Show dialogue and transition biome
await showEncounterText(`${namespace}:transport`);
await Promise.all([animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals()]);
globalScene.updateBiomeWaveText();
globalScene.playBgm();
await showEncounterText(`${namespace}:attacked`);
// Init enemy
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const bossSpecies = globalScene.arena.randomSpecies(
globalScene.currentBattle.waveIndex,
level,
0,
getPartyLuckValue(globalScene.getPlayerParty()),
true,
);
const bossPokemon = new EnemyPokemon(bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
globalScene.currentBattle.waveIndex < 50
? [Stat.DEF, Stat.SPDEF, Stat.SPD]
: [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
const config: EnemyPartyConfig = {
pokemonConfigs: [
{
level: level,
species: bossSpecies,
dataSource: new PokemonData(bossPokemon),
isBoss: true,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:boss_enraged`);
globalScene.phaseManager.unshiftNew(
"StatStageChangePhase",
pokemon.getBattlerIndex(),
true,
statChangesForBattle,
1,
);
},
},
],
};
return config;
}
async function animateBiomeChange(nextBiome: BiomeId) {
return new Promise<void>(resolve => {
globalScene.tweens.add({
targets: [globalScene.arenaEnemy, globalScene.lastEnemyTrainer],
x: "+=300",
duration: 2000,
onComplete: () => {
globalScene.newArena(nextBiome);
const biomeKey = getBiomeKey(nextBiome);
const bgTexture = `${biomeKey}_bg`;
globalScene.arenaBgTransition.setTexture(bgTexture);
globalScene.arenaBgTransition.setAlpha(0);
globalScene.arenaBgTransition.setVisible(true);
globalScene.arenaPlayerTransition.setBiome(nextBiome);
globalScene.arenaPlayerTransition.setAlpha(0);
globalScene.arenaPlayerTransition.setVisible(true);
globalScene.tweens.add({
targets: [globalScene.arenaPlayer, globalScene.arenaBgTransition, globalScene.arenaPlayerTransition],
duration: 1000,
ease: "Sine.easeInOut",
alpha: (target: any) => (target === globalScene.arenaPlayer ? 0 : 1),
onComplete: () => {
globalScene.arenaBg.setTexture(bgTexture);
globalScene.arenaPlayer.setBiome(nextBiome);
globalScene.arenaPlayer.setAlpha(1);
globalScene.arenaEnemy.setBiome(nextBiome);
globalScene.arenaEnemy.setAlpha(1);
globalScene.arenaNextEnemy.setBiome(nextBiome);
globalScene.arenaBgTransition.setVisible(false);
globalScene.arenaPlayerTransition.setVisible(false);
if (globalScene.lastEnemyTrainer) {
globalScene.lastEnemyTrainer.destroy();
}
resolve();
globalScene.tweens.add({
targets: globalScene.arenaEnemy,
x: "-=300",
});
},
});
},
});
});
}