diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a8cffa1d9cd..4c3cfd3276b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -22,7 +22,7 @@ import { getModifierPoolForType, getModifierType, getPartyLuckValue, - modifierTypes + modifierTypes, PokemonHeldItemModifierType } from "./modifier/modifier-type"; import AbilityBar from "./ui/ability-bar"; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, ChangeMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability"; @@ -2521,13 +2521,19 @@ export default class BattleScene extends SceneBase { party.forEach((enemyPokemon: EnemyPokemon, i: integer) => { if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i] && heldModifiersConfigs[i].length > 0) { heldModifiersConfigs[i].forEach(mt => { - const stackCount = mt.stackCount ?? 1; - // const isTransferable = mt.isTransferable ?? true; - const modifier = mt.modifierType.newModifier(enemyPokemon); - modifier.stackCount = stackCount; - // TODO: set isTransferable - // modifier.setIsTransferable(isTransferable); - this.addEnemyModifier(modifier, true); + if (mt.modifier instanceof PokemonHeldItemModifierType) { + const stackCount = mt.stackCount ?? 1; + // const isTransferable = mt.isTransferable ?? true; + const modifier = mt.modifier.newModifier(enemyPokemon); + modifier.stackCount = stackCount; + // TODO: set isTransferable + // modifier.setIsTransferable(isTransferable); + this.addEnemyModifier(modifier, true); + } else { + const modifier = mt.modifier as PokemonHeldItemModifier; + modifier.pokemonId = enemyPokemon.id; + this.addEnemyModifier(modifier, true); + } }); } else { const isBoss = enemyPokemon.isBoss() || (this.currentBattle.battleType === BattleType.TRAINER && !!this.currentBattle.trainer?.config.isBoss); diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 9ff861fc5b1..147c998eb4d 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -13,6 +13,8 @@ import { IEggOptions } from "#app/data/egg"; import { EggSourceType } from "#enums/egg-source-types"; import { EggTier } from "#enums/egg-type"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; +import { ModifierTier } from "#app/modifier/modifier-tier"; +import { modifierTypes } from "#app/modifier/modifier-type"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:aTrainersTest"; @@ -136,7 +138,7 @@ export const ATrainersTestEncounter: MysteryEncounter = }, async (scene: BattleScene) => { const encounter = scene.currentBattle.mysteryEncounter!; - // Spawn standard trainer battle with memory mushroom reward + // Battle the stat trainer for an Egg and great rewards const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; await transitionMysteryEncounterIntroVisuals(scene); @@ -149,7 +151,7 @@ export const ATrainersTestEncounter: MysteryEncounter = tier: EggTier.ULTRA }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`)); - setEncounterRewards(scene, { fillRemaining: true }, [eggOptions]); + setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH], guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA], fillRemaining: true }, [eggOptions]); return initBattleWithEnemyConfig(scene, config); } diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 3d89e4531d8..76cfea4da1a 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -141,11 +141,11 @@ export const DancingLessonsEncounter: MysteryEncounter = species: species, dataSource: oricorioData, isBoss: true, - // Gets +1 to all stats on battle start + // Gets +1 to all stats except SPD on battle start tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`); - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD], 1)); + pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF], 1)); } }], }; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index eaa3c94d129..89c14b0228c 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -12,6 +12,7 @@ import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; +import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; /** i18n namespace for encounter */ const namespace = "mysteryEncounter:darkDeal"; @@ -125,25 +126,27 @@ export const DarkDealEncounter: MysteryEncounter = // Removes random pokemon (including fainted) from party and adds name to dialogue data tokens // Will never return last battle able mon and instead pick fainted/unable to battle const removedPokemon = getRandomPlayerPokemon(scene, false, true); + // Get all the pokemon's held items + const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); scene.removePokemonFromPlayerParty(removedPokemon); const encounter = scene.currentBattle.mysteryEncounter!; encounter.setDialogueToken("pokeName", removedPokemon.getNameToRender()); // Store removed pokemon types - encounter.misc = [ - removedPokemon.species.type1, - ]; - if (removedPokemon.species.type2) { - encounter.misc.push(removedPokemon.species.type2); - } + encounter.misc = { + removedTypes: removedPokemon.getTypes(), + modifiers + }; }) .withOptionPhase(async (scene: BattleScene) => { // Give the player 5 Rogue Balls + const encounter = scene.currentBattle.mysteryEncounter!; scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL)); // Start encounter with random legendary (7-10 starter strength) that has level additive - const bossTypes = scene.currentBattle.mysteryEncounter!.misc as Type[]; + const bossTypes: Type[] = encounter.misc.removedTypes; + const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers; // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ const roll = randSeedInt(100); const starterTier: number | [number, number] = @@ -152,6 +155,11 @@ export const DarkDealEncounter: MysteryEncounter = const pokemonConfig: EnemyPokemonConfig = { species: bossSpecies, isBoss: true, + modifierConfigs: bossModifiers.map(m => { + return { + modifier: m + }; + }) }; if (!isNullOrUndefined(bossSpecies.forms) && bossSpecies.forms.length > 0) { pokemonConfig.formIndex = 0; diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 9c20a511bdc..c59aa941e62 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -6,7 +6,7 @@ import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter" import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; import { Nature } from "#app/data/nature"; -import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Moves } from "#enums/moves"; @@ -30,6 +30,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 + .withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party .withHideWildIntroMessage(true) .withAutoHideIntroVisuals(false) .withIntroSpriteConfigs([ @@ -124,35 +125,21 @@ export const TheStrongStuffEncounter: MysteryEncounter = transitionMysteryEncounterIntroVisuals(scene, true, true, 50); }); - // -20 to all base stats of highest BST, +10 to all base stats of rest of party - // Get highest BST mon - const party = scene.getParty(); - let highestBst: PlayerPokemon | null = null; - let statTotal = 0; - for (const pokemon of party) { - if (!highestBst) { - highestBst = pokemon; - statTotal = pokemon.getSpeciesForm().getBaseStatTotal(); - continue; - } - - const total = pokemon.getSpeciesForm().getBaseStatTotal(); - if (total > statTotal) { - highestBst = pokemon; - statTotal = total; - } - } - - if (!highestBst) { - highestBst = party[0]; - } - - modifyPlayerPokemonBST(highestBst, -20); - for (const pokemon of party) { - if (highestBst.id === pokemon.id) { - continue; - } + // -15 to all base stats of highest BST (halved for HP), +10 to all base stats of rest of party (halved for HP) + // Sort party by bst (inverted to pop 2 highest off end) + const sortedParty = scene.getParty().slice(0) + .sort((pokemon1, pokemon2) => { + const pokemon1Bst = pokemon1.calculateBaseStats().reduce((a, b) => a + b, 0); + const pokemon2Bst = pokemon2.calculateBaseStats().reduce((a, b) => a + b, 0); + return pokemon1Bst - pokemon2Bst; + }); + const highestBst = sortedParty.pop()!; + const highestBst2 = sortedParty.pop()!; + modifyPlayerPokemonBST(highestBst, -15); + modifyPlayerPokemonBST(highestBst2, -15); + // +10 for the rest + for (const pokemon of sortedParty) { modifyPlayerPokemonBST(pokemon, 10); } diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index d7ffdb78dae..3c1b7f80d0d 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -32,7 +32,7 @@ const namespace = "mysteryEncounter:theWinstrateChallenge"; export const TheWinstrateChallengeEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE) .withEncounterTier(MysteryEncounterTier.ROGUE) - .withSceneWaveRangeRequirement(80, 180) + .withSceneWaveRangeRequirement(100, 180) .withIntroSpriteConfigs([ { spriteKey: "vito", diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 4ffe60e29bd..f640af4a14e 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -4,7 +4,7 @@ import { getNatureName, Nature } from "#app/data/nature"; import { speciesStarters } from "#app/data/pokemon-species"; import { getStatName } from "#app/data/pokemon-stat"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { AbilityAttr } from "#app/system/game-data"; import PokemonData from "#app/system/pokemon-data"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; @@ -247,9 +247,10 @@ export const TrainingSessionEncounter: MysteryEncounter = playerPokemon.setNature(encounter.misc.chosenNature); scene.gameData.setPokemonCaught(playerPokemon, false); - // Add pokemon and mods back + // Add pokemon and modifiers back scene.getParty().push(playerPokemon); for (const mod of modifiers.value) { + mod.pokemonId = playerPokemon.id; scene.addModifier(mod, true, false, false, true); } scene.updateModifiers(true); @@ -385,10 +386,10 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon, segmen playerPokemon.resetSummonData(); // Passes modifiers by reference - modifiers.value = scene.findModifiers((m) => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === playerPokemon.id) as PokemonHeldItemModifier[]; + modifiers.value = playerPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); const modifierConfigs = modifiers.value.map((mod) => { return { - modifierType: mod.type + modifier: mod }; }) as HeldModifierConfig[]; diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index da76922a5ef..b01f83f9dcd 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -31,7 +31,7 @@ const SOUND_EFFECT_WAIT_TIME = 700; export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE) .withEncounterTier(MysteryEncounterTier.ULTRA) - .withSceneWaveRangeRequirement(10, 180) + .withSceneWaveRangeRequirement(60, 180) .withMaxAllowedEncounters(1) .withIntroSpriteConfigs([ { diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index d3ddafb0194..34138ce9c27 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -94,6 +94,7 @@ const STANDARD_BST_TRANSFORM_BASE_VALUES = [40, 50]; export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) .withEncounterTier(MysteryEncounterTier.ROGUE) + .withSceneWaveRangeRequirement(10, 180) .withIntroSpriteConfigs([ { spriteKey: "girawitch", @@ -111,7 +112,6 @@ export const WeirdDreamEncounter: MysteryEncounter = text: `${namespace}.intro_dialogue`, }, ]) - .withSceneWaveRangeRequirement(10, 180) .withTitle(`${namespace}.title`) .withDescription(`${namespace}.description`) .withQuery(`${namespace}.query`) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0bcb4725f4c..a9b3c202f6d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -10,7 +10,7 @@ import * as Utils from "../utils"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { getLevelTotalExp } from "../data/exp"; import { Stat } from "../data/pokemon-stat"; -import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier } from "../modifier/modifier"; +import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, PokemonBaseStatFlatModifier } from "../modifier/modifier"; import { PokeballType } from "../data/pokeball"; import { Gender } from "../data/gender"; import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; @@ -778,19 +778,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!this.stats) { this.stats = [ 0, 0, 0, 0, 0, 0 ]; } - const baseStats = this.getSpeciesForm().baseStats.slice(0); - this.scene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); - if (this.fusionSpecies) { - const fusionBaseStats = this.getFusionSpeciesForm().baseStats; - for (let s = 0; s < this.stats.length; s++) { - baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2); - } - } else if (this.scene.gameMode.isSplicedOnly) { - for (let s = 0; s < this.stats.length; s++) { - baseStats[s] = Math.ceil(baseStats[s] / 2); - } - } - this.scene.applyModifiers(PokemonBaseStatModifier, this.isPlayer(), this, baseStats); + const baseStats = this.calculateBaseStats(); const stats = Utils.getEnumValues(Stat); for (const s of stats) { const isHp = s === Stat.HP; @@ -822,6 +810,27 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, this.stats); } + calculateBaseStats(): number[] { + const baseStats = this.getSpeciesForm().baseStats.slice(0); + // Shuckle Juice + this.scene.applyModifiers(PokemonBaseStatTotalModifier, this.isPlayer(), this, baseStats); + // Old Gateau + this.scene.applyModifiers(PokemonBaseStatFlatModifier, this.isPlayer(), this, baseStats); + if (this.fusionSpecies) { + const fusionBaseStats = this.getFusionSpeciesForm().baseStats; + for (let s = 0; s < this.stats.length; s++) { + baseStats[s] = Math.ceil((baseStats[s] + fusionBaseStats[s]) / 2); + } + } else if (this.scene.gameMode.isSplicedOnly) { + for (let s = 0; s < this.stats.length; s++) { + baseStats[s] = Math.ceil(baseStats[s] / 2); + } + } + // Vitamins + this.scene.applyModifiers(PokemonBaseStatModifier, this.isPlayer(), this, baseStats); + return baseStats; + } + getNature(): Nature { return this.natureOverride !== -1 ? this.natureOverride : this.nature; } diff --git a/src/interfaces/held-modifier-config.ts b/src/interfaces/held-modifier-config.ts index 304de72f01b..2285babdbfd 100644 --- a/src/interfaces/held-modifier-config.ts +++ b/src/interfaces/held-modifier-config.ts @@ -1,7 +1,8 @@ import { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier"; export default interface HeldModifierConfig { - modifierType: PokemonHeldItemModifierType; + modifier: PokemonHeldItemModifierType | PokemonHeldItemModifier; stackCount?: number; isTransferable?: boolean; } diff --git a/src/overrides.ts b/src/overrides.ts index cbde74314c6..8436a01eaa4 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -132,9 +132,9 @@ class DefaultOverrides { // ------------------------- /** 1 to 256, set to null to ignore */ - readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null; + readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = 256; readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null; - readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null; + readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = MysteryEncounterType.DARK_DEAL; // ------------------------- // MODIFIER / ITEM OVERRIDES diff --git a/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts b/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts index ac4128e80f9..2ebd2872d88 100644 --- a/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/dancing-lessons-encounter.test.ts @@ -119,7 +119,7 @@ describe("Dancing Lessons - Mystery Encounter", () => { expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(enemyField[0].species.speciesId).toBe(Species.ORICORIO); - expect(enemyField[0].summonData.battleStats).toEqual([1, 1, 1, 1, 1, 0, 0]); + expect(enemyField[0].summonData.battleStats).toEqual([1, 1, 1, 1, 0, 0, 0]); const moveset = enemyField[0].moveset.map(m => m?.moveId); expect(moveset.some(m => m === Moves.REVELATION_DANCE)).toBeTruthy();