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>
385 lines
15 KiB
TypeScript
385 lines
15 KiB
TypeScript
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 {
|
|
CombinationPokemonRequirement,
|
|
HeldItemRequirement,
|
|
MoneyRequirement,
|
|
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
|
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
|
import {
|
|
generateModifierType,
|
|
leaveEncounterWithoutBattle,
|
|
selectPokemonForOption,
|
|
updatePlayerMoney,
|
|
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
|
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
|
import type { PlayerPokemon } from "#app/field/pokemon";
|
|
import type Pokemon from "#app/field/pokemon";
|
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
|
|
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
|
import {
|
|
BerryModifier,
|
|
HealingBoosterModifier,
|
|
LevelIncrementBoosterModifier,
|
|
MoneyMultiplierModifier,
|
|
PreserveBerryModifier,
|
|
} from "#app/modifier/modifier";
|
|
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
|
import { modifierTypes } from "#app/data/data-lists";
|
|
import i18next from "#app/plugins/i18n";
|
|
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
|
import { randSeedItem } from "#app/utils/common";
|
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|
import { SpeciesId } from "#enums/species-id";
|
|
import { timedEventManager } from "#app/global-event-manager";
|
|
|
|
/** the i18n namespace for this encounter */
|
|
const namespace = "mysteryEncounters/delibirdy";
|
|
|
|
/** Berries only */
|
|
const OPTION_2_ALLOWED_MODIFIERS = ["BerryModifier", "PokemonInstantReviveModifier"];
|
|
|
|
/** Disallowed items are berries, Reviver Seeds, and Vitamins (form change items and fusion items are not PokemonHeldItemModifiers) */
|
|
const OPTION_3_DISALLOWED_MODIFIERS = [
|
|
"BerryModifier",
|
|
"PokemonInstantReviveModifier",
|
|
"TerastallizeModifier",
|
|
"PokemonBaseStatModifier",
|
|
"PokemonBaseStatTotalModifier",
|
|
];
|
|
|
|
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
|
|
|
|
const doEventReward = () => {
|
|
const event_buff = timedEventManager.getDelibirdyBuff();
|
|
if (event_buff.length > 0) {
|
|
const candidates = event_buff.filter(c => {
|
|
const mtype = generateModifierType(modifierTypes[c]);
|
|
const existingCharm = globalScene.findModifier(m => m.type.id === mtype?.id);
|
|
return !(existingCharm && existingCharm.getStackCount() >= existingCharm.getMaxStackCount());
|
|
});
|
|
if (candidates.length > 0) {
|
|
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes[randSeedItem(candidates)]);
|
|
} else {
|
|
// At max stacks, give a Voucher instead
|
|
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.VOUCHER);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Delibird-y encounter.
|
|
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3804 | GitHub Issue #3804}
|
|
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
|
|
*/
|
|
export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(
|
|
MysteryEncounterType.DELIBIRDY,
|
|
)
|
|
.withEncounterTier(MysteryEncounterTier.GREAT)
|
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
|
.withSceneRequirement(new MoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER)) // Must have enough money for it to spawn at the very least
|
|
.withPrimaryPokemonRequirement(
|
|
CombinationPokemonRequirement.Some(
|
|
// Must also have either option 2 or 3 available to spawn
|
|
new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS),
|
|
new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true),
|
|
),
|
|
)
|
|
.withIntroSpriteConfigs([
|
|
{
|
|
spriteKey: "",
|
|
fileRoot: "",
|
|
species: SpeciesId.DELIBIRD,
|
|
hasShadow: true,
|
|
repeat: true,
|
|
startFrame: 38,
|
|
scale: 0.94,
|
|
},
|
|
{
|
|
spriteKey: "",
|
|
fileRoot: "",
|
|
species: SpeciesId.DELIBIRD,
|
|
hasShadow: true,
|
|
repeat: true,
|
|
scale: 1.06,
|
|
},
|
|
{
|
|
spriteKey: "",
|
|
fileRoot: "",
|
|
species: SpeciesId.DELIBIRD,
|
|
hasShadow: true,
|
|
repeat: true,
|
|
startFrame: 65,
|
|
x: 1,
|
|
y: 5,
|
|
yShadow: 5,
|
|
},
|
|
])
|
|
.withIntroDialogue([
|
|
{
|
|
text: `${namespace}:intro`,
|
|
},
|
|
])
|
|
.setLocalizationKey(`${namespace}`)
|
|
.withTitle(`${namespace}:title`)
|
|
.withDescription(`${namespace}:description`)
|
|
.withQuery(`${namespace}:query`)
|
|
.withOutroDialogue([
|
|
{
|
|
text: `${namespace}:outro`,
|
|
},
|
|
])
|
|
.withOnInit(() => {
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
encounter.setDialogueToken("delibirdName", getPokemonSpecies(SpeciesId.DELIBIRD).getName());
|
|
|
|
globalScene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3");
|
|
return true;
|
|
})
|
|
.withOnVisualsStart(() => {
|
|
globalScene.fadeAndSwitchBgm("mystery_encounter_delibirdy");
|
|
return true;
|
|
})
|
|
.withOption(
|
|
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
|
.withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn
|
|
.withDialogue({
|
|
buttonLabel: `${namespace}:option.1.label`,
|
|
buttonTooltip: `${namespace}:option.1.tooltip`,
|
|
selected: [
|
|
{
|
|
text: `${namespace}:option.1.selected`,
|
|
},
|
|
],
|
|
})
|
|
.withPreOptionPhase(async (): Promise<boolean> => {
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
updatePlayerMoney(-(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney, true, false);
|
|
return true;
|
|
})
|
|
.withOptionPhase(async () => {
|
|
// Give the player an Amulet Coin
|
|
// Check if the player has max stacks of that item already
|
|
const existing = globalScene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
|
|
|
|
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
|
// At max stacks, give the first party pokemon a Shell Bell instead
|
|
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
|
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
|
globalScene.playSound("item_fanfare");
|
|
await showEncounterText(
|
|
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
|
null,
|
|
undefined,
|
|
true,
|
|
);
|
|
doEventReward();
|
|
} else {
|
|
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.AMULET_COIN);
|
|
doEventReward();
|
|
}
|
|
|
|
leaveEncounterWithoutBattle(true);
|
|
})
|
|
.build(),
|
|
)
|
|
.withOption(
|
|
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
|
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_2_ALLOWED_MODIFIERS))
|
|
.withDialogue({
|
|
buttonLabel: `${namespace}:option.2.label`,
|
|
buttonTooltip: `${namespace}:option.2.tooltip`,
|
|
secondOptionPrompt: `${namespace}:option.2.select_prompt`,
|
|
selected: [
|
|
{
|
|
text: `${namespace}:option.2.selected`,
|
|
},
|
|
],
|
|
})
|
|
.withPreOptionPhase(async (): Promise<boolean> => {
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
|
// Get Pokemon held items and filter for valid ones
|
|
const validItems = pokemon.getHeldItems().filter(it => {
|
|
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
|
});
|
|
|
|
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
|
const option: OptionSelectItem = {
|
|
label: modifier.type.name,
|
|
handler: () => {
|
|
// Pokemon and item selected
|
|
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
|
encounter.misc = {
|
|
chosenPokemon: pokemon,
|
|
chosenModifier: modifier,
|
|
};
|
|
return true;
|
|
},
|
|
};
|
|
return option;
|
|
});
|
|
};
|
|
|
|
const selectableFilter = (pokemon: Pokemon) => {
|
|
// If pokemon has valid item, it can be selected
|
|
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(pokemon);
|
|
if (!meetsReqs) {
|
|
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
|
})
|
|
.withOptionPhase(async () => {
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
const modifier: BerryModifier | PokemonInstantReviveModifier = encounter.misc.chosenModifier;
|
|
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
|
|
|
// Give the player a Candy Jar if they gave a Berry, and a Berry Pouch for Reviver Seed
|
|
if (modifier instanceof BerryModifier) {
|
|
// Check if the player has max stacks of that Candy Jar already
|
|
const existing = globalScene.findModifier(
|
|
m => m instanceof LevelIncrementBoosterModifier,
|
|
) as LevelIncrementBoosterModifier;
|
|
|
|
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
|
// At max stacks, give the first party pokemon a Shell Bell instead
|
|
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
|
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
|
globalScene.playSound("item_fanfare");
|
|
await showEncounterText(
|
|
i18next.t("battle:rewardGain", {
|
|
modifierName: shellBell.name,
|
|
}),
|
|
null,
|
|
undefined,
|
|
true,
|
|
);
|
|
doEventReward();
|
|
} else {
|
|
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.CANDY_JAR);
|
|
doEventReward();
|
|
}
|
|
} else {
|
|
// Check if the player has max stacks of that Berry Pouch already
|
|
const existing = globalScene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier;
|
|
|
|
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
|
// At max stacks, give the first party pokemon a Shell Bell instead
|
|
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
|
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerPokemon()!, shellBell);
|
|
globalScene.playSound("item_fanfare");
|
|
await showEncounterText(
|
|
i18next.t("battle:rewardGain", {
|
|
modifierName: shellBell.name,
|
|
}),
|
|
null,
|
|
undefined,
|
|
true,
|
|
);
|
|
doEventReward();
|
|
} else {
|
|
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.BERRY_POUCH);
|
|
doEventReward();
|
|
}
|
|
}
|
|
|
|
chosenPokemon.loseHeldItem(modifier, false);
|
|
|
|
leaveEncounterWithoutBattle(true);
|
|
})
|
|
.build(),
|
|
)
|
|
.withOption(
|
|
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
|
.withPrimaryPokemonRequirement(new HeldItemRequirement(OPTION_3_DISALLOWED_MODIFIERS, 1, true))
|
|
.withDialogue({
|
|
buttonLabel: `${namespace}:option.3.label`,
|
|
buttonTooltip: `${namespace}:option.3.tooltip`,
|
|
secondOptionPrompt: `${namespace}:option.3.select_prompt`,
|
|
selected: [
|
|
{
|
|
text: `${namespace}:option.3.selected`,
|
|
},
|
|
],
|
|
})
|
|
.withPreOptionPhase(async (): Promise<boolean> => {
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
|
// Get Pokemon held items and filter for valid ones
|
|
const validItems = pokemon.getHeldItems().filter(it => {
|
|
return (
|
|
!OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable
|
|
);
|
|
});
|
|
|
|
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
|
const option: OptionSelectItem = {
|
|
label: modifier.type.name,
|
|
handler: () => {
|
|
// Pokemon and item selected
|
|
encounter.setDialogueToken("chosenItem", modifier.type.name);
|
|
encounter.misc = {
|
|
chosenPokemon: pokemon,
|
|
chosenModifier: modifier,
|
|
};
|
|
return true;
|
|
},
|
|
};
|
|
return option;
|
|
});
|
|
};
|
|
|
|
const selectableFilter = (pokemon: Pokemon) => {
|
|
// If pokemon has valid item, it can be selected
|
|
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(pokemon);
|
|
if (!meetsReqs) {
|
|
return getEncounterText(`${namespace}:invalid_selection`) ?? null;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
return selectPokemonForOption(onPokemonSelected, undefined, selectableFilter);
|
|
})
|
|
.withOptionPhase(async () => {
|
|
const encounter = globalScene.currentBattle.mysteryEncounter!;
|
|
const modifier = encounter.misc.chosenModifier;
|
|
const chosenPokemon: PlayerPokemon = encounter.misc.chosenPokemon;
|
|
|
|
// Check if the player has max stacks of Healing Charm already
|
|
const existing = globalScene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier;
|
|
|
|
if (existing && existing.getStackCount() >= existing.getMaxStackCount()) {
|
|
// At max stacks, give the first party pokemon a Shell Bell instead
|
|
const shellBell = generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType;
|
|
await applyModifierTypeToPlayerPokemon(globalScene.getPlayerParty()[0], shellBell);
|
|
globalScene.playSound("item_fanfare");
|
|
await showEncounterText(
|
|
i18next.t("battle:rewardGain", { modifierName: shellBell.name }),
|
|
null,
|
|
undefined,
|
|
true,
|
|
);
|
|
doEventReward();
|
|
} else {
|
|
globalScene.phaseManager.unshiftNew("ModifierRewardPhase", modifierTypes.HEALING_CHARM);
|
|
doEventReward();
|
|
}
|
|
|
|
chosenPokemon.loseHeldItem(modifier, false);
|
|
|
|
leaveEncounterWithoutBattle(true);
|
|
})
|
|
.build(),
|
|
)
|
|
.build();
|