mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-06-21 00:52:47 +02:00
* 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>
348 lines
12 KiB
TypeScript
348 lines
12 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 {
|
|
generateModifierType,
|
|
generateModifierTypeOption,
|
|
getRandomEncounterSpecies,
|
|
initBattleWithEnemyConfig,
|
|
leaveEncounterWithoutBattle,
|
|
setEncounterExp,
|
|
setEncounterRewards,
|
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
|
import type Pokemon from "#app/field/pokemon";
|
|
import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
|
|
import { regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
|
|
import { modifierTypes } from "#app/data/data-lists";
|
|
import { ModifierPoolType } from "#enums/modifier-pool-type";
|
|
import { randSeedInt } from "#app/utils/common";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
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 { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
|
import { getPokemonNameWithAffix } from "#app/messages";
|
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
|
import {
|
|
applyModifierTypeToPlayerPokemon,
|
|
getEncounterPokemonLevelForWave,
|
|
getHighestStatPlayerPokemon,
|
|
getSpriteKeysFromPokemon,
|
|
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,
|
|
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
|
import PokemonData from "#app/system/pokemon-data";
|
|
import { BerryModifier } from "#app/modifier/modifier";
|
|
import i18next from "#app/plugins/i18n";
|
|
import { BerryType } from "#enums/berry-type";
|
|
import { PERMANENT_STATS, Stat } from "#enums/stat";
|
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
|
|
|
/** the i18n namespace for the encounter */
|
|
const namespace = "mysteryEncounters/berriesAbound";
|
|
|
|
/**
|
|
* Berries Abound encounter.
|
|
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3810 | GitHub Issue #3810}
|
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
|
*/
|
|
export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
|
MysteryEncounterType.BERRIES_ABOUND,
|
|
)
|
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
|
.withCatchAllowed(true)
|
|
.withHideWildIntroMessage(true)
|
|
.withFleeAllowed(false)
|
|
.withIntroSpriteConfigs([]) // Set in onInit()
|
|
.withIntroDialogue([
|
|
{
|
|
text: `${namespace}:intro`,
|
|
},
|
|
])
|
|
.withOnInit(() => {
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
|
|
// Calculate boss mon
|
|
const level = getEncounterPokemonLevelForWave(STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
|
|
const bossPokemon = getRandomEncounterSpecies(level, true);
|
|
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
|
const config: EnemyPartyConfig = {
|
|
pokemonConfigs: [
|
|
{
|
|
level: level,
|
|
species: bossPokemon.species,
|
|
dataSource: new PokemonData(bossPokemon),
|
|
isBoss: true,
|
|
},
|
|
],
|
|
};
|
|
encounter.enemyPartyConfigs = [config];
|
|
|
|
// Calculate the number of extra berries that player receives
|
|
// 10-40: 2, 40-120: 4, 120-160: 5, 160-180: 7
|
|
const numBerries =
|
|
globalScene.currentBattle.waveIndex > 160
|
|
? 7
|
|
: globalScene.currentBattle.waveIndex > 120
|
|
? 5
|
|
: globalScene.currentBattle.waveIndex > 40
|
|
? 4
|
|
: 2;
|
|
regenerateModifierPoolThresholds(globalScene.getPlayerParty(), ModifierPoolType.PLAYER, 0);
|
|
encounter.misc = { numBerries };
|
|
|
|
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
|
encounter.spriteConfigs = [
|
|
{
|
|
spriteKey: "berries_abound_bush",
|
|
fileRoot: "mystery-encounters",
|
|
x: 25,
|
|
y: -6,
|
|
yShadow: -7,
|
|
disableAnimation: true,
|
|
hasShadow: true,
|
|
},
|
|
{
|
|
spriteKey: spriteKey,
|
|
fileRoot: fileRoot,
|
|
hasShadow: true,
|
|
tint: 0.25,
|
|
x: -5,
|
|
repeat: true,
|
|
isPokemon: true,
|
|
isShiny: bossPokemon.shiny,
|
|
variant: bossPokemon.variant,
|
|
},
|
|
];
|
|
|
|
// Get fastest party pokemon for option 2
|
|
const fastestPokemon = getHighestStatPlayerPokemon(PERMANENT_STATS[Stat.SPD], true, false);
|
|
encounter.misc.fastestPokemon = fastestPokemon;
|
|
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
|
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
|
|
|
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!;
|
|
const numBerries = encounter.misc.numBerries;
|
|
|
|
const doBerryRewards = () => {
|
|
const berryText = i18next.t(`${namespace}:berries`);
|
|
|
|
globalScene.playSound("item_fanfare");
|
|
queueEncounterMessage(
|
|
i18next.t("battle:rewardGainCount", {
|
|
modifierName: berryText,
|
|
count: numBerries,
|
|
}),
|
|
);
|
|
|
|
// Generate a random berry and give it to the first Pokemon with room for it
|
|
for (let i = 0; i < numBerries; i++) {
|
|
tryGiveBerry();
|
|
}
|
|
};
|
|
|
|
const shopOptions: ModifierTypeOption[] = [];
|
|
for (let i = 0; i < 5; i++) {
|
|
// Generate shop berries
|
|
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
|
if (mod) {
|
|
shopOptions.push(mod);
|
|
}
|
|
}
|
|
|
|
setEncounterRewards(
|
|
{ guaranteedModifierTypeOptions: shopOptions, fillRemaining: false },
|
|
undefined,
|
|
doBerryRewards,
|
|
);
|
|
await initBattleWithEnemyConfig(globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]);
|
|
},
|
|
)
|
|
.withOption(
|
|
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
|
.withDialogue({
|
|
buttonLabel: `${namespace}:option.2.label`,
|
|
buttonTooltip: `${namespace}:option.2.tooltip`,
|
|
})
|
|
.withOptionPhase(async () => {
|
|
// Pick race for berries
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
const fastestPokemon: PlayerPokemon = encounter.misc.fastestPokemon;
|
|
const enemySpeed: number = encounter.misc.enemySpeed;
|
|
const speedDiff = fastestPokemon.getStat(Stat.SPD) / (enemySpeed * 1.1);
|
|
const numBerries: number = encounter.misc.numBerries;
|
|
|
|
const shopOptions: ModifierTypeOption[] = [];
|
|
for (let i = 0; i < 5; i++) {
|
|
// Generate shop berries
|
|
const mod = generateModifierTypeOption(modifierTypes.BERRY);
|
|
if (mod) {
|
|
shopOptions.push(mod);
|
|
}
|
|
}
|
|
|
|
if (speedDiff < 1) {
|
|
// Caught and attacked by boss, gets +1 to all stats at start of fight
|
|
const doBerryRewards = () => {
|
|
const berryText = i18next.t(`${namespace}:berries`);
|
|
|
|
globalScene.playSound("item_fanfare");
|
|
queueEncounterMessage(
|
|
i18next.t("battle:rewardGainCount", {
|
|
modifierName: berryText,
|
|
count: numBerries,
|
|
}),
|
|
);
|
|
|
|
// Generate a random berry and give it to the first Pokemon with room for it
|
|
for (let i = 0; i < numBerries; i++) {
|
|
tryGiveBerry();
|
|
}
|
|
};
|
|
|
|
// 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 = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
|
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
|
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
|
queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
|
|
globalScene.phaseManager.unshiftNew(
|
|
"StatStageChangePhase",
|
|
pokemon.getBattlerIndex(),
|
|
true,
|
|
statChangesForBattle,
|
|
1,
|
|
);
|
|
};
|
|
setEncounterRewards(
|
|
{
|
|
guaranteedModifierTypeOptions: shopOptions,
|
|
fillRemaining: false,
|
|
},
|
|
undefined,
|
|
doBerryRewards,
|
|
);
|
|
await showEncounterText(`${namespace}:option.2.selected_bad`);
|
|
await initBattleWithEnemyConfig(config);
|
|
return;
|
|
}
|
|
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2
|
|
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1) / 0.08), numBerries), 2);
|
|
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
|
const doFasterBerryRewards = () => {
|
|
const berryText = i18next.t(`${namespace}:berries`);
|
|
|
|
globalScene.playSound("item_fanfare");
|
|
queueEncounterMessage(
|
|
i18next.t("battle:rewardGainCount", {
|
|
modifierName: berryText,
|
|
count: numBerriesGrabbed,
|
|
}),
|
|
);
|
|
|
|
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
|
|
for (let i = 0; i < numBerriesGrabbed; i++) {
|
|
tryGiveBerry(fastestPokemon);
|
|
}
|
|
};
|
|
|
|
setEncounterExp(fastestPokemon.id, encounter.enemyPartyConfigs[0].pokemonConfigs![0].species.baseExp);
|
|
setEncounterRewards(
|
|
{
|
|
guaranteedModifierTypeOptions: shopOptions,
|
|
fillRemaining: false,
|
|
},
|
|
undefined,
|
|
doFasterBerryRewards,
|
|
);
|
|
await showEncounterText(`${namespace}:option.2.selected`);
|
|
leaveEncounterWithoutBattle();
|
|
})
|
|
.build(),
|
|
)
|
|
.withSimpleOption(
|
|
{
|
|
buttonLabel: `${namespace}:option.3.label`,
|
|
buttonTooltip: `${namespace}:option.3.tooltip`,
|
|
selected: [
|
|
{
|
|
text: `${namespace}:option.3.selected`,
|
|
},
|
|
],
|
|
},
|
|
async () => {
|
|
// Leave encounter with no rewards or exp
|
|
leaveEncounterWithoutBattle(true);
|
|
return true;
|
|
},
|
|
)
|
|
.build();
|
|
|
|
function tryGiveBerry(prioritizedPokemon?: PlayerPokemon) {
|
|
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !Number.isNaN(Number(s))).length) as BerryType;
|
|
const berry = generateModifierType(modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
|
|
|
const party = globalScene.getPlayerParty();
|
|
|
|
// Will try to apply to prioritized pokemon first, then do normal application method if it fails
|
|
if (prioritizedPokemon) {
|
|
const heldBerriesOfType = globalScene.findModifier(
|
|
m =>
|
|
m instanceof BerryModifier &&
|
|
m.pokemonId === prioritizedPokemon.id &&
|
|
(m as BerryModifier).berryType === berryType,
|
|
true,
|
|
) as BerryModifier;
|
|
|
|
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
|
applyModifierTypeToPlayerPokemon(prioritizedPokemon, berry);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Iterate over the party until berry was successfully given
|
|
for (const pokemon of party) {
|
|
const heldBerriesOfType = globalScene.findModifier(
|
|
m => m instanceof BerryModifier && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType,
|
|
true,
|
|
) as BerryModifier;
|
|
|
|
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount()) {
|
|
applyModifierTypeToPlayerPokemon(pokemon, berry);
|
|
return;
|
|
}
|
|
}
|
|
}
|