Converted most MEs, introduced some more utility functions and methods

This commit is contained in:
Wlowscha 2025-06-19 18:20:54 +02:00
parent 2321de0983
commit ebf0f3e352
No known key found for this signature in database
GPG Key ID: 3C8F1AD330565D04
11 changed files with 188 additions and 394 deletions

View File

@ -906,6 +906,7 @@ export default class BattleScene extends SceneBase {
variant?: Variant,
ivs?: number[],
nature?: Nature,
heldItemConfig?: HeldItemConfiguration,
dataSource?: Pokemon | PokemonData,
postProcess?: (playerPokemon: PlayerPokemon) => void,
): PlayerPokemon {
@ -925,6 +926,9 @@ export default class BattleScene extends SceneBase {
postProcess(pokemon);
}
pokemon.init();
if (heldItemConfig) {
assignItemsFromConfiguration(heldItemConfig, pokemon);
}
return pokemon;
}
@ -934,6 +938,7 @@ export default class BattleScene extends SceneBase {
trainerSlot: TrainerSlot,
boss = false,
shinyLock = false,
heldItemConfig?: HeldItemConfiguration,
dataSource?: PokemonData,
postProcess?: (enemyPokemon: EnemyPokemon) => void,
): EnemyPokemon {
@ -975,6 +980,9 @@ export default class BattleScene extends SceneBase {
}
pokemon.init();
if (heldItemConfig) {
assignItemsFromConfiguration(heldItemConfig, pokemon);
}
return pokemon;
}

View File

@ -1,6 +1,6 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
getPartyBerries,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
@ -9,14 +9,12 @@ import {
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { modifierTypes } from "#app/data/data-lists";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
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 { PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -25,19 +23,17 @@ import { MoveId } from "#enums/move-id";
import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils/common";
import { BattlerIndex } from "#enums/battler-index";
import {
applyModifierTypeToPlayerPokemon,
catchPokemon,
getHighestLevelPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { TrainerSlot } from "#enums/trainer-slot";
import { PokeballType } from "#enums/pokeball";
import type HeldModifierConfig from "#app/@types/held-modifier-config";
import type { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat";
import i18next from "i18next";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import { MoveUseMode } from "#enums/move-use-mode";
import type { HeldItemConfiguration } from "#app/items/held-item-data-types";
import { allHeldItems } from "#app/items/all-held-items";
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
import { HeldItemRequirement } from "../mystery-encounter-requirements";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/absoluteAvarice";
@ -64,7 +60,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
)
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(20, 180)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 6)) // Must have at least 6 berries to spawn
.withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 6)) // Must have at least 6 berries to spawn
.withFleeAllowed(false)
.withIntroSpriteConfigs([
{
@ -114,35 +110,17 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
globalScene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
globalScene.loadSe("Follow Me", "battle_anims", "Follow Me.mp3");
// Get all player berry items, remove from party, and store reference
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
// Get all berries in party, with references to the pokemon
const berryItems = getPartyBerries();
// Sort berries by party member ID to more easily re-add later if necessary
const berryItemsMap = new Map<number, BerryModifier[]>();
globalScene.getPlayerParty().forEach(pokemon => {
const pokemonBerries = berryItems.filter(b => b.pokemonId === pokemon.id);
if (pokemonBerries?.length > 0) {
berryItemsMap.set(pokemon.id, pokemonBerries);
}
encounter.misc.berryItemsMap = berryItems;
// Adds stolen berries to the Greedent item configuration
const bossHeldItemConfig: HeldItemConfiguration = [];
berryItems.forEach(map => {
bossHeldItemConfig.push({ entry: map.item, count: 1 });
});
encounter.misc = { berryItemsMap };
// Generates copies of the stolen berries to put on the Greedent
const bossModifierConfigs: HeldModifierConfig[] = [];
berryItems.forEach(berryMod => {
// Can't define stack count on a ModifierType, have to just create separate instances for each stack
// Overflow berries will be "lost" on the boss, but it's un-catchable anyway
for (let i = 0; i < berryMod.stackCount; i++) {
const modifierType = generateModifierType(modifierTypes.BERRY, [
berryMod.berryType,
]) as PokemonHeldItemModifierType;
bossModifierConfigs.push({ modifier: modifierType });
}
});
// Do NOT remove the real berries yet or else it will be persisted in the session data
// +1 SpDef below wave 50, SpDef and Speed otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] =
globalScene.currentBattle.waveIndex < 50 ? [Stat.SPDEF] : [Stat.SPDEF, Stat.SPD];
@ -157,7 +135,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
bossSegments: 3,
shiny: false, // Shiny lock because of consistency issues between the different options
moveSet: [MoveId.THRASH, MoveId.CRUNCH, MoveId.BODY_PRESS, MoveId.SLACK_OFF],
modifierConfigs: bossModifierConfigs,
heldItemConfig: bossHeldItemConfig,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(`${namespace}:option.1.boss_enraged`);
@ -184,9 +162,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
// Remove the berries from the party
// Session has been safely saved at this point, so data won't be lost
const berryItems = globalScene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
berryItems.forEach(berryMod => {
globalScene.removeModifier(berryMod);
const berryItems = getPartyBerries();
berryItems.forEach(map => {
map.pokemon.heldItemManager.remove(map.item);
});
globalScene.updateModifiers(true);
@ -209,19 +187,14 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
const encounter = globalScene.currentBattle.mysteryEncounter!;
// Provides 1x Reviver Seed to each party member at end of battle
const revSeed = generateModifierType(modifierTypes.REVIVER_SEED);
encounter.setDialogueToken(
"foodReward",
revSeed?.name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
allHeldItems[HeldItemId.REVIVER_SEED].name ?? i18next.t("modifierType:ModifierType.REVIVER_SEED.name"),
);
const givePartyPokemonReviverSeeds = () => {
const party = globalScene.getPlayerParty();
party.forEach(p => {
const heldItems = p.getHeldItems();
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
const seedModifier = revSeed.newModifier(p);
globalScene.addModifier(seedModifier, false, false, false, true);
}
p.heldItemManager.add(HeldItemId.REVIVER_SEED);
});
queueEncounterMessage(`${namespace}:option.1.food_stash`);
};
@ -257,19 +230,16 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilde
// Returns 2/5 of the berries stolen to each Pokemon
const party = globalScene.getPlayerParty();
party.forEach(pokemon => {
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
const berryTypesAsArray: BerryType[] = [];
stolenBerries?.forEach(bMod => berryTypesAsArray.push(...new Array(bMod.stackCount).fill(bMod.berryType)));
const returnedBerryCount = Math.floor(((berryTypesAsArray.length ?? 0) * 2) / 5);
// TODO: is this check legal?
const stolenBerries = berryMap.filter(map => map.pokemon === pokemon);
const returnedBerryCount = Math.floor(((stolenBerries.length ?? 0) * 2) / 5);
if (returnedBerryCount > 0) {
for (let i = 0; i < returnedBerryCount; i++) {
// Shuffle remaining berry types and pop
Phaser.Math.RND.shuffle(berryTypesAsArray);
const randBerryType = berryTypesAsArray.pop();
const berryModType = generateModifierType(modifierTypes.BERRY, [randBerryType]) as BerryModifierType;
applyModifierTypeToPlayerPokemon(pokemon, berryModType);
Phaser.Math.RND.shuffle(stolenBerries);
const randBerryType = stolenBerries.pop();
pokemon.heldItemManager.add(randBerryType);
}
}
});

View File

@ -1,13 +1,11 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
generateModifierType,
generateModifierTypeOption,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
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";
@ -19,17 +17,18 @@ import { AbilityId } from "#enums/ability-id";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { MoveId } from "#enums/move-id";
import { Nature } from "#enums/nature";
import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostBattleInitAbAttrs } from "#app/data/abilities/apply-ab-attrs";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import i18next from "i18next";
import { ModifierTier } from "#enums/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { BattlerTagType } from "#enums/battler-tag-type";
import { modifierTypes } from "#app/data/data-lists";
import { ModifierTier } from "#enums/modifier-tier";
import { HeldItemId } from "#enums/held-item-id";
//TODO: make all items unstealable
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theWinstrateChallenge";
@ -257,16 +256,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
abilityIndex: 0, // Guts
nature: Nature.ADAMANT,
moveSet: [MoveId.FACADE, MoveId.BRAVE_BIRD, MoveId.PROTECT, MoveId.QUICK_ATTACK],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
heldItemConfig: [
{ entry: HeldItemId.FLAME_ORB, count: 1 },
{ entry: HeldItemId.FOCUS_BAND, count: 2 },
],
},
{
@ -275,16 +267,9 @@ function getVictorTrainerConfig(): EnemyPartyConfig {
abilityIndex: 1, // Guts
nature: Nature.ADAMANT,
moveSet: [MoveId.FACADE, MoveId.OBSTRUCT, MoveId.NIGHT_SLASH, MoveId.FIRE_PUNCH],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType,
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
heldItemConfig: [
{ entry: HeldItemId.FLAME_ORB, count: 1 },
{ entry: HeldItemId.LEFTOVERS, count: 2 },
],
},
],
@ -301,16 +286,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
abilityIndex: 0, // Natural Cure
nature: Nature.CALM,
moveSet: [MoveId.SYNTHESIS, MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.SLEEP_POWDER],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType,
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
heldItemConfig: [
{ entry: HeldItemId.SOUL_DEW, count: 1 },
{ entry: HeldItemId.QUICK_CLAW, count: 2 },
],
},
{
@ -319,21 +297,9 @@ function getVictoriaTrainerConfig(): EnemyPartyConfig {
formIndex: 1,
nature: Nature.TIMID,
moveSet: [MoveId.PSYSHOCK, MoveId.MOONBLAST, MoveId.SHADOW_BALL, MoveId.WILL_O_WISP],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.PSYCHIC,
]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, [
PokemonType.FAIRY,
]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false,
},
heldItemConfig: [
{ entry: HeldItemId.TWISTED_SPOON, count: 1 },
{ entry: HeldItemId.FAIRY_FEATHER, count: 1 },
],
},
],
@ -350,17 +316,9 @@ function getViviTrainerConfig(): EnemyPartyConfig {
abilityIndex: 3, // Lightning Rod
nature: Nature.ADAMANT,
moveSet: [MoveId.WATERFALL, MoveId.MEGAHORN, MoveId.KNOCK_OFF, MoveId.REST],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false,
},
heldItemConfig: [
{ entry: HeldItemId.LUM_BERRY, count: 2 },
{ entry: HeldItemId.HP_UP, count: 4 },
],
},
{
@ -369,16 +327,9 @@ function getViviTrainerConfig(): EnemyPartyConfig {
abilityIndex: 1, // Poison Heal
nature: Nature.JOLLY,
moveSet: [MoveId.SPORE, MoveId.SWORDS_DANCE, MoveId.SEED_BOMB, MoveId.DRAIN_PUNCH],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType,
stackCount: 4,
isTransferable: false,
},
{
modifier: generateModifierType(modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType,
isTransferable: false,
},
heldItemConfig: [
{ entry: HeldItemId.HP_UP, count: 4 },
{ entry: HeldItemId.TOXIC_ORB, count: 1 },
],
},
{
@ -387,13 +338,7 @@ function getViviTrainerConfig(): EnemyPartyConfig {
formIndex: 1,
nature: Nature.CALM,
moveSet: [MoveId.EARTH_POWER, MoveId.FIRE_BLAST, MoveId.YAWN, MoveId.PROTECT],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 3,
isTransferable: false,
},
],
heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 3 }],
},
],
};
@ -409,12 +354,7 @@ function getVickyTrainerConfig(): EnemyPartyConfig {
formIndex: 1,
nature: Nature.IMPISH,
moveSet: [MoveId.AXE_KICK, MoveId.ICE_PUNCH, MoveId.ZEN_HEADBUTT, MoveId.BULLET_PUNCH],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType,
isTransferable: false,
},
],
heldItemConfig: [{ entry: HeldItemId.SHELL_BELL, count: 1 }],
},
],
};
@ -430,13 +370,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
abilityIndex: 0, // Soundproof
nature: Nature.MODEST,
moveSet: [MoveId.THUNDERBOLT, MoveId.GIGA_DRAIN, MoveId.FOUL_PLAY, MoveId.THUNDER_WAVE],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
],
heldItemConfig: [{ entry: HeldItemId.ZINC, count: 2 }],
},
{
species: getPokemonSpecies(SpeciesId.SWALOT),
@ -444,51 +378,18 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
abilityIndex: 2, // Gluttony
nature: Nature.QUIET,
moveSet: [MoveId.SLUDGE_BOMB, MoveId.GIGA_DRAIN, MoveId.ICE_BEAM, MoveId.EARTHQUAKE],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
stackCount: 2,
},
{
modifier: generateModifierType(modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType,
stackCount: 2,
},
heldItemConfig: [
{ entry: HeldItemId.SITRUS_BERRY, count: 2 },
{ entry: HeldItemId.APICOT_BERRY, count: 2 },
{ entry: HeldItemId.GANLON_BERRY, count: 2 },
{ entry: HeldItemId.STARF_BERRY, count: 2 },
{ entry: HeldItemId.SALAC_BERRY, count: 2 },
{ entry: HeldItemId.LUM_BERRY, count: 2 },
{ entry: HeldItemId.LANSAT_BERRY, count: 2 },
{ entry: HeldItemId.LIECHI_BERRY, count: 2 },
{ entry: HeldItemId.PETAYA_BERRY, count: 2 },
{ entry: HeldItemId.ENIGMA_BERRY, count: 2 },
{ entry: HeldItemId.LEPPA_BERRY, count: 2 },
],
},
{
@ -497,13 +398,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
abilityIndex: 2, // Tangled Feet
nature: Nature.JOLLY,
moveSet: [MoveId.DRILL_PECK, MoveId.QUICK_ATTACK, MoveId.THRASH, MoveId.KNOCK_OFF],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
],
heldItemConfig: [{ entry: HeldItemId.KINGS_ROCK, count: 2 }],
},
{
species: getPokemonSpecies(SpeciesId.ALAKAZAM),
@ -511,13 +406,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
formIndex: 1,
nature: Nature.BOLD,
moveSet: [MoveId.PSYCHIC, MoveId.SHADOW_BALL, MoveId.FOCUS_BLAST, MoveId.THUNDERBOLT],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
],
heldItemConfig: [{ entry: HeldItemId.WIDE_LENS, count: 2 }],
},
{
species: getPokemonSpecies(SpeciesId.DARMANITAN),
@ -525,13 +414,7 @@ function getVitoTrainerConfig(): EnemyPartyConfig {
abilityIndex: 0, // Sheer Force
nature: Nature.IMPISH,
moveSet: [MoveId.EARTHQUAKE, MoveId.U_TURN, MoveId.FLARE_BLITZ, MoveId.ROCK_SLIDE],
modifierConfigs: [
{
modifier: generateModifierType(modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType,
stackCount: 2,
isTransferable: false,
},
],
heldItemConfig: [{ entry: HeldItemId.QUICK_CLAW, count: 2 }],
},
],
};

View File

@ -1,5 +1,6 @@
import type { EnemyPartyConfig, EnemyPokemonConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
assignItemToFirstFreePokemon,
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
@ -16,7 +17,6 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { SpeciesId } from "#enums/species-id";
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";
@ -27,6 +27,8 @@ import { PokemonMove } from "#app/data/moves/pokemon-move";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { randSeedInt } from "#app/utils/common";
import { MoveUseMode } from "#enums/move-use-mode";
import { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
import { allHeldItems } from "#app/items/all-held-items";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/trashToTreasure";
@ -81,41 +83,13 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
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),
},
heldItemConfig: [
{ entry: HeldItemCategoryId.BERRY, count: 4 },
{ entry: HeldItemCategoryId.BASE_STAT_BOOST, count: 2 },
{ entry: HeldItemId.TOXIC_ORB, count: randSeedInt(2, 0) },
{ entry: HeldItemId.SOOTHE_BELL, count: randSeedInt(2, 1) },
{ entry: HeldItemId.LUCKY_EGG, count: randSeedInt(3, 1) },
{ entry: HeldItemId.GOLDEN_EGG, count: randSeedInt(2, 0) },
],
};
const config: EnemyPartyConfig = {
@ -222,44 +196,18 @@ export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilde
.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;
}
}
assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party);
// 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;
}
}
assignItemToFirstFreePokemon(HeldItemId.LEFTOVERS, party);
globalScene.playSound("item_fanfare");
await showEncounterText(
i18next.t("battle:rewardGainCount", {
modifierName: leftovers.name,
modifierName: allHeldItems[HeldItemId.LEFTOVERS].name,
count: 2,
}),
null,
@ -268,23 +216,12 @@ async function tryApplyDigRewardItems() {
);
// 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;
}
}
assignItemToFirstFreePokemon(HeldItemId.SHELL_BELL, party);
globalScene.playSound("item_fanfare");
await showEncounterText(
i18next.t("battle:rewardGainCount", {
modifierName: shellBell.name,
modifierName: allHeldItems[HeldItemId.SHELL_BELL].name,
count: 1,
}),
null,

View File

@ -1,6 +1,7 @@
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
getPartyBerries,
getRandomEncounterSpecies,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
@ -15,10 +16,7 @@ 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 {
MoveRequirement,
PersistentModifierRequirement,
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { HeldItemRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import {
@ -36,6 +34,8 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { MoveUseMode } from "#enums/move-use-mode";
import type { PokemonItemMap } from "#app/items/held-item-data-types";
import { HeldItemCategoryId } from "#enums/held-item-id";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/uncommonBreed";
@ -190,7 +190,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
)
.withOption(
MysteryEncounterOptionBuilder.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL)
.withSceneRequirement(new PersistentModifierRequirement("BerryModifier", 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withSceneRequirement(new HeldItemRequirement(HeldItemCategoryId.BERRY, 4)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically
.withDialogue({
buttonLabel: `${namespace}:option.2.label`,
buttonTooltip: `${namespace}:option.2.tooltip`,
@ -206,19 +206,18 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
// Remove 4 random berries from player's party
// Get all player berry items, remove from party, and store reference
const berryItems: BerryModifier[] = globalScene.findModifiers(
m => m instanceof BerryModifier,
) as BerryModifier[];
const berryMap = getPartyBerries();
const stolenBerryMap: PokemonItemMap[] = [];
for (let i = 0; i < 4; i++) {
const index = randSeedInt(berryItems.length);
const randBerry = berryItems[index];
randBerry.stackCount--;
if (randBerry.stackCount === 0) {
globalScene.removeModifier(randBerry);
berryItems.splice(index, 1);
}
const index = randSeedInt(berryMap.length);
const randBerry = berryMap[index];
randBerry.pokemon.heldItemManager.remove(randBerry.item);
stolenBerryMap.push(randBerry);
berryMap.splice(index, 1);
}
await globalScene.updateModifiers(true, true);
await globalScene.updateModifiers(true);
// Pokemon joins the team, with 2 egg moves
const encounter = globalScene.currentBattle.mysteryEncounter!;

View File

@ -7,7 +7,6 @@ import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-en
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import type { EnemyPartyConfig, EnemyPokemonConfig } from "../utils/encounter-phase-utils";
import {
generateModifierType,
initBattleWithEnemyConfig,
leaveEncounterWithoutBattle,
setEncounterRewards,
@ -21,11 +20,9 @@ import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/utils/pokemon-utils";
import { allSpecies } from "#app/data/data-lists";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { HiddenAbilityRateBoosterModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/data/data-lists";
import i18next from "#app/plugins/i18n";
import {
@ -40,10 +37,12 @@ import { PlayerGender } from "#enums/player-gender";
import { TrainerType } from "#enums/trainer-type";
import PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature";
import type HeldModifierConfig from "#app/@types/held-modifier-config";
import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { TrainerPartyTemplate } from "#app/data/trainers/TrainerPartyTemplate";
import { PartyMemberStrength } from "#enums/party-member-strength";
import type { HeldItemConfiguration, HeldItemSpecs } from "#app/items/held-item-data-types";
import { assignItemsFromConfiguration } from "#app/items/held-item-pool";
import { HeldItemId } from "#enums/held-item-id";
/** i18n namespace for encounter */
const namespace = "mysteryEncounters/weirdDream";
@ -265,24 +264,20 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
dataSource.player = false;
// Copy held items to new pokemon
const newPokemonHeldItemConfigs: HeldModifierConfig[] = [];
for (const item of transformation.heldItems) {
newPokemonHeldItemConfigs.push({
modifier: item.clone() as PokemonHeldItemModifier,
stackCount: item.getStackCount(),
isTransferable: false,
});
}
// TODO: Make items untransferable
const newPokemonHeldItemConfig = transformation.heldItems;
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon);
newPokemonHeldItemConfigs.push({
modifier: generateModifierType(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU, [
OLD_GATEAU_STATS_UP,
stats,
]) as PokemonHeldItemModifierType,
stackCount: 1,
isTransferable: false,
const gateauItem = {
id: HeldItemId.OLD_GATEAU,
stack: 1,
data: { statModifier: OLD_GATEAU_STATS_UP, stats: stats },
} as HeldItemSpecs;
newPokemonHeldItemConfig.push({
entry: gateauItem,
count: 1,
});
}
@ -291,7 +286,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD,
level: previousPokemon.level,
dataSource: dataSource,
modifierConfigs: newPokemonHeldItemConfigs,
heldItemConfig: newPokemonHeldItemConfig,
};
enemyPokemonConfigs.push(enemyConfig);
@ -372,7 +367,7 @@ interface PokemonTransformation {
previousPokemon: PlayerPokemon;
newSpecies: PokemonSpecies;
newPokemon: PlayerPokemon;
heldItems: PokemonHeldItemModifier[];
heldItems: HeldItemConfiguration;
}
function getTeamTransformations(): PokemonTransformation[] {
@ -397,9 +392,7 @@ function getTeamTransformations(): PokemonTransformation[] {
for (let i = 0; i < numPokemon; i++) {
const removed = removedPokemon[i];
const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id);
pokemonTransformations[index].heldItems = removed
.getHeldItems()
.filter(m => !(m instanceof PokemonFormChangeItemModifier));
pokemonTransformations[index].heldItems = removed.heldItemManager.generateHeldItemConfiguration();
const bst = removed.getSpeciesForm().getBaseStatTotal();
let newBstRange: [number, number];
@ -455,22 +448,22 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
}
// Copy old items to new pokemon
for (const item of transformation.heldItems) {
item.pokemonId = newPokemon.id;
globalScene.addModifier(item, false, false, false, true);
}
const heldItemConfiguration = transformation.heldItems;
// Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats
if (shouldGetOldGateau(newPokemon)) {
const stats = getOldGateauBoostedStats(newPokemon);
const modType = modifierTypes
.MYSTERY_ENCOUNTER_OLD_GATEAU()
.generateType(globalScene.getPlayerParty(), [OLD_GATEAU_STATS_UP, stats])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
const modifier = modType?.newModifier(newPokemon);
if (modifier) {
globalScene.addModifier(modifier, false, false, false, true);
}
const gateauItem = {
id: HeldItemId.OLD_GATEAU,
stack: 1,
data: { statModifier: OLD_GATEAU_STATS_UP, stats: stats },
} as HeldItemSpecs;
heldItemConfiguration.push({
entry: gateauItem,
count: 1,
});
}
assignItemsFromConfiguration(heldItemConfiguration, newPokemon);
newPokemon.calculateStats();
await newPokemon.updateInfo();

View File

@ -14,7 +14,7 @@ import { MoveId } from "#enums/move-id";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { SpeciesId } from "#enums/species-id";
import { TimeOfDay } from "#enums/time-of-day";
import type { HeldItemId } from "#enums/held-item-id";
import type { HeldItemCategoryId, HeldItemId } from "#enums/held-item-id";
import { allHeldItems } from "#app/items/all-held-items";
export interface EncounterRequirement {
@ -351,39 +351,6 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
}
}
export class PersistentModifierRequirement extends EncounterSceneRequirement {
requiredHeldItemModifiers: string[];
minNumberOfItems: number;
constructor(heldItem: string | string[], minNumberOfItems = 1) {
super();
this.minNumberOfItems = minNumberOfItems;
this.requiredHeldItemModifiers = coerceArray(heldItem);
}
override meetsRequirement(): boolean {
const partyPokemon = globalScene.getPlayerParty();
if (isNullOrUndefined(partyPokemon) || this.requiredHeldItemModifiers?.length < 0) {
return false;
}
let modifierCount = 0;
for (const modifier of this.requiredHeldItemModifiers) {
const matchingMods = globalScene.findModifiers(m => m.constructor.name === modifier);
if (matchingMods?.length > 0) {
for (const matchingMod of matchingMods) {
modifierCount += matchingMod.stackCount;
}
}
}
return modifierCount >= this.minNumberOfItems;
}
override getDialogueToken(_pokemon?: PlayerPokemon): [string, string] {
return ["requiredItem", this.requiredHeldItemModifiers[0]];
}
}
export class MoneyRequirement extends EncounterSceneRequirement {
requiredMoney: number; // Static value
scalingMultiplier: number; // Calculates required money based off wave index
@ -833,13 +800,13 @@ export class CanFormChangeWithItemRequirement extends EncounterPokemonRequiremen
}
export class HeldItemRequirement extends EncounterPokemonRequirement {
requiredHeldItems: HeldItemId[];
requiredHeldItems: HeldItemId[] | HeldItemCategoryId[];
minNumberOfPokemon: number;
invertQuery: boolean;
requireTransferable: boolean;
constructor(
heldItem: HeldItemId | HeldItemId[],
heldItem: HeldItemId | HeldItemId[] | HeldItemCategoryId | HeldItemCategoryId[],
minNumberOfPokemon = 1,
invertQuery = false,
requireTransferable = true,
@ -863,7 +830,7 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
if (!this.invertQuery) {
return partyPokemon.filter(pokemon =>
this.requiredHeldItems.some(heldItem => {
pokemon.heldItemManager.hasItem(heldItem) &&
(pokemon.heldItemManager.hasItem(heldItem) || pokemon.heldItemManager.hasItemCategory(heldItem)) &&
(!this.requireTransferable || allHeldItems[heldItem].isTransferable);
}),
);

View File

@ -52,7 +52,9 @@ import { PokemonType } from "#enums/pokemon-type";
import { getNatureName } from "#app/data/nature";
import { getPokemonNameWithAffix } from "#app/messages";
import { timedEventManager } from "#app/global-event-manager";
import type { HeldItemConfiguration } from "#app/items/held-item-data-types";
import type { HeldItemConfiguration, PokemonItemMap } from "#app/items/held-item-data-types";
import { HeldItemCategoryId, type HeldItemId, isItemInCategory } from "#enums/held-item-id";
import { allHeldItems } from "#app/items/all-held-items";
/**
* Animates exclamation sprite over trainer's head at start of encounter
@ -1305,3 +1307,29 @@ export function calculateRareSpawnAggregateStats(luckValue: number) {
console.log(stats);
}
// Iterate over the party until an item is successfully given
export function assignItemToFirstFreePokemon(item: HeldItemId, party: Pokemon[]): void {
for (const pokemon of party) {
const stack = pokemon.heldItemManager.getStack(item);
if (stack < allHeldItems[item].getMaxStackCount()) {
pokemon.heldItemManager.add(item);
return;
}
}
}
// Creates an item map of berries to pokemon, storing each berry separately (splitting up stacks)
export function getPartyBerries(): PokemonItemMap[] {
const pokemonItems: PokemonItemMap[] = [];
globalScene.getPlayerParty().forEach(pokemon => {
const berries = pokemon.getHeldItems().filter(item => isItemInCategory(item, HeldItemCategoryId.BERRY));
berries.forEach(berryId => {
const berryStack = pokemon.heldItemManager.getStack(berryId);
for (let i = 1; i <= berryStack; i++) {
pokemonItems.push({ item: berryId, pokemon: pokemon });
}
});
});
return pokemonItems;
}

View File

@ -38,14 +38,13 @@ export const HeldItemId = {
BLACK_GLASSES: 0x0311,
FAIRY_FEATHER: 0x0312,
// Stat Boosters
EVIOLITE: 0x0401,
LIGHT_BALL: 0x0402,
THICK_CLUB: 0x0403,
METAL_POWDER: 0x0404,
QUICK_POWDER: 0x0405,
DEEP_SEA_SCALE: 0x0406,
DEEP_SEA_TOOTH: 0x0407,
// Species Stat Boosters
LIGHT_BALL: 0x0401,
THICK_CLUB: 0x0402,
METAL_POWDER: 0x0403,
QUICK_POWDER: 0x0404,
DEEP_SEA_SCALE: 0x0405,
DEEP_SEA_TOOTH: 0x0406,
// Crit Boosters
SCOPE_LENS: 0x0501,
@ -72,6 +71,7 @@ export const HeldItemId = {
SOUL_DEW: 0x070D,
BATON: 0x070E,
MINI_BLACK_HOLE: 0x070F,
EVIOLITE: 0x0710,
// Vitamins
HP_UP: 0x0801,
@ -110,7 +110,7 @@ export const HeldItemCategoryId = {
BERRY: 0x0100,
CONSUMABLE: 0x0200,
TYPE_ATTACK_BOOSTER: 0x0300,
STAT_BOOSTER: 0x0400,
SPECIES_STAT_BOOSTER: 0x0400,
CRIT_BOOSTER: 0x0500,
GAIN_INCREASE: 0x0600,
UNIQUE: 0x0700,

View File

@ -1,5 +1,5 @@
import { allHeldItems } from "#app/items/all-held-items";
import { isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id";
import { isItemInCategory, isItemInRequested, type HeldItemCategoryId, type HeldItemId } from "#app/enums/held-item-id";
import type { FormChangeItem } from "#enums/form-change-item";
import {
type HeldItemConfiguration,
@ -75,6 +75,10 @@ export class PokemonItemManager {
return itemType in this.heldItems;
}
hasItemCategory(categoryId: HeldItemCategoryId): boolean {
return Object.keys(this.heldItems).some(id => isItemInCategory(Number(id), categoryId));
}
getStack(itemType: HeldItemId): number {
const item = this.heldItems[itemType];
return item ? item.stack : 0;

View File

@ -70,3 +70,8 @@ type HeldItemConfigurationEntry = {
};
export type HeldItemConfiguration = HeldItemConfigurationEntry[];
export type PokemonItemMap = {
item: HeldItemId;
pokemon: Pokemon;
};