pokerogue/src/data/mystery-encounters/encounters/trash-to-treasure-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

307 lines
11 KiB
TypeScript

import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
loadCustomMovesForEncounter,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/data/data-lists";
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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { SpeciesId } from "#enums/species-id";
import { HitHealModifier, PokemonHeldItemModifier, TurnHealModifier } from "#app/modifier/modifier";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#enums/modifier-tier";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#enums/battler-index";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { randSeedInt } from "#app/utils/common";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/trashToTreasure";
const SOUND_EFFECT_WAIT_TIME = 700;
// Items will cost 2.5x as much for remainder of the run
const SHOP_ITEM_COST_MULTIPLIER = 2.5;
/**
* Trash to Treasure encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
MysteryEncounterType.TRASH_TO_TREASURE,
)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(60, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withMaxAllowedEncounters(1)
.withFleeAllowed(false)
.withIntroSpriteConfigs([
{
spriteKey: SpeciesId.GARBODOR.toString() + "-gigantamax",
fileRoot: "pokemon",
hasShadow: false,
disableAnimation: true,
scale: 1.5,
y: 8,
tint: 0.4,
},
])
.withAutoHideIntroVisuals(false)
.withIntroDialogue([
{
text: `${namespace}:intro`,
},
])
.setLocalizationKey(`${namespace}`)
.withTitle(`${namespace}:title`)
.withDescription(`${namespace}:description`)
.withQuery(`${namespace}:query`)
.withOnInit(() => {
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Calculate boss mon (shiny locked)
const bossSpecies = getPokemonSpecies(SpeciesId.GARBODOR);
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,
isBoss: true,
shiny: false, // Shiny lock because of custom intro sprite
formIndex: 1, // Gmax
bossSegmentModifier: 1, // +1 Segment from normal
moveSet: [MoveId.GUNK_SHOT, MoveId.STOMPING_TANTRUM, MoveId.HAMMER_ARM, MoveId.PAYBACK],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BERRY) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
stackCount: randSeedInt(2, 0),
},
{
modifier: generateModifierType(modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType,
stackCount: randSeedInt(2, 1),
},
{
modifier: generateModifierType(modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType,
stackCount: randSeedInt(3, 1),
},
{
modifier: generateModifierType(modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType,
stackCount: randSeedInt(2, 0),
},
],
};
const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5,
pokemonConfigs: [pokemonConfig],
disableSwitch: true,
};
encounter.enemyPartyConfigs = [config];
// Load animations/sfx for Garbodor fight start moves
loadCustomMovesForEncounter([MoveId.TOXIC, MoveId.STOCKPILE]);
globalScene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
globalScene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString());
return true;
})
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.1.label`,
buttonTooltip: `${namespace}:option.1.tooltip`,
selected: [
{
text: `${namespace}:option.1.selected`,
},
],
})
.withPreOptionPhase(async () => {
// Play Dig2 and then Venom Drench sfx
doGarbageDig();
})
.withOptionPhase(async () => {
// Gain 2 Leftovers and 1 Shell Bell
await transitionMysteryEncounterIntroVisuals();
await tryApplyDigRewardItems();
const blackSludge = generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [
SHOP_ITEM_COST_MULTIPLIER,
]);
const modifier = blackSludge?.newModifier();
if (modifier) {
await globalScene.addModifier(modifier, false, false, false, true);
globalScene.playSound("battle_anims/PRSFX- Venom Drench", {
volume: 2,
});
await showEncounterText(
i18next.t("battle:rewardGain", {
modifierName: modifier.type.name,
}),
null,
undefined,
true,
);
}
leaveEncounterWithoutBattle(true);
})
.build(),
)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
selected: [
{
text: `${namespace}:option.2.selected`,
},
],
})
.withOptionPhase(async () => {
// Investigate garbage, battle Gmax Garbodor
globalScene.setFieldScale(0.75);
await showEncounterText(`${namespace}:option.2.selected_2`);
await transitionMysteryEncounterIntroVisuals();
const encounter = globalScene.currentBattle.mysteryEncounter!;
setEncounterRewards({
guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT],
fillRemaining: true,
});
encounter.startOfBattleEffects.push(
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.PLAYER],
move: new PokemonMove(MoveId.TOXIC),
ignorePp: true,
},
{
sourceBattlerIndex: BattlerIndex.ENEMY,
targets: [BattlerIndex.ENEMY],
move: new PokemonMove(MoveId.STOCKPILE),
ignorePp: true,
},
);
await initBattleWithEnemyConfig(encounter.enemyPartyConfigs[0]);
})
.build(),
)
.build();
async function tryApplyDigRewardItems() {
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
const leftovers = generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType;
const party = globalScene.getPlayerParty();
// Iterate over the party until an item was successfully given
// First leftovers
for (const pokemon of party) {
const heldItems = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
true,
) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
break;
}
}
// Second leftovers
for (const pokemon of party) {
const heldItems = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
true,
) as PokemonHeldItemModifier[];
const existingLeftovers = heldItems.find(m => m instanceof TurnHealModifier) as TurnHealModifier;
if (!existingLeftovers || existingLeftovers.getStackCount() < existingLeftovers.getMaxStackCount()) {
await applyModifierTypeToPlayerPokemon(pokemon, leftovers);
break;
}
}
globalScene.playSound("item_fanfare");
await showEncounterText(
i18next.t("battle:rewardGainCount", {
modifierName: leftovers.name,
count: 2,
}),
null,
undefined,
true,
);
// Only Shell bell
for (const pokemon of party) {
const heldItems = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.pokemonId === pokemon.id,
true,
) as PokemonHeldItemModifier[];
const existingShellBell = heldItems.find(m => m instanceof HitHealModifier) as HitHealModifier;
if (!existingShellBell || existingShellBell.getStackCount() < existingShellBell.getMaxStackCount()) {
await applyModifierTypeToPlayerPokemon(pokemon, shellBell);
break;
}
}
globalScene.playSound("item_fanfare");
await showEncounterText(
i18next.t("battle:rewardGainCount", {
modifierName: shellBell.name,
count: 1,
}),
null,
undefined,
true,
);
}
function doGarbageDig() {
globalScene.playSound("battle_anims/PRSFX- Dig2");
globalScene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => {
globalScene.playSound("battle_anims/PRSFX- Dig2");
globalScene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
});
globalScene.time.delayedCall(SOUND_EFFECT_WAIT_TIME * 2, () => {
globalScene.playSound("battle_anims/PRSFX- Dig2");
});
}