Compare commits

..

No commits in common. "3ae2ecac6eda4a30a641fd79ff6fdb0553cbb9c4" and "c9cda53724f990cffac7b1f0c49fe91dd93c5d9a" have entirely different histories.

133 changed files with 882 additions and 2019 deletions

View File

@ -56,7 +56,7 @@ Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to
- Pokémon Legends: Arceus
- Pokémon Scarlet/Violet
- Firel (Custom Ice Cave, Laboratory, Metropolis, Plains, Power Plant, Seabed, Space, and Volcano biome music)
- Lmz (Custom Ancient Ruins, Jungle, and Lake biome music)
- Lmz (Custom Jungle biome music)
- Andr06 (Custom Slum and Sea biome music)
### 🎵 Sound Effects

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,29 +5,29 @@
"format": "RGBA8888",
"size": {
"w": 78,
"h": 86
"h": 87
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"trimmed": true,
"sourceSize": {
"w": 78,
"h": 86
"w": 80,
"h": 87
},
"spriteSourceSize": {
"x": 0,
"x": 1,
"y": 0,
"w": 78,
"h": 86
"h": 87
},
"frame": {
"x": 0,
"y": 0,
"w": 78,
"h": 86
"h": 87
}
}
]
@ -36,6 +36,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:65266da62e9d2953511c0d68ae431345:c1ca63690bed8dd5af71bb443910c830:56468b7a2883e66dadcd2af13ebd8010$"
"smartupdate": "$TexturePacker:SmartUpdate:d3cce87ee0e3a880d840bffe9373d5d4:7c776d33b75abad1fe36b14a5e5734af:56468b7a2883e66dadcd2af13ebd8010$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "expert_pokemon_breeder.png",
"format": "RGBA8888",
"size": {
"w": 39,
"h": 75
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 80,
"h": 80
},
"spriteSourceSize": {
"x": 21,
"y": 3,
"w": 39,
"h": 75
},
"frame": {
"x": 0,
"y": 0,
"w": 39,
"h": 75
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:cb681265d8dca038a518ab14076fd140:18ff41b1ef6967682643a11695926e58:c59ea3971195f5a395b75223a77d9068$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1006 B

View File

@ -1911,19 +1911,6 @@ export default class BattleScene extends SceneBase {
return false;
}
/**
* Fades out current track for `delay` ms, then fades in new track.
* @param newBgmKey
* @param destroy
* @param delay
*/
fadeAndSwitchBgm(newBgmKey: string, destroy: boolean = false, delay: number = 2000) {
this.fadeOutBgm(delay, destroy);
this.time.delayedCall(delay, () => {
this.playBgm(newBgmKey);
});
}
playSound(sound: string | AnySound, config?: object): AnySound {
const key = typeof sound === "string" ? sound : sound.key;
config = config ?? {};
@ -2170,16 +2157,6 @@ export default class BattleScene extends SceneBase {
return 13.13;
case "battle_macro_boss": //SWSH Rose Battle
return 11.42;
case "mystery_encounter_gen_5_gts": // BW GTS
return 8.52;
case "mystery_encounter_gen_6_gts": // XY GTS
return 9.24;
case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff
return 4.78;
case "mystery_encounter_weird_dream": // EoS Temporal Spire
return 41.42;
case "mystery_encounter_delibirdy": // Firel Delibirdy
return 82.28;
}
return 0;
@ -2626,7 +2603,7 @@ export default class BattleScene extends SceneBase {
}
party.forEach((enemyPokemon: EnemyPokemon, i: integer) => {
if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i]) {
if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i] && heldModifiersConfigs[i].length > 0) {
heldModifiersConfigs[i].forEach(mt => {
let modifier: PokemonHeldItemModifier;
if (mt.modifier instanceof PokemonHeldItemModifierType) {
@ -2637,7 +2614,8 @@ export default class BattleScene extends SceneBase {
}
const stackCount = mt.stackCount ?? 1;
modifier.stackCount = stackCount;
modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable;
// TODO: set isTransferable
// modifier.isTransferrable = mt.isTransferable ?? true;
this.addEnemyModifier(modifier, true);
});
} else {

View File

@ -14,7 +14,7 @@ import { PlayerGender } from "#enums/player-gender";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import i18next from "#app/plugins/i18n";
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter from "./data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
export enum BattleType {
@ -157,7 +157,7 @@ export default class Battle {
}
addPostBattleLoot(enemyPokemon: EnemyPokemon): void {
this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferable, false).map(i => {
this.postBattleLoot.push(...enemyPokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemyPokemon.id && m.isTransferrable, false).map(i => {
const ret = i as PokemonHeldItemModifier;
//@ts-ignore - this is awful to fix/change
ret.pokemonId = null;

View File

@ -1670,7 +1670,7 @@ export class PostAttackStealHeldItemAbAttr extends PostAttackAbAttr {
applyPostAttackAfterMoveTypeCheck(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.stealCondition || this.stealCondition(pokemon, defender, move))) {
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferable);
const heldItems = this.getTargetHeldItems(defender).filter(i => i.isTransferrable);
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => {
@ -1763,7 +1763,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferrable);
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
pokemon.scene.tryTransferHeldItemModifier(stolenItem, pokemon, false).then(success => {
@ -2625,11 +2625,7 @@ export class PreStatStageChangeAbAttr extends AbAttr {
}
}
/**
* Protect one or all {@linkcode BattleStat} from reductions caused by other Pokémon's moves and Abilities
*/
export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
private protectedStat?: BattleStat;
constructor(protectedStat?: BattleStat) {
@ -2638,17 +2634,7 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
this.protectedStat = protectedStat;
}
/**
* Apply the {@linkcode ProtectedStatAbAttr} to an interaction
* @param _pokemon
* @param _passive
* @param simulated
* @param stat the {@linkcode BattleStat} being affected
* @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true if the stat is protected
* @param _args
* @returns true if the stat is protected, false otherwise
*/
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
cancelled.value = true;
return true;
@ -3771,7 +3757,7 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr {
this.multiplier = multiplier;
}
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
(args[0] as Utils.IntegerHolder).value *= this.multiplier;
return true;

View File

@ -488,14 +488,14 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
moveAnims.set(move, null);
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
const moveName = Moves[move].toLowerCase().replace(/\_/g, "-");
const fetchAnimAndResolve = (move: Moves) => {
scene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
scene.cachedFetch(`./battle-anims/${moveName}.json`)
.then(response => {
const contentType = response.headers.get("content-type");
if (!response.ok || contentType?.indexOf("application/json") === -1) {
useDefaultAnim(move, defaultMoveAnim);
logMissingMoveAnim(move, response.status, response.statusText);
console.error(`Could not load animation file for move '${moveName}'`, response.status, response.statusText);
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
return resolve();
}
return response.json();
@ -515,11 +515,6 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
} else {
resolve();
}
})
.catch(error => {
useDefaultAnim(move, defaultMoveAnim);
logMissingMoveAnim(move, error);
return resolve();
});
};
fetchAnimAndResolve(move);
@ -527,29 +522,6 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
});
}
/**
* Populates the default animation for the given move.
*
* @param move the move to populate an animation for
* @param defaultMoveAnim the move to use as the default animation
*/
function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) {
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
}
/**
* Helper method for printing a warning to the console when a move animation is missing.
*
* @param move the move to populate an animation for
* @param optionalParams parameters to add to the error logging
*
* @remarks use {@linkcode useDefaultAnim} to use a default animation
*/
function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
const moveName = Utils.animationFileName(move);
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
}
/**
* Fetches animation configs to be used in a Mystery Encounter
* @param scene

View File

@ -1,6 +1,6 @@
import BattleScene from "../battle-scene";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
import { VariantTier } from "../enums/variant-tier";
import { VariantTier } from "../enums/variant-tiers";
import * as Utils from "../utils";
import Overrides from "#app/overrides";
import { pokemonPrevolutions } from "./pokemon-evolutions";
@ -178,7 +178,7 @@ export class Egg {
// be done because species with no variants get filtered at rollSpecies but if the
// species is set via options or the legendary gacha pokemon gets choosen the check never happens
if (this._species && !getPokemonSpecies(this._species).hasVariants()) {
this._variantTier = VariantTier.STANDARD;
this._variantTier = VariantTier.COMMON;
}
// Needs this._tier so it needs to be generated afer the tier override if bought from same species
this._eggMoveIndex = eggOptions?.eggMoveIndex ?? this.rollEggMoveIndex();
@ -494,12 +494,12 @@ export class Egg {
// place but I don't want to touch the pokemon class.
private rollVariant(): VariantTier {
if (!this.isShiny) {
return VariantTier.STANDARD;
return VariantTier.COMMON;
}
const rand = Utils.randSeedInt(10);
if (rand >= 4) {
return VariantTier.STANDARD; // 6/10
return VariantTier.COMMON; // 6/10
} else if (rand >= 1) {
return VariantTier.RARE; // 3/10
} else {

View File

@ -1,15 +1,16 @@
import { ChargeAnim, initMoveAnim, loadMoveAnimAssets, MoveChargeAnim } from "./battle-anims";
import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, SubstituteTag, TrappedTag, TypeBoostTag } from "./battler-tags";
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, ShellTrapTag, StockpilingTag, TrappedTag, SubstituteTag, TypeBoostTag } from "./battler-tags";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
import { getNonVolatileStatusEffects, getStatusEffectHealText, isNonVolatileStatusEffect, StatusEffect } from "./status-effect";
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects } from "./status-effect";
import { getTypeDamageMultiplier, Type } from "./type";
import { Constructor, NumberHolder } from "#app/utils";
import { Constructor } from "#app/utils";
import * as Utils from "../utils";
import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
import { allAbilities, AllyMoveCategoryPowerBoostAbAttr, applyAbAttrs, applyPostAttackAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, BlockItemTheftAbAttr, BlockNonDirectDamageAbAttr, BlockOneHitKOAbAttr, BlockRecoilDamageAttr, ConfusionOnStatusEffectAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, HealFromBerryUseAbAttr, IgnoreContactAbAttr, IgnoreMoveEffectsAbAttr, IgnoreProtectOnContactAbAttr, MaxMultiHitAbAttr, MoveAbilityBypassAbAttr, MoveEffectChanceMultiplierAbAttr, MoveTypeChangeAbAttr, ReverseDrainAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, UnswappableAbilityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "./ability";
import { AttackTypeBoosterModifier, BerryModifier, PokemonHeldItemModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PreserveBerryModifier } from "../modifier/modifier";
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr, WonderSkinAbAttr, applyPreAttackAbAttrs, MoveTypeChangeAbAttr, UserFieldMoveTypePowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr, VariableMovePowerAbAttr } from "./ability";
import { allAbilities } from "./ability";
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, PokemonMoveAccuracyBoosterModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier";
import { BattlerIndex, BattleType } from "../battle";
import { TerrainType } from "./terrain";
import { ModifierPoolType } from "#app/modifier/modifier-type";
@ -24,7 +25,7 @@ import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { MoveUsedEvent } from "#app/events/battle-scene";
import { BATTLE_STATS, type BattleStat, EFFECTIVE_STATS, type EffectiveStat, getStatKey, Stat } from "#app/enums/stat";
import { Stat, type BattleStat, type EffectiveStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
import { PartyStatusCurePhase } from "#app/phases/party-status-cure-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
@ -35,6 +36,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms";
import { NumberHolder } from "#app/utils";
import { GameMode } from "#app/game-mode";
import { applyChallenges, ChallengeType } from "./challenge";
@ -2134,7 +2136,7 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
if (rand >= this.chance) {
return resolve(false);
}
const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable);
if (heldItems.length) {
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is the bang after tier correct?
@ -2211,7 +2213,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
}
// Considers entire transferrable item pool by default (Knock Off). Otherwise berries only if specified (Incinerate).
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferable);
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable);
if (this.berriesOnly) {
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
@ -2415,16 +2417,6 @@ export class BypassSleepAttr extends MoveAttr {
return false;
}
/**
* Returns arbitrarily high score when Pokemon is asleep, otherwise shouldn't be used
* @param user
* @param target
* @param move
*/
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
return user.status && user.status.effect === StatusEffect.SLEEP ? 200 : -10;
}
}
/**
@ -3847,7 +3839,7 @@ export class StormAccuracyAttr extends VariableAccuracyAttr {
* @extends VariableAccuracyAttr
* @see {@linkcode apply}
*/
export class AlwaysHitMinimizeAttr extends VariableAccuracyAttr {
export class MinimizeAccuracyAttr extends VariableAccuracyAttr {
/**
* @see {@linkcode apply}
* @param user N/A
@ -3982,17 +3974,18 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.NumberHolder);
const atkRatio = user.getEffectiveStat(Stat.ATK, target, move) / target.getEffectiveStat(Stat.DEF, user, move);
const specialRatio = user.getEffectiveStat(Stat.SPATK, target, move) / target.getEffectiveStat(Stat.SPDEF, user, move);
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true);
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true);
if (predictedPhysDmg > predictedSpecDmg) {
// Shell Side Arm is much more complicated than it looks, this is a partial implementation to try to achieve something similar to the games
if (atkRatio > specialRatio) {
category.value = MoveCategory.PHYSICAL;
return true;
} else if (predictedPhysDmg === predictedSpecDmg && user.randSeedInt(2) === 0) {
} else if (atkRatio === specialRatio && user.randSeedInt(2) === 0) {
category.value = MoveCategory.PHYSICAL;
return true;
}
return false;
}
}
@ -4859,9 +4852,7 @@ export class RemoveAllSubstitutesAttr extends MoveEffectAttr {
}
/**
* Attribute used when a move can deal damage to {@linkcode BattlerTagType}
* Moves that always hit but do not deal double damage: Thunder, Fissure, Sky Uppercut,
* Smack Down, Hurricane, Thousand Arrows
* Attribute used when a move hits a {@linkcode BattlerTagType} for double damage
* @extends MoveAttr
*/
export class HitsTagAttr extends MoveAttr {
@ -4870,7 +4861,7 @@ export class HitsTagAttr extends MoveAttr {
/** Should this move deal double damage against {@linkcode HitsTagAttr.tagType}? */
public doubleDamage: boolean;
constructor(tagType: BattlerTagType, doubleDamage: boolean = false) {
constructor(tagType: BattlerTagType, doubleDamage?: boolean) {
super();
this.tagType = tagType;
@ -4882,17 +4873,6 @@ export class HitsTagAttr extends MoveAttr {
}
}
/**
* Used for moves that will always hit for a given tag but also doubles damage.
* Moves include: Gust, Stomp, Body Slam, Surf, Earthquake, Magnitude, Twister,
* Whirlpool, Dragon Rush, Heat Crash, Steam Roller, Flying Press
*/
export class HitsTagForDoubleDamageAttr extends HitsTagAttr {
constructor(tagType: BattlerTagType) {
super(tagType, true);
}
}
export class AddArenaTagAttr extends MoveEffectAttr {
public tagType: ArenaTagType;
public turnCount: integer;
@ -6413,7 +6393,7 @@ export class AttackedByItemAttr extends MoveAttr {
*/
getCondition(): MoveConditionFunc {
return (user: Pokemon, target: Pokemon, move: Move) => {
const heldItems = target.getHeldItems().filter(i => i.isTransferable);
const heldItems = target.getHeldItems().filter(i => i.isTransferrable);
if (heldItems.length === 0) {
return false;
}
@ -6772,11 +6752,12 @@ export function initMoves() {
new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1)
.slicingMove(),
new AttackMove(Moves.GUST, Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.FLYING)
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
.windMove(),
new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1),
new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1)
.attr(ForceSwitchOutAttr)
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.ignoresSubstitute()
.hidesTarget()
.windMove(),
@ -6789,8 +6770,8 @@ export function initMoves() {
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1),
new AttackMove(Moves.STOMP, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 1)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(FlinchAttr),
new AttackMove(Moves.DOUBLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1)
.attr(MultiHitAttr, MultiHitType._2),
@ -6814,8 +6795,8 @@ export function initMoves() {
.attr(OneHitKOAccuracyAttr),
new AttackMove(Moves.TACKLE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
new AttackMove(Moves.BODY_SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 30, 0, 1)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1)
.attr(TrapAttr, BattlerTagType.WRAP),
@ -6883,7 +6864,7 @@ export function initMoves() {
new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1),
new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1)
.target(MoveTarget.ALL_NEAR_OTHERS)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERWATER)
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true)
.attr(GulpMissileTagAttr),
new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
.attr(StatusEffectAttr, StatusEffect.FREEZE),
@ -6966,18 +6947,18 @@ export function initMoves() {
new AttackMove(Moves.THUNDER, Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 1)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
.attr(ThunderAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.FLYING),
.attr(HitsTagAttr, BattlerTagType.FLYING, false),
new AttackMove(Moves.ROCK_THROW, Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1)
.makesContact(false),
new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND)
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
.attr(OneHitKOAttr)
.attr(OneHitKOAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND)
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, false)
.makesContact(false),
new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1)
.attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", {pokemonName: "{USER}"}), BattlerTagType.UNDERGROUND)
@ -7366,7 +7347,7 @@ export function initMoves() {
.attr(PreMoveMessageAttr, magnitudeMessageFunc)
.attr(MagnitudePowerAttr)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERGROUND)
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.DYNAMIC_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, 100, 0, 2)
@ -7422,7 +7403,7 @@ export function initMoves() {
new AttackMove(Moves.CROSS_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2)
.attr(HighCritAttr),
new AttackMove(Moves.TWISTER, Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.FLYING)
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
.attr(FlinchAttr)
.windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
@ -7454,7 +7435,7 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.DEF ], -1),
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.UNDERWATER),
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
.attr(MultiHitAttr, MultiHitType.BEAT_UP)
.attr(BeatUpAttr)
@ -7552,7 +7533,7 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true)
.condition((user, target, move) => !target.status && !target.scene.arena.getTagOnSide(ArenaTagType.SAFEGUARD, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)),
new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferable).length > 0 ? 1.5 : 1)
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferrable).length > 0 ? 1.5 : 1)
.attr(RemoveHeldItemAttr, false),
new AttackMove(Moves.ENDEAVOR, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3)
.attr(MatchHpAttr)
@ -7908,8 +7889,8 @@ export function initMoves() {
new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4)
.pulseMove(),
new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(FlinchAttr),
new AttackMove(Moves.POWER_GEM, Type.ROCK, MoveCategory.SPECIAL, 80, 100, 20, -1, 0, 4),
new AttackMove(Moves.DRAIN_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 4)
@ -8106,7 +8087,7 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
.attr(HitsTagAttr, BattlerTagType.FLYING)
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.makesContact(false),
new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
.attr(CritOnlyAttr),
@ -8119,9 +8100,9 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.danceMove(),
new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
.attr(AlwaysHitMinimizeAttr)
.attr(MinimizeAccuracyAttr)
.attr(CompareWeightPowerAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED),
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true),
new AttackMove(Moves.SYNCHRONOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
.target(MoveTarget.ALL_NEAR_OTHERS)
.condition(unknownTypeCondition)
@ -8204,7 +8185,7 @@ export function initMoves() {
new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5)
.unimplemented(),
new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferable).reduce((v, m) => v + m.stackCount, 0))),
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferrable).reduce((v, m) => v + m.stackCount, 0))),
new StatusMove(Moves.REFLECT_TYPE, Type.NORMAL, -1, 15, -1, 0, 5)
.ignoresSubstitute()
.attr(CopyTypeAttr),
@ -8272,14 +8253,12 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.DEF ], -1)
.slicingMove(),
new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
.attr(AlwaysHitMinimizeAttr)
.attr(MinimizeAccuracyAttr)
.attr(CompareWeightPowerAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED),
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true),
new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5)
.attr(StatStageChangeAttr, [ Stat.ACC ], -1),
new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(FlinchAttr),
new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5)
.attr(StatStageChangeAttr, [ Stat.DEF ], 3, true),
@ -8292,7 +8271,7 @@ export function initMoves() {
new AttackMove(Moves.HURRICANE, Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5)
.attr(ThunderAccuracyAttr)
.attr(ConfuseAttr)
.attr(HitsTagAttr, BattlerTagType.FLYING)
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.windMove(),
new AttackMove(Moves.HEAD_CHARGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5)
.attr(RecoilAttr)
@ -8346,9 +8325,9 @@ export function initMoves() {
.attr(LastMoveDoublePowerAttr, Moves.FUSION_FLARE)
.makesContact(false),
new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6)
.attr(AlwaysHitMinimizeAttr)
.attr(MinimizeAccuracyAttr)
.attr(FlyingTypeMultiplierAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.condition(failOnGravityCondition),
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
.target(MoveTarget.USER_SIDE)
@ -8519,8 +8498,8 @@ export function initMoves() {
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
.attr(HitsTagAttr, BattlerTagType.FLYING)
.attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN)
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN, false)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
.makesContact(false)
@ -8777,8 +8756,6 @@ export function initMoves() {
.partial()
.ignoresVirtual(),
new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.partial()
.ignoresVirtual(),
new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
@ -9126,7 +9103,7 @@ export function initMoves() {
new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8)
.attr(ShellSideArmCategoryAttr)
.attr(StatusEffectAttr, StatusEffect.POISON)
.partial(), // Physical version of the move does not make contact
.partial(),
new AttackMove(Moves.MISTY_EXPLOSION, Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
.attr(SacrificialAttr)
.target(MoveTarget.ALL_NEAR_OTHERS)

View File

@ -2,7 +2,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattl
import { trainerConfigs, } from "#app/data/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
@ -99,7 +99,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
const trainerConfig = trainerConfigs[trainerType].clone();
const trainerSpriteKey = trainerConfig.getSpriteKey();
encounter.enemyPartyConfigs.push({
levelAdditiveModifier: 1,
levelAdditiveMultiplier: 1,
trainerConfig: trainerConfig
});
@ -152,6 +152,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SACRED_ASH], guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ULTRA], fillRemaining: true }, [eggOptions]);
return initBattleWithEnemyConfig(scene, config);
}
)
@ -179,7 +180,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
)
.withOutroDialogue([
{
text: `${namespace}.outro`
}
text: `${namespace}.outro`,
},
])
.build();

View File

@ -4,9 +4,9 @@ import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { 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 MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { PersistentModifierRequirement } from "../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";
@ -208,7 +208,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
// Calculate boss mon
const config: EnemyPartyConfig = {
levelAdditiveModifier: 1,
levelAdditiveMultiplier: 1,
pokemonConfigs: [
{
species: getPokemonSpecies(Species.GREEDENT),
@ -291,7 +291,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!;
const berryMap = encounter.misc.berryItemsMap;
// Returns 2/5 of the berries stolen to each Pokemon
// Returns 2/5 of the berries stolen from each Pokemon
const party = scene.getParty();
party.forEach(pokemon => {
const stolenBerries: BerryModifier[] = berryMap.get(pokemon.id);
@ -310,7 +310,6 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
}
}
});
await scene.updateModifiers(true);
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
leaveEncounterWithoutBattle(scene, true);

View File

@ -3,9 +3,9 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species";

View File

@ -17,13 +17,13 @@ import { randSeedInt } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../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 { TrainerSlot } from "#app/data/trainer-config";
import { applyModifierTypeToPlayerPokemon, getEncounterPokemonLevelForWave, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { applyModifierTypeToPlayerPokemon, getHighestStatPlayerPokemon, getSpriteKeysFromPokemon } 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";
@ -56,11 +56,12 @@ export const BerriesAboundEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [{
level: level,
species: bossSpecies,

View File

@ -9,7 +9,6 @@ import {
transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
getRandomPartyMemberFunc,
trainerConfigs,
TrainerPartyCompoundTemplate,
TrainerPartyTemplate,
@ -18,12 +17,14 @@ import {
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { Moves } from "#enums/moves";
@ -191,7 +192,6 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
new AttackTypeBoosterHeldItemTypeRequirement(Type.BUG, 1),
new TypeRequirement(Type.BUG, false, 1)
))
.withMaxAllowedEncounters(1)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([]) // These are set in onInit()
.withAutoHideIntroVisuals(false)
@ -286,8 +286,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
// Player gets different rewards depending on the number of bug types they have
const numBugTypes = scene.getParty().filter(p => p.isOfType(Type.BUG, true)).length;
const numBugTypesText = i18next.t(`${namespace}.numBugTypes`, { count: numBugTypes });
encounter.setDialogueToken("numBugTypes", numBugTypesText);
encounter.setDialogueToken("numBugTypes", numBugTypes.toString());
if (numBugTypes < 2) {
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.SUPER_LURE, modifierTypes.GREAT_BALL], fillRemaining: false });
@ -583,6 +582,16 @@ function getTrainerConfigForWave(waveIndex: number) {
return config;
}
function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void) {
return (scene: BattleScene, level: number, strength: PartyMemberStrength) => {
let species = Utils.randSeedItem(speciesPool);
if (!ignoreEvolution) {
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength);
}
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess);
};
}
function doBugTypeMoveTutor(scene: BattleScene): Promise<void> {
return new Promise<void>(async resolve => {
const moveOptions = scene.currentBattle.mysteryEncounter!.misc.moveTutorOptions;

View File

@ -5,7 +5,7 @@ import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifi
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
@ -246,12 +246,12 @@ export const ClowningAroundEncounter: MysteryEncounter =
const party = scene.getParty();
let mostHeldItemsPokemon = party[0];
let count = mostHeldItemsPokemon.getHeldItems()
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0);
party.forEach(pokemon => {
const nextCount = pokemon.getHeldItems()
.filter(m => m.isTransferable && !(m instanceof BerryModifier))
.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
.reduce((v, m) => v + m.stackCount, 0);
if (nextCount > count) {
mostHeldItemsPokemon = pokemon;
@ -276,7 +276,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
let numUltra = 0;
let numRogue = 0;
items.filter(m => m.isTransferable && !(m instanceof BerryModifier))
items.filter(m => m.isTransferrable && !(m instanceof BerryModifier))
.forEach(m => {
const type = m.type.withTierFromPool();
const tier = type.tier ?? ModifierTier.ULTRA;

View File

@ -3,8 +3,8 @@ import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/po
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { getPokemonSpecies } from "#app/data/pokemon-species";
@ -19,7 +19,7 @@ import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-
import { DANCING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { BattlerIndex } from "#app/battle";
import { catchPokemon, getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { catchPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { PokeballType } from "#enums/pokeball";
import { modifierTypes } from "#app/modifier/modifier-type";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
@ -107,7 +107,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!;
const species = getPokemonSpecies(Species.ORICORIO);
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const enemyPokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, false);
if (!enemyPokemon.moveset.some(m => m && m.getMove().id === Moves.REVELATION_DANCE)) {
if (enemyPokemon.moveset.length < 4) {
@ -133,7 +133,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
}
const oricorioData = new PokemonData(enemyPokemon);
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, oricorioData);
const oricorio = scene.addEnemyPokemon(species, scene.currentBattle.enemyLevels![0], TrainerSlot.NONE, false, oricorioData);
// Adds a real Pokemon sprite to the field (required for the animation)
scene.getEnemyParty().forEach(enemyPokemon => {
@ -146,6 +146,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
encounter.loadAssets.push(oricorio.loadAssets());
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [{
species: species,
dataSource: oricorioData,

View File

@ -5,8 +5,8 @@ import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { modifierTypes } from "#app/modifier/modifier-type";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils";
import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -18,7 +18,7 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** i18n namespace for encounter */
const namespace = "mysteryEncounter:darkDeal";
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and Mythicals */
/** Exclude Ultra Beasts (inludes Cosmog/Solgaleo/Lunala/Necrozma), Paradox (includes Miraidon/Koraidon), Eternatus, and egg-locked mythicals */
const excludedBosses = [
Species.NECROZMA,
Species.COSMOG,
@ -63,24 +63,11 @@ const excludedBosses = [
Species.CELEBI,
Species.DEOXYS,
Species.JIRACHI,
Species.DARKRAI,
Species.PHIONE,
Species.MANAPHY,
Species.ARCEUS,
Species.SHAYMIN,
Species.VICTINI,
Species.MELOETTA,
Species.KELDEO,
Species.GENESECT,
Species.DIANCIE,
Species.HOOPA,
Species.VOLCANION,
Species.MAGEARNA,
Species.MARSHADOW,
Species.ZERAORA,
Species.ZARUDE,
Species.MELTAN,
Species.MELMETAL,
Species.PECHARUNT,
];
@ -164,7 +151,7 @@ export const DarkDealEncounter: MysteryEncounter =
// Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+
const roll = randSeedInt(100);
const starterTier: number | [number, number] =
roll >= 65 ? 6 : roll >= 15 ? 7 : roll >= 5 ? 8 : [9, 10];
roll > 65 ? 6 : roll > 15 ? 7 : roll > 5 ? 8 : [9, 10];
const bossSpecies = getPokemonSpecies(getRandomSpeciesByStarterTier(starterTier, excludedBosses, bossTypes));
const pokemonConfig: EnemyPokemonConfig = {
species: bossSpecies,

View File

@ -4,9 +4,9 @@ import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifi
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { 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 MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -33,8 +33,6 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
"PokemonBaseStatTotalModifier"
];
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 1.5;
/**
* Delibird-y encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3804 | GitHub Issue #3804}
@ -44,7 +42,7 @@ 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
.withSceneRequirement(new MoneyRequirement(0, 2)) // Must have enough money for it to spawn at the very least
.withPrimaryPokemonRequirement(new CombinationPokemonRequirement( // 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)
@ -95,18 +93,12 @@ export const DelibirdyEncounter: MysteryEncounter =
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
encounter.setDialogueToken("delibirdName", getPokemonSpecies(Species.DELIBIRD).getName());
scene.loadBgm("mystery_encounter_delibirdy", "mystery_encounter_delibirdy.mp3");
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm("mystery_encounter_delibirdy");
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, DELIBIRDY_MONEY_PRICE_MULTIPLIER) // Must have money to spawn
.withSceneMoneyRequirement(0, 2) // Must have money to spawn
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,

View File

@ -9,7 +9,7 @@ import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, {
MysteryEncounterBuilder,
} from "#app/data/mystery-encounters/mystery-encounter";
} from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";

View File

@ -6,7 +6,7 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Stat } from "#enums/stat";

View File

@ -3,8 +3,8 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounte
import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { TypeRequirement } from "../mystery-encounter-requirements";
import { Species } from "#enums/species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender";

View File

@ -17,12 +17,12 @@ import {
} from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { TrainerSlot } from "#app/data/trainer-config";
import { getEncounterPokemonLevelForWave, getSpriteKeysFromPokemon, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -54,11 +54,12 @@ export const FightOrFlightEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!;
// Calculate boss mon
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", bossPokemon.getNameToRender());
const config: EnemyPartyConfig = {
levelAdditiveMultiplier: 1,
pokemonConfigs: [{
level: level,
species: bossSpecies,
@ -68,7 +69,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
// Randomly boost 1 stat 2 stages
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [randSeedInt(5)], 2));
}
}],
};

View File

@ -1,7 +1,7 @@
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
@ -84,7 +84,12 @@ export const FunAndGamesEncounter: MysteryEncounter =
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm("mystery_encounter_fun_and_games");
// Change the bgm
scene.fadeOutBgm(2000, false);
scene.time.delayedCall(2000, () => {
scene.playBgm("mystery_encounter_fun_and_games");
});
return true;
})
.withOption(MysteryEncounterOptionBuilder
@ -170,9 +175,7 @@ async function summonPlayerPokemon(scene: BattleScene) {
const party = scene.getParty();
const chosenIndex = party.indexOf(playerPokemon);
if (chosenIndex !== 0) {
const leadPokemon = party[0];
party[0] = playerPokemon;
party[chosenIndex] = leadPokemon;
[party[chosenIndex], party[0]] = [party[chosenIndex], party[chosenIndex]];
}
// Do trainer summon animation

View File

@ -4,7 +4,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { getPlayerModifierTypeOptions, ModifierPoolType, ModifierTypeOption, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { Species } from "#enums/species";
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
@ -23,7 +23,6 @@ import { getPokeballAtlasKey, getPokeballTintColor, PokeballType } from "#app/da
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { trainerNamePools } from "#app/data/trainer-names";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:globalTradeSystem";
@ -119,13 +118,17 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey);
// Change the bgm
scene.fadeOutBgm(1500, false);
scene.time.delayedCall(1500, () => {
scene.playBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey);
});
return true;
})
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
@ -197,7 +200,6 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000);
scene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon);
await hideTradeBackground(scene);
tradedPokemon.destroy();
@ -208,7 +210,6 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withHasDexProgress(true)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
@ -217,7 +218,8 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!;
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Randomly generate a Wonder Trade pokemon
const randomTradeOption = generateTradeOption(scene.getParty().map(p => p.species));
// const randomTradeOption = generateTradeOption(scene.getParty().map(p => p.species));
const randomTradeOption = getPokemonSpecies(Species.BURMY);
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
// Extra shiny roll at 1/128 odds (boosted by events and charms)
if (!tradePokemon.shiny) {
@ -278,8 +280,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
await showTradeBackground(scene);
await doPokemonTradeSequence(scene, tradedPokemon, newPlayerPokemon);
await showEncounterText(scene, `${namespace}.trade_received`, null, 0, true, 4000);
scene.playBgm(encounter.misc.bgmKey);
await addPokemonDataToDexAndValidateAchievements(scene, newPlayerPokemon);
scene.playBgm(scene.currentBattle.mysteryEncounter!.misc.bgmKey);
await hideTradeBackground(scene);
tradedPokemon.destroy();
@ -300,7 +301,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const onPokemonSelected = (pokemon: PlayerPokemon) => {
// Get Pokemon held items and filter for valid ones
const validItems = pokemon.getHeldItems().filter((it) => {
return it.isTransferable;
return it.isTransferrable;
});
return validItems.map((modifier: PokemonHeldItemModifier) => {
@ -321,7 +322,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const selectableFilter = (pokemon: Pokemon) => {
// If pokemon has items to trade
const meetsReqs = pokemon.getHeldItems().filter((it) => {
return it.isTransferable;
return it.isTransferrable;
}).length > 0;
if (!meetsReqs) {
return getEncounterText(scene, `${namespace}.option.3.invalid_selection`) ?? null;

View File

@ -3,8 +3,8 @@ import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter-phase-utils";
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";

View File

@ -15,7 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import BattleScene from "#app/battle-scene";
import * as Utils from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
@ -75,7 +75,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
const hardSpriteKey = hardConfig.getSpriteKey(female, hardConfig.doubleOnly);
encounter.enemyPartyConfigs.push({
trainerConfig: hardConfig,
levelAdditiveModifier: 1,
levelAdditiveMultiplier: 1,
female: female,
});
@ -98,7 +98,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter =
const brutalSpriteKey = brutalConfig.getSpriteKey(female, brutalConfig.doubleOnly);
encounter.enemyPartyConfigs.push({
trainerConfig: brutalConfig,
levelAdditiveModifier: 1.5,
levelAdditiveMultiplier: 1.5,
female: female,
});

View File

@ -5,8 +5,8 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { getPokemonSpecies } from "#app/data/pokemon-species";
@ -68,7 +68,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Calculate boss mon
const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5,
levelAdditiveMultiplier: 0.5,
disableSwitch: true,
pokemonConfigs: [
{
@ -179,7 +179,6 @@ export const MysteriousChestEncounter: MysteryEncounter =
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
await showEncounterText(scene, `${namespace}.option.1.bad`);
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
setEncounterRewards(scene, { fillRemaining: true });
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
}
}

View File

@ -2,8 +2,8 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Stat } from "#enums/stat";

View File

@ -1,7 +1,7 @@
import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { TrainerSlot } from "#app/data/trainer-config";
import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifier/modifier";
@ -25,7 +25,7 @@ const namespace = "mysteryEncounter:safariZone";
const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
const SAFARI_MONEY_MULTIPLIER = 2;
const SAFARI_MONEY_MULTIPLIER = 2.75;
/**
* Safari Zone encounter.

View File

@ -5,9 +5,9 @@ import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -19,9 +19,6 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:shadyVitaminDealer";
const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5;
const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 3.5;
/**
* Shady Vitamin Dealer encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3798 | GitHub Issue #3798}
@ -31,7 +28,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER)
.withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
.withSceneRequirement(new MoneyRequirement(0, 1.5)) // Must have the money for at least the cheap deal
.withPrimaryPokemonHealthRatioRequirement([0.5, 1]) // At least 1 Pokemon must have above half HP
.withIntroSpriteConfigs([
{
@ -67,7 +64,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)
.withSceneMoneyRequirement(0, 1.5)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`,
@ -118,7 +115,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
}
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(scene);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Damage and status applied after dealer leaves (to make thematic sense)
@ -145,7 +142,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(0, VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER)
.withSceneMoneyRequirement(0, 3.5)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
buttonTooltip: `${namespace}.option.2.tooltip`,
@ -196,7 +193,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
await applyModifierTypeToPlayerPokemon(scene, chosenPokemon, modType);
}
leaveEncounterWithoutBattle(scene, true);
leaveEncounterWithoutBattle(scene);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Status applied after dealer leaves (to make thematic sense)

View File

@ -1,24 +1,22 @@
import { STEALING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import { StatusEffect } from "#app/data/status-effect";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MoveRequirement } from "../mystery-encounter-requirements";
import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { AiType, PokemonMove } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { BerryType } from "#enums/berry-type";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounter:slumberingSnorlax";
@ -40,7 +38,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
fileRoot: "pokemon",
hasShadow: true,
tint: 0.25,
scale: 1.25,
scale: 1.5,
repeat: true,
y: 5,
},
@ -60,22 +58,10 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
species: bossSpecies,
isBoss: true,
status: [StatusEffect.SLEEP, 5], // Extra turns on timer for Snorlax's start of fight moves
moveSet: [Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType,
stackCount: 2
},
{
modifier: generateModifierType(scene, modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType,
stackCount: 2
},
],
mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }),
aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep
moveSet: [Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT]
};
const config: EnemyPartyConfig = {
levelAdditiveModifier: 0.5,
levelAdditiveMultiplier: 0.5,
pokemonConfigs: [pokemonConfig],
};
encounter.enemyPartyConfigs = [config];

View File

@ -2,8 +2,8 @@ import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig
import { randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement, WaveModulusRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoneyRequirement, WaveModulusRequirement } from "../mystery-encounter-requirements";
import Pokemon, { EnemyPokemon } from "#app/field/pokemon";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -20,13 +20,12 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { getEncounterPokemonLevelForWave, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:teleportingHijinks";
const MONEY_COST_MULTIPLIER = 1.75;
const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND, Biome.WASTELAND, Biome.DOJO];
const MONEY_COST_MULTIPLIER = 2.5;
const BIOME_CANDIDATES = [Biome.SPACE, Biome.FAIRY_CAVE, Biome.LABORATORY, Biome.ISLAND];
const MACHINE_INTERFACING_TYPES = [Type.ELECTRIC, Type.STEEL];
/**
@ -131,7 +130,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!;
// Init enemy
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
@ -167,7 +166,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
await showEncounterText(scene, `${namespace}.attacked`);
// Init enemy
const level = getEncounterPokemonLevelForWave(scene, STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER);
const level = (scene.currentBattle.enemyLevels?.[0] ?? scene.currentBattle.waveIndex) + Math.max(Math.round((scene.currentBattle.waveIndex / 10)), 0);
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));

View File

@ -1,549 +0,0 @@
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { trainerConfigs } from "#app/data/trainer-config";
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import { randSeedShuffle } from "#app/utils";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Biome } from "#enums/biome";
import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next";
import { Species } from "#enums/species";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { Nature } from "#enums/nature";
import { Moves } from "#enums/moves";
import { Type } from "#app/data/type";
import { Stat } from "#enums/stat";
import { PlayerPokemon } from "#app/field/pokemon";
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { IEggOptions } from "#app/data/egg";
import { EggSourceType } from "#enums/egg-source-types";
import { EggTier } from "#enums/egg-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { achvs } from "#app/system/achv";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:expertPokemonBreeder";
const trainerNameKey = "trainerNames:expert_pokemon_breeder";
const FIRST_STAGE_EVOLUTION_WAVE = 30;
const SECOND_STAGE_EVOLUTION_WAVE = 45;
const FINAL_STAGE_EVOLUTION_WAVE = 60;
const FRIENDSHIP_ADDED = 20;
class BreederSpeciesEvolution {
species: Species;
evolution: number;
constructor(species: Species, evolution: number) {
this.species = species;
this.evolution = evolution;
}
}
const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[Species.MUNCHLAX, new BreederSpeciesEvolution(Species.SNORLAX, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.HAPPINY, new BreederSpeciesEvolution(Species.CHANSEY, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.BLISSEY, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.MAGBY, new BreederSpeciesEvolution(Species.MAGMAR, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MAGMORTAR, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.ELEKID, new BreederSpeciesEvolution(Species.ELECTABUZZ, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ELECTIVIRE, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.RIOLU, new BreederSpeciesEvolution(Species.LUCARIO, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.BUDEW, new BreederSpeciesEvolution(Species.ROSELIA, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ROSERADE, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.TOXEL, new BreederSpeciesEvolution(Species.TOXTRICITY, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.MIME_JR, new BreederSpeciesEvolution(Species.GALAR_MR_MIME, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.MR_RIME, FINAL_STAGE_EVOLUTION_WAVE)]
];
const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [
[Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.JYNX],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.IGGLYBUFF, new BreederSpeciesEvolution(Species.JIGGLYPUFF, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.WIGGLYTUFF, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.AZURILL, new BreederSpeciesEvolution(Species.MARILL, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.AZUMARILL, FINAL_STAGE_EVOLUTION_WAVE)],
[Species.WYNAUT, new BreederSpeciesEvolution(Species.WOBBUFFET, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.CHINGLING, new BreederSpeciesEvolution(Species.CHIMECHO, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.BONSLY, new BreederSpeciesEvolution(Species.SUDOWOODO, SECOND_STAGE_EVOLUTION_WAVE)],
[Species.MANTYKE, new BreederSpeciesEvolution(Species.MANTINE, SECOND_STAGE_EVOLUTION_WAVE)]
];
/**
* The Expert Pokémon Breeder encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3818 | GitHub Issue #3818}
* @see For biome requirements check {@linkcode mysteryEncountersByBiome}
*/
export const TheExpertPokemonBreederEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER)
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(4, 6, true) // Must have at least 4 legal pokemon in party
.withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([
{
text: `${namespace}.intro`,
},
{
speaker: trainerNameKey,
text: `${namespace}.intro_dialogue`,
},
])
.withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
const waveIndex = scene.currentBattle.waveIndex;
// Calculates what trainers are available for battle in the encounter
// If player is in space biome, uses special "Space" version of the trainer
encounter.enemyPartyConfigs = [
getPartyConfig(scene)
];
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE;
encounter.spriteConfigs = [
{
spriteKey: cleffaSpecies.toString(),
fileRoot: "pokemon",
hasShadow: true,
repeat: true,
x: 14,
y: -2,
yShadow: -2
},
{
spriteKey: "expert_pokemon_breeder",
fileRoot: "trainer",
hasShadow: true,
x: -14,
y: 4,
yShadow: 2
},
];
// Determine the 3 pokemon the player can battle with
let partyCopy = scene.getParty().slice(0);
partyCopy = partyCopy
.filter(p => p.isAllowedInBattle())
.sort((a, b) => a.friendship - b.friendship);
const pokemon1 = partyCopy[0];
const pokemon2 = partyCopy[1];
const pokemon3 = partyCopy[2];
encounter.setDialogueToken("pokemon1Name", pokemon1.getNameToRender());
encounter.setDialogueToken("pokemon2Name", pokemon2.getNameToRender());
encounter.setDialogueToken("pokemon3Name", pokemon3.getNameToRender());
// Dialogue and egg calcs for Pokemon 1
const [pokemon1CommonEggs, pokemon1RareEggs] = calculateEggRewardsForPokemon(pokemon1);
let pokemon1Tooltip = getEncounterText(scene, `${namespace}.option.1.tooltip_base`)!;
if (pokemon1RareEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon1RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon1Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1RareEggs", eggsText);
}
if (pokemon1CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon1CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon1Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
}
encounter.options[0].dialogue!.buttonTooltip = pokemon1Tooltip;
// Dialogue and egg calcs for Pokemon 2
const [pokemon2CommonEggs, pokemon2RareEggs] = calculateEggRewardsForPokemon(pokemon2);
let pokemon2Tooltip = getEncounterText(scene, `${namespace}.option.2.tooltip_base`)!;
if (pokemon2RareEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon2RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon2Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon2RareEggs", eggsText);
}
if (pokemon2CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon2Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon1CommonEggs", eggsText);
}
encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip;
// Dialogue and egg calcs for Pokemon 3
const [pokemon3CommonEggs, pokemon3RareEggs] = calculateEggRewardsForPokemon(pokemon3);
let pokemon3Tooltip = getEncounterText(scene, `${namespace}.option.3.tooltip_base`)!;
if (pokemon3RareEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon3RareEggs, rarity: i18next.t("egg:greatTier") });
pokemon3Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon3RareEggs", eggsText);
}
if (pokemon3CommonEggs > 0) {
const eggsText = i18next.t(`${namespace}.numEggs`, { count: pokemon3CommonEggs, rarity: i18next.t("egg:defaultTier") });
pokemon3Tooltip += i18next.t(`${namespace}.eggs_tooltip`, { eggs: eggsText });
encounter.setDialogueToken("pokemon3CommonEggs", eggsText);
}
encounter.options[2].dialogue!.buttonTooltip = pokemon3Tooltip;
encounter.misc = {
pokemon1,
pokemon1CommonEggs,
pokemon1RareEggs,
pokemon2,
pokemon2CommonEggs,
pokemon2RareEggs,
pokemon3,
pokemon3CommonEggs,
pokemon3RareEggs
};
return true;
})
.withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.1.label`,
selected: [
{
speaker: trainerNameKey,
text: `${namespace}.option.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn battle with first pokemon
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1);
// Configure outro dialogue for egg rewards
encounter.dialogue.outro = [
{
speaker: trainerNameKey,
text: `${namespace}.outro`,
},
];
if (encounter.dialogueTokens.hasOwnProperty("pokemon1CommonEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1CommonEggs"] }),
});
}
if (encounter.dialogueTokens.hasOwnProperty("pokemon1RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon1RareEggs"] }),
});
}
initBattleWithEnemyConfig(scene, config);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Give achievement if in Space biome
checkAchievement(scene);
// Give 20 friendship to the chosen pokemon
scene.currentBattle.mysteryEncounter!.misc.pokemon1.addFriendship(FRIENDSHIP_ADDED);
await restorePartyAndHeldItems(scene);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.2.label`,
selected: [
{
speaker: trainerNameKey,
text: `${namespace}.option.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn battle with second pokemon
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2);
// Configure outro dialogue for egg rewards
encounter.dialogue.outro = [
{
speaker: trainerNameKey,
text: `${namespace}.outro`,
},
];
if (encounter.dialogueTokens.hasOwnProperty("pokemon2CommonEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2CommonEggs"] }),
});
}
if (encounter.dialogueTokens.hasOwnProperty("pokemon2RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon2RareEggs"] }),
});
}
initBattleWithEnemyConfig(scene, config);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Give achievement if in Space biome
checkAchievement(scene);
// Give 20 friendship to the chosen pokemon
scene.currentBattle.mysteryEncounter!.misc.pokemon2.addFriendship(FRIENDSHIP_ADDED);
await restorePartyAndHeldItems(scene);
})
.build()
)
.withOption(
MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
.withDialogue({
buttonLabel: `${namespace}.option.3.label`,
selected: [
{
speaker: trainerNameKey,
text: `${namespace}.option.selected`,
},
],
})
.withOptionPhase(async (scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!;
// Spawn battle with third pokemon
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
// Remove all Pokemon from the party except the chosen Pokemon
removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3);
// Configure outro dialogue for egg rewards
encounter.dialogue.outro = [
{
speaker: trainerNameKey,
text: `${namespace}.outro`,
},
];
if (encounter.dialogueTokens.hasOwnProperty("pokemon3CommonEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3CommonEggs"] }),
});
}
if (encounter.dialogueTokens.hasOwnProperty("pokemon3RareEggs")) {
encounter.dialogue.outro.push({
text: i18next.t(`${namespace}.gained_eggs`, { numEggs: encounter.dialogueTokens["pokemon3RareEggs"] }),
});
}
initBattleWithEnemyConfig(scene, config);
})
.withPostOptionPhase(async (scene: BattleScene) => {
// Give achievement if in Space biome
checkAchievement(scene);
// Give 20 friendship to the chosen pokemon
scene.currentBattle.mysteryEncounter!.misc.pokemon3.addFriendship(FRIENDSHIP_ADDED);
await restorePartyAndHeldItems(scene);
})
.build()
)
.withOutroDialogue([
{
text: `${namespace}.outro`,
},
])
.build();
function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
// Bug type superfan trainer config
const waveIndex = scene.currentBattle.waveIndex;
const breederConfig = trainerConfigs[TrainerType.EXPERT_POKEMON_BREEDER].clone();
breederConfig.name = i18next.t(trainerNameKey);
// First mon is *always* this special cleffa
const cleffaSpecies = waveIndex < FIRST_STAGE_EVOLUTION_WAVE ? Species.CLEFFA : waveIndex < FINAL_STAGE_EVOLUTION_WAVE ? Species.CLEFAIRY : Species.CLEFABLE;
const baseConfig: EnemyPartyConfig = {
trainerType: TrainerType.EXPERT_POKEMON_BREEDER,
pokemonConfigs: [
{
nickname: i18next.t(`${namespace}.cleffa_1_nickname`),
species: getPokemonSpecies(cleffaSpecies),
isBoss: false,
abilityIndex: 1, // Magic Guard
shiny: false,
nature: Nature.ADAMANT,
moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH],
ivs: [31, 31, 31, 31, 31, 31],
modifierConfigs: [
{
modifier: generateModifierType(scene, modifierTypes.TERA_SHARD, [Type.STEEL]) as PokemonHeldItemModifierType,
},
{
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.ATK]) as PokemonHeldItemModifierType,
stackCount: 1 + Math.floor(waveIndex / 20), // +1 Protein every 20 waves
},
{
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
stackCount: 1 + Math.floor(waveIndex / 40), // +1 Carbos every 40 waves
},
]
}
]
};
if (scene.arena.biomeType === Biome.SPACE) {
// All 3 members always Cleffa line, but different configs
baseConfig.pokemonConfigs!.push({
nickname: i18next.t(`${namespace}.cleffa_2_nickname`),
species: getPokemonSpecies(cleffaSpecies),
isBoss: false,
abilityIndex: 1, // Magic Guard
shiny: true,
variant: 1,
nature: Nature.MODEST,
moveSet: [Moves.MOONBLAST, Moves.MYSTICAL_FIRE, Moves.ICE_BEAM, Moves.THUNDERBOLT],
ivs: [31, 31, 31, 31, 31, 31]
},
{
nickname: i18next.t(`${namespace}.cleffa_3_nickname`, { speciesName: getPokemonSpecies(cleffaSpecies).getName() }),
species: getPokemonSpecies(cleffaSpecies),
isBoss: false,
abilityIndex: 2, // Friend Guard / Unaware
shiny: true,
variant: 2,
nature: Nature.BOLD,
moveSet: [Moves.TRI_ATTACK, Moves.STORED_POWER, Moves.TAKE_HEART, Moves.MOONLIGHT],
ivs: [31, 31, 31, 31, 31, 31]
});
} else {
// Second member from pool 1
const pool1Species = getSpeciesFromPool(POOL_1_POKEMON, waveIndex);
// Third member from pool 2
const pool2Species = getSpeciesFromPool(POOL_2_POKEMON, waveIndex);
baseConfig.pokemonConfigs!.push({
species: getPokemonSpecies(pool1Species),
isBoss: false,
ivs: [31, 31, 31, 31, 31, 31]
},
{
species: getPokemonSpecies(pool2Species),
isBoss: false,
ivs: [31, 31, 31, 31, 31, 31]
});
}
return baseConfig;
}
function getSpeciesFromPool(speciesPool: (Species | BreederSpeciesEvolution)[][], waveIndex: number): Species {
const poolCopy = speciesPool.slice(0);
randSeedShuffle(poolCopy);
const speciesEvolutions = poolCopy.pop()!.slice(0);
let speciesObject = speciesEvolutions.pop()!;
while (speciesObject instanceof BreederSpeciesEvolution && speciesObject.evolution > waveIndex) {
speciesObject = speciesEvolutions.pop()!;
}
return speciesObject instanceof BreederSpeciesEvolution ? speciesObject.species : speciesObject;
}
function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number] {
const bst = pokemon.calculateBaseStats().reduce((a, b) => a + b, 0);
// 1 point for every 20 points below 680 BST the pokemon is, (max 18, min 1)
const pointsFromBst = Math.min(Math.max(Math.floor((680 - bst) / 20), 1), 18);
const rootSpecies = pokemon.species.getRootSpeciesId(true);
let pointsFromStarterTier = 0;
// 2 points for every 1 below 7 that the pokemon's starter tier is (max 12, min 0)
if (speciesStarters.hasOwnProperty(rootSpecies)) {
const starterTier = speciesStarters[rootSpecies];
pointsFromStarterTier = Math.min(Math.max(Math.floor(7 - starterTier) * 2, 0), 12);
}
// Maximum of 30 points
const totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30);
// 1 Rare egg for every 6 points
const numRares = Math.floor(totalPoints / 6);
// 1 Common egg for every point leftover
const numCommons = totalPoints % 6;
return [numCommons, numRares];
}
function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) {
const eggDescription = i18next.t(`${namespace}.title`) + ":\n" + i18next.t(trainerNameKey);
const eggOptions: IEggOptions[] = [];
if (commonEggs > 0) {
for (let i = 0; i < commonEggs; i++) {
eggOptions.push({
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription,
tier: EggTier.COMMON
});
}
}
if (rareEggs > 0) {
for (let i = 0; i < rareEggs; i++) {
eggOptions.push({
scene,
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription,
tier: EggTier.GREAT
});
}
}
return eggOptions;
}
function removePokemonFromPartyAndStoreHeldItems(scene: BattleScene, encounter: MysteryEncounter, chosenPokemon: PlayerPokemon) {
const party = scene.getParty();
const chosenIndex = party.indexOf(chosenPokemon);
party[chosenIndex] = party[0];
party[0] = chosenPokemon;
encounter.misc.originalParty = scene.getParty().slice(1);
encounter.misc.originalPartyHeldItems = encounter.misc.originalParty
.map(p => p.getHeldItems());
scene["party"] = [
chosenPokemon
];
}
function checkAchievement(scene: BattleScene) {
if (scene.arena.biomeType === Biome.SPACE) {
scene.validateAchv(achvs.BREEDERS_IN_SPACE);
}
}
async function restorePartyAndHeldItems(scene: BattleScene) {
const encounter = scene.currentBattle.mysteryEncounter!;
// Restore original party
scene.getParty().push(...encounter.misc.originalParty);
// Restore held items
const originalHeldItems = encounter.misc.originalPartyHeldItems;
originalHeldItems.forEach(pokemonHeldItemsList => {
pokemonHeldItemsList.forEach(heldItem => {
scene.addModifier(heldItem, true, false, false, true);
});
});
await scene.updateModifiers(true);
}

View File

@ -2,8 +2,8 @@ import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, up
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoneyRequirement } from "../mystery-encounter-requirements";
import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
@ -15,15 +15,11 @@ import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { Abilities } from "#enums/abilities";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:pokemonSalesman";
const MAX_POKEMON_PRICE_MULTIPLIER = 4;
/** Odds of shiny magikarp will be 1/value */
const SHINY_MAGIKARP_WEIGHT = 100;
const MAX_POKEMON_PRICE_MULTIPLIER = 6;
/**
* Pokemon Salesman encounter.
@ -62,12 +58,12 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
const tries = 0;
// Reroll any species that don't have HAs
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
while (isNullOrUndefined(species.abilityHidden) && tries < 5) {
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
}
let pokemon: PlayerPokemon;
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
if (isNullOrUndefined(species.abilityHidden) || randSeedInt(100) === 0) {
// If no HA mon found or you roll 1%, give shiny Magikarp
species = getPokemonSpecies(Species.MAGIKARP);
const hiddenIndex = species.ability2 ? 2 : 1;

View File

@ -2,7 +2,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounte
import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Species } from "#enums/species";
import { Nature } from "#app/data/nature";
@ -36,7 +36,6 @@ export const TheStrongStuffEncounter: MysteryEncounter =
.withEncounterTier(MysteryEncounterTier.GREAT)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
.withMaxAllowedEncounters(1)
.withHideWildIntroMessage(true)
.withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([
@ -71,7 +70,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
// Calculate boss mon
const config: EnemyPartyConfig = {
levelAdditiveModifier: 1,
levelAdditiveMultiplier: 1,
disableSwitch: true,
pokemonConfigs: [
{
@ -160,11 +159,6 @@ export const TheStrongStuffEncounter: MysteryEncounter =
encounter.setDialogueToken("increaseValue", BST_INCREASE_VALUE.toString());
await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, undefined, true);
encounter.dialogue.outro = [
{
text: `${namespace}.outro`,
}
];
setEncounterRewards(scene, { fillRemaining: true });
leaveEncounterWithoutBattle(scene, true);
return true;
@ -198,7 +192,6 @@ export const TheStrongStuffEncounter: MysteryEncounter =
ignorePp: true
});
encounter.dialogue.outro = [];
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
}

View File

@ -2,7 +2,7 @@ import { EnemyPartyConfig, generateModifierType, generateModifierTypeOption, ini
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
@ -146,7 +146,7 @@ async function spawnNextTrainerOrEndEncounter(scene: BattleScene) {
// Give 10x Voucher
const newModifier = modifierTypes.VOUCHER_PREMIUM().newModifier();
await scene.addModifier(newModifier);
scene.addModifier(newModifier);
scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: newModifier?.type.name }));

View File

@ -3,7 +3,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattl
import { getNatureName, Nature } from "#app/data/nature";
import { speciesStarters } from "#app/data/pokemon-species";
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";
@ -11,8 +11,8 @@ import { isNullOrUndefined, randSeedShuffle } from "#app/utils";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -34,7 +34,6 @@ export const TrainingSessionEncounter: MysteryEncounter =
.withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 unfainted pokemon in party
.withFleeAllowed(false)
.withHideWildIntroMessage(true)
.withIntroSpriteConfigs([
{
@ -98,7 +97,12 @@ export const TrainingSessionEncounter: MysteryEncounter =
5
);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
const config = getEnemyConfig(
scene,
playerPokemon,
segments,
modifiers
);
scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
@ -159,7 +163,6 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Add pokemon and mods 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);
@ -227,9 +230,17 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Spawn medium training session with chosen pokemon
// Every 40 waves, add +1 boss segment, capping at 6
const segments = Math.min(2 + Math.floor(scene.currentBattle.waveIndex / 40), 6);
const segments = Math.min(
2 + Math.floor(scene.currentBattle.waveIndex / 40),
6
);
const modifiers = new ModifiersHolder();
const config = getEnemyConfig(scene, playerPokemon, segments, modifiers);
const config = getEnemyConfig(
scene,
playerPokemon,
segments,
modifiers
);
scene.removePokemonFromPlayerParty(playerPokemon, false);
const onBeforeRewardsPhase = () => {
@ -366,7 +377,6 @@ export const TrainingSessionEncounter: MysteryEncounter =
// Add pokemon and mods 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);
@ -400,12 +410,10 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon, segmen
playerPokemon.resetSummonData();
// Passes modifiers by reference
modifiers.value = playerPokemon.getHeldItems();
modifiers.value = playerPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
const modifierConfigs = modifiers.value.map((mod) => {
return {
modifier: mod.clone(),
isTransferable: false,
stackCount: mod.stackCount
modifier: mod
};
}) as HeldModifierConfig[];

View File

@ -2,8 +2,8 @@ import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleW
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Species } from "#enums/species";
@ -67,7 +67,7 @@ export const TrashToTreasureEncounter: MysteryEncounter =
moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH]
};
const config: EnemyPartyConfig = {
levelAdditiveModifier: 1,
levelAdditiveMultiplier: 1,
pokemonConfigs: [pokemonConfig],
disableSwitch: true
};

View File

@ -5,8 +5,8 @@ import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { getPartyLuckValue } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MoveRequirement, PersistentModifierRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MoveRequirement, PersistentModifierRequirement } from "../mystery-encounter-requirements";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { TrainerSlot } from "#app/data/trainer-config";

View File

@ -2,8 +2,8 @@ import { Type } from "#app/data/type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import BattleScene from "#app/battle-scene";
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option";
import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
@ -14,7 +14,7 @@ import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, Pokemo
import { achvs } from "#app/system/achv";
import { speciesEggMoves } from "#app/data/egg-moves";
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifierTypes } from "#app/modifier/modifier-type";
import i18next from "#app/plugins/i18n";
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
@ -130,7 +130,12 @@ export const WeirdDreamEncounter: MysteryEncounter =
return true;
})
.withOnVisualsStart((scene: BattleScene) => {
scene.fadeAndSwitchBgm("mystery_encounter_weird_dream");
// Change the bgm
scene.fadeOutBgm(3000, false);
scene.time.delayedCall(3000, () => {
scene.playBgm("mystery_encounter_weird_dream");
});
return true;
})
.withOption(
@ -335,7 +340,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false);
if (newStarterUnlocked) {
atLeastOneNewStarter = true;
await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
queueEncounterMessage(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() }));
}
}

View File

@ -3,7 +3,7 @@ import { Moves } from "#app/enums/moves";
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
import BattleScene from "#app/battle-scene";
import { Type } from "../type";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { EncounterPokemonRequirement, EncounterSceneRequirement, MoneyRequirement, TypeRequirement } from "./mystery-encounter-requirements";
import { CanLearnMoveRequirement, CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";

View File

@ -25,9 +25,6 @@ export interface EncounterStartOfBattleEffect {
followUp?: boolean;
}
const DEFAULT_MAX_ALLOWED_ENCOUNTERS = 2;
const DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS = 1;
/**
* Used by {@linkcode MysteryEncounterBuilder} class to define required/optional properties on the {@linkcode MysteryEncounter} class when building.
*
@ -45,7 +42,6 @@ export interface IMysteryEncounter {
autoHideIntroVisuals: boolean;
enterIntroVisualsFromRight: boolean;
catchAllowed: boolean;
fleeAllowed: boolean;
continuousEncounter: boolean;
maxAllowedEncounters: number;
hasBattleAnimationsWithoutTargets: boolean;
@ -114,11 +110,6 @@ export default class MysteryEncounter implements IMysteryEncounter {
* Default false
*/
catchAllowed: boolean;
/**
* If true, allows fleeing from a wild encounter (trainer battle MEs auto-disable fleeing)
* Default true
*/
fleeAllowed: boolean;
/**
* If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave
* MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE
@ -255,8 +246,8 @@ export default class MysteryEncounter implements IMysteryEncounter {
this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON;
this.dialogue = this.dialogue ?? {};
this.spriteConfigs = this.spriteConfigs ? [...this.spriteConfigs] : [];
// Default max is 1 for ROGUE encounters, 2 for others
this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? DEFAULT_MAX_ALLOWED_ROGUE_ENCOUNTERS : DEFAULT_MAX_ALLOWED_ENCOUNTERS;
// Default max is 1 for ROGUE encounters, 3 for others
this.maxAllowedEncounters = this.maxAllowedEncounters ?? this.encounterTier === MysteryEncounterTier.ROGUE ? 1 : 3;
this.encounterMode = MysteryEncounterMode.DEFAULT;
this.requirements = this.requirements ? this.requirements : [];
this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false;
@ -529,7 +520,6 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
enterIntroVisualsFromRight: boolean = false;
continuousEncounter: boolean = false;
catchAllowed: boolean = false;
fleeAllowed: boolean = true;
lockEncounterRewardTiers: boolean = false;
startOfBattleEffectsComplete: boolean = false;
hasBattleAnimationsWithoutTargets: boolean = false;
@ -590,8 +580,8 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* There should be at least 2 options defined and no more than 4.
* If complex use {@linkcode MysteryEncounterBuilder.withOption}
*
* @param dialogue {@linkcode OptionTextDisplay}
* @param callback {@linkcode OptionPhaseCallback}
* @param dialogue - {@linkcode OptionTextDisplay}
* @param callback - {@linkcode OptionPhaseCallback}
* @returns
*/
withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick<IMysteryEncounter, "options"> {
@ -742,7 +732,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
*
* @param min min wave (or exact size if only min is given)
* @param max optional max size. If not given, defaults to min => exact wave
* @param excludeFainted if true, only counts unfainted mons
* @param excludeFainted - if true, only counts unfainted mons
* @returns
*/
withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
@ -808,7 +798,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
* It may be better to programmatically set doEncounterRewards elsewhere.
* There is a helper function in mystery-encounter utils, setEncounterRewards(), which can be called programmatically to set rewards
* @param doEncounterRewards Synchronous callback function to perform during rewards phase of the encounter
* @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterRewards">> {
@ -822,7 +812,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* NOTE: If rewards are dependent on options selected, runtime data, etc.,
* It may be better to programmatically set doEncounterExp elsewhere.
* There is a helper function in mystery-encounter utils, setEncounterExp(), which can be called programmatically to set rewards
* @param doEncounterExp Synchronous callback function to perform during rewards phase of the encounter
* @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter
* @returns
*/
withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "doEncounterExp">> {
@ -833,7 +823,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
* Can be used to perform init logic before intro visuals are shown and before the MysteryEncounterPhase begins
* Useful for performing things like procedural generation of intro sprites, etc.
*
* @param onInit Synchronous callback function to perform as soon as the encounter is selected for the next phase
* @param onInit - synchronous callback function to perform as soon as the encounter is selected for the next phase
* @returns
*/
withOnInit(onInit: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onInit">> {
@ -843,7 +833,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Can be used to perform some extra logic (usually animations) when the enemy field is finished sliding in
*
* @param onVisualsStart Synchronous callback function to perform as soon as the enemy field finishes sliding in
* @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in
* @returns
*/
withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required<Pick<IMysteryEncounter, "onVisualsStart">> {
@ -853,7 +843,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Can set whether catching is allowed or not on the encounter
* This flag can also be programmatically set inside option event functions or elsewhere
* @param catchAllowed If `true`, allows enemy pokemon to be caught during the encounter
* @param catchAllowed - if true, allows enemy pokemon to be caught during the encounter
* @returns
*/
withCatchAllowed(catchAllowed: boolean): this & Required<Pick<IMysteryEncounter, "catchAllowed">> {
@ -861,16 +851,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
}
/**
* Can set whether fleeing is allowed or not on the encounter
* @param fleeAllowed If `false`, prevents fleeing from a wild battle (trainer battle MEs already have flee disabled)
* @returns
*/
withFleeAllowed(fleeAllowed: boolean): this & Required<Pick<IMysteryEncounter, "fleeAllowed">> {
return Object.assign(this, { fleeAllowed });
}
/**
* @param hideBattleIntroMessage If `true`, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
* @param hideBattleIntroMessage - if true, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter
* @returns
*/
withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required<Pick<IMysteryEncounter, "hideBattleIntroMessage">> {
@ -878,7 +859,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
}
/**
* @param autoHideIntroVisuals If `false`, will not hide the intro visuals that are displayed at the beginning of encounter
* @param autoHideIntroVisuals - if false, will not hide the intro visuals that are displayed at the beginning of encounter
* @returns
*/
withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required<Pick<IMysteryEncounter, "autoHideIntroVisuals">> {
@ -886,7 +867,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
}
/**
* @param enterIntroVisualsFromRight If `true`, will slide in intro visuals from the right side of the screen. If false, slides in from left, as normal
* @param enterIntroVisualsFromRight - If true, will slide in intro visuals from the right side of the screen. If false, slides in from left, as normal
* Default false
* @returns
*/
@ -897,7 +878,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Add a title for the encounter
*
* @param title Title of the encounter
* @param title - title of the encounter
* @returns
*/
withTitle(title: string): this {
@ -917,7 +898,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Add a description of the encounter
*
* @param description Description of the encounter
* @param description - description of the encounter
* @returns
*/
withDescription(description: string): this {
@ -937,7 +918,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Add a query for the encounter
*
* @param query Query to use for the encounter
* @param query - query to use for the encounter
* @returns
*/
withQuery(query: string): this {
@ -957,7 +938,7 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
/**
* Add outro dialogue/s for the encounter
*
* @param dialogue Outro dialogue(s)
* @param dialogue - outro dialogue/s
* @returns
*/
withOutroDialogue(dialogue: MysteryEncounterDialogue["outro"] = []): this {

View File

@ -31,7 +31,6 @@ import { BugTypeSuperfanEncounter } from "#app/data/mystery-encounters/encounter
import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fun-and-games-encounter";
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
/**
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
@ -185,8 +184,7 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [
MysteryEncounterType.SHADY_VITAMIN_DEALER,
MysteryEncounterType.THE_POKEMON_SALESMAN,
MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE,
MysteryEncounterType.THE_WINSTRATE_CHALLENGE,
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER
MysteryEncounterType.THE_WINSTRATE_CHALLENGE
];
const civilizationBiomeEncounters: MysteryEncounterType[] = [
@ -240,6 +238,7 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
MysteryEncounterType.SAFARI_ZONE,
MysteryEncounterType.ABSOLUTE_AVARICE
]],
[Biome.SEA, [
MysteryEncounterType.LOST_AT_SEA
]],
@ -276,9 +275,7 @@ export const mysteryEncountersByBiome = new Map<Biome, MysteryEncounterType[]>([
[Biome.ABYSS, [
MysteryEncounterType.DANCING_LESSONS
]],
[Biome.SPACE, [
MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER
]],
[Biome.SPACE, []],
[Biome.CONSTRUCTION_SITE, []],
[Biome.JUNGLE, [
MysteryEncounterType.SAFARI_ZONE
@ -322,7 +319,6 @@ export function initMysteryEncounters() {
allMysteryEncounters[MysteryEncounterType.FUN_AND_GAMES] = FunAndGamesEncounter;
allMysteryEncounters[MysteryEncounterType.UNCOMMON_BREED] = UncommonBreedEncounter;
allMysteryEncounters[MysteryEncounterType.GLOBAL_TRADE_SYSTEM] = GlobalTradeSystemEncounter;
allMysteryEncounters[MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER] = TheExpertPokemonBreederEncounter;
// Add extreme encounters to biome map
extremeBiomeEncounters.forEach(encounter => {

View File

@ -2,7 +2,7 @@ import BattleScene from "#app/battle-scene";
import { Moves } from "#app/enums/moves";
import { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils";
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { EncounterPokemonRequirement } from "../mystery-encounter-requirements";
/**
* {@linkcode CanLearnMoveRequirement} options

View File

@ -3,7 +3,7 @@ import { biomeLinks, BiomePoolTier } from "#app/data/biomes";
import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { AVERAGE_ENCOUNTERS_PER_RUN_TARGET, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import Pokemon, { AiType, FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon";
import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon";
import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases";
import PokemonData from "#app/system/pokemon-data";
@ -36,7 +36,6 @@ import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase";
import { Variant } from "#app/data/variant";
/**
* Animates exclamation sprite over trainer's head at start of encounter
@ -68,7 +67,6 @@ export function doTrainerExclamation(scene: BattleScene) {
export interface EnemyPokemonConfig {
species: PokemonSpecies;
isBoss: boolean;
nickname?: string;
bossSegments?: number;
bossSegmentModifier?: number; // Additive to the determined segment number
mysteryEncounterPokemonData?: MysteryEncounterPokemonData;
@ -81,32 +79,27 @@ export interface EnemyPokemonConfig {
nature?: Nature;
ivs?: [number, number, number, number, number, number];
shiny?: boolean;
/** Is only checked if Pokemon is shiny */
variant?: Variant;
/** Can set just the status, or pass a timer on the status turns */
status?: StatusEffect | [StatusEffect, number];
mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void;
modifierConfigs?: HeldModifierConfig[];
tags?: BattlerTagType[];
dataSource?: PokemonData;
aiType?: AiType;
}
export interface EnemyPartyConfig {
/** Formula for enemy level: level += waveIndex / 10 * levelAdditiveModifier */
levelAdditiveModifier?: number;
/** Formula for enemy: level += waveIndex / 10 * levelAdditive */
levelAdditiveMultiplier?: number;
doubleBattle?: boolean;
/** Generates trainer battle solely off trainer type */
trainerType?: TrainerType;
/** More customizable option for configuring trainer battle */
trainerConfig?: TrainerConfig;
pokemonConfigs?: EnemyPokemonConfig[];
/** `true` for female trainer, false for male */
/** True for female trainer, false for male */
female?: boolean;
/** `true` will prevent player from switching */
/** True will prevent player from switching */
disableSwitch?: boolean;
/** `true` or leaving undefined will increment dex seen count for the encounter battle, `false` will not */
countAsSeen?: boolean;
}
/**
@ -163,10 +156,10 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// ME levels are modified by an additive value that scales with wave index
// Base scaling: Every 10 waves, modifier gets +1 level
// This can be amplified or counteracted by setting levelAdditiveModifier in config
// levelAdditiveModifier value of 0.5 will halve the modifier scaling, 2 will double it, etc.
// This can be amplified or counteracted by setting levelAdditiveMultiplier in config
// levelAdditiveMultiplier value of 0.5 will halve the modifier scaling, 2 will double it, etc.
// Leaving null/undefined will disable level scaling
const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier! : 0;
const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveMultiplier) ? partyConfig.levelAdditiveMultiplier! : 0;
const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0);
battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
@ -217,18 +210,13 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemyPokemon.resetSummonData();
}
if (!loaded && isNullOrUndefined(partyConfig.countAsSeen) || partyConfig.countAsSeen) {
if (!loaded) {
scene.gameData.setPokemonSeen(enemyPokemon, true, !!(trainerType || trainerConfig));
}
if (partyConfig?.pokemonConfigs && e < partyConfig.pokemonConfigs.length) {
const config = partyConfig.pokemonConfigs[e];
// Set form
if (!isNullOrUndefined(config.nickname)) {
enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname!)));
}
// Generate new id, reset status and HP in case using data source
if (config.dataSource) {
enemyPokemon.id = Utils.randSeedInt(4294967296);
@ -244,11 +232,6 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemyPokemon.shiny = config.shiny!;
}
// Set Variant
if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) {
enemyPokemon.variant = config.variant!;
}
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) {
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData!;
@ -303,11 +286,6 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
enemyPokemon.summonData.gender = config.gender!;
}
// Set AI type
if (!isNullOrUndefined(config.aiType)) {
enemyPokemon.aiType = config.aiType!;
}
// Set moves
if (config?.moveSet && config.moveSet.length > 0) {
const moves = config.moveSet.map(m => new PokemonMove(m));
@ -329,9 +307,6 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// Requires re-priming summon data to update everything properly
enemyPokemon.primeSummonData(enemyPokemon.summonData);
if (enemyPokemon.isShiny() && !enemyPokemon["shinySparkle"]) {
enemyPokemon.initShinySparkle();
}
enemyPokemon.initBattleInfo();
enemyPokemon.getBattleInfo().initInfo(enemyPokemon);
enemyPokemon.generateName();
@ -727,19 +702,19 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
if (encounter.continuousEncounter || doNotContinue) {
return;
} else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) {
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
scene.pushPhase(new EggLapsePhase(scene));
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
} else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) {
scene.pushPhase(new BattleEndPhase(scene));
if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) {
scene.pushPhase(new TrainerVictoryPhase(scene));
}
if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) {
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
if (!encounter.doContinueEncounter) {
// Only lapse eggs once for multi-battle encounters
scene.pushPhase(new EggLapsePhase(scene));
}
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
}
}
}

View File

@ -19,10 +19,6 @@ import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifi
import { Gender } from "#app/data/gender";
import { PermanentStat } from "#enums/stat";
import { VictoryPhase } from "#app/phases/victory-phase";
import { SummaryUiMode } from "#app/ui/summary-ui-handler";
/** Will give +1 level every 10 waves */
export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1;
/**
* Gets the sprite key and file root for a given PokemonSpecies (accounts for gender, shiny, variants, forms, and experimental)
@ -293,12 +289,10 @@ export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: numb
*/
export async function applyModifierTypeToPlayerPokemon(scene: BattleScene, pokemon: PlayerPokemon, modType: PokemonHeldItemModifierType, fallbackModifierType?: PokemonHeldItemModifierType) {
// Check if the Pokemon has max stacks of that item already
const modifier = modType.newModifier(pokemon);
const existing = scene.findModifier(m => (
m instanceof PokemonHeldItemModifier &&
m.type.id === modType.id &&
m.pokemonId === pokemon.id &&
m.matchType(modifier)
m.pokemonId === pokemon.id
)) as PokemonHeldItemModifier;
// At max stacks
@ -311,6 +305,7 @@ export async function applyModifierTypeToPlayerPokemon(scene: BattleScene, pokem
return applyModifierTypeToPlayerPokemon(scene, pokemon, fallbackModifierType);
}
const modifier = modType.newModifier(pokemon);
await scene.addModifier(modifier, false, false, false, true);
}
@ -332,7 +327,7 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon,
const _3m = 3 * pokemon.getMaxHp();
const _2h = 2 * pokemon.hp;
const catchRate = pokemon.species.catchRate;
const pokeballMultiplier = getPokeballCatchMultiplier(pokeballType);
const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType);
const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1;
const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier);
ballTwitchRate = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x)));
@ -506,6 +501,8 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number,
* @param isObtain
*/
export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, pokeball: Phaser.GameObjects.Sprite | null, pokeballType: PokeballType, showCatchObtainMessage: boolean = true, isObtain: boolean = false): Promise<void> {
scene.unshiftPhase(new VictoryPhase(scene, pokemon.id, true));
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
@ -531,11 +528,6 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
return new Promise(resolve => {
const doPokemonCatchMenu = () => {
const end = () => {
// Ensure the pokemon is in the enemy party in all situations
if (!scene.getEnemyParty().some(p => p.id === pokemon.id)) {
scene.getEnemyParty().push(pokemon);
}
scene.unshiftPhase(new VictoryPhase(scene, pokemon.id, true));
scene.pokemonInfoContainer.hide();
if (pokeball) {
removePb(scene, pokeball);
@ -547,8 +539,8 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
scene.field.remove(pokemon, true);
}
};
const addToParty = (slotIndex?: number) => {
const newPokemon = pokemon.addToParty(pokeballType, slotIndex);
const addToParty = () => {
const newPokemon = pokemon.addToParty(pokeballType);
const modifiers = scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
if (scene.getParty().filter(p => p.isShiny()).length === 6) {
scene.validateAchv(achvs.SHINY_PARTY);
@ -567,19 +559,12 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
if (scene.getParty().length === 6) {
const promptRelease = () => {
scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => {
scene.pokemonInfoContainer.makeRoomForConfirmUi(1, true);
scene.pokemonInfoContainer.makeRoomForConfirmUi();
scene.ui.setMode(Mode.CONFIRM, () => {
const newPokemon = scene.addPlayerPokemon(pokemon.species, pokemon.level, pokemon.abilityIndex, pokemon.formIndex, pokemon.gender, pokemon.shiny, pokemon.variant, pokemon.ivs, pokemon.nature, pokemon);
scene.ui.setMode(Mode.SUMMARY, newPokemon, 0, SummaryUiMode.DEFAULT, () => {
scene.ui.setMode(Mode.MESSAGE).then(() => {
promptRelease();
});
}, false);
}, () => {
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => {
scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: number, _option: PartyOption) => {
scene.ui.setMode(Mode.MESSAGE).then(() => {
if (slotIndex < 6) {
addToParty(slotIndex);
addToParty();
} else {
promptRelease();
}
@ -590,7 +575,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po
removePokemon();
end();
});
}, "fullParty");
});
});
};
promptRelease();
@ -726,50 +711,13 @@ export function getGoldenBugNetSpecies(): PokemonSpecies {
const roll = randSeedInt(totalWeight);
let w = 0;
for (const speciesWeightPair of GOLDEN_BUG_NET_SPECIES_POOL) {
w += speciesWeightPair[1];
for (const species of GOLDEN_BUG_NET_SPECIES_POOL) {
w += species[1];
if (roll < w) {
return getPokemonSpecies(speciesWeightPair[0]);
return getPokemonSpecies(species);
}
}
// Defaults to Scyther
return getPokemonSpecies(Species.SCYTHER);
}
/**
* Generates a Pokemon level for a given wave, with an option to increase/decrease by a scaling modifier
* @param scene
* @param levelAdditiveModifier Default 0. will add +(1 level / 10 waves * levelAdditiveModifier) to the level calculation
*/
export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) {
const currentBattle = scene.currentBattle;
// Default to use the first generated level from enemyLevels, or generate a new one if it DNE
const baseLevel = currentBattle.enemyLevels && currentBattle.enemyLevels?.length > 0 ? currentBattle.enemyLevels[0] : currentBattle.getLevelForWave();
// Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier
return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0);
}
export async function addPokemonDataToDexAndValidateAchievements(scene: BattleScene, pokemon: PlayerPokemon) {
const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm();
if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) {
scene.validateAchv(achvs.HIDDEN_ABILITY);
}
if (pokemon.species.subLegendary) {
scene.validateAchv(achvs.CATCH_SUB_LEGENDARY);
}
if (pokemon.species.legendary) {
scene.validateAchv(achvs.CATCH_LEGENDARY);
}
if (pokemon.species.mythical) {
scene.validateAchv(achvs.CATCH_MYTHICAL);
}
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
return scene.gameData.setPokemonCaught(pokemon, true, false, false);
}

View File

@ -103,7 +103,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
scene.time.delayedCall(1000, () => {
pokemonEvoTintSprite.setScale(0.25);
pokemonEvoTintSprite.setVisible(true);
doCycle(scene, 1.5, 6, pokemonTintSprite, pokemonEvoTintSprite).then(() => {
doCycle(scene, 2, 6, pokemonTintSprite, pokemonEvoTintSprite).then(() => {
pokemonEvoSprite.setVisible(true);
doCircleInward(scene, transformationBaseBg, transformationContainer, xOffset, yOffset);
@ -115,7 +115,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
delay: 150,
easing: "Sine.easeIn",
onComplete: () => {
scene.time.delayedCall(3000, () => {
scene.time.delayedCall(2500, () => {
resolve();
scene.tweens.add({
targets: pokemonEvoSprite,

View File

@ -1105,16 +1105,8 @@ function getGymLeaderPartyTemplate(scene: BattleScene) {
return getWavePartyTemplate(scene, trainerPartyTemplates.GYM_LEADER_1, trainerPartyTemplates.GYM_LEADER_2, trainerPartyTemplates.GYM_LEADER_3, trainerPartyTemplates.GYM_LEADER_4, trainerPartyTemplates.GYM_LEADER_5);
}
/**
* Randomly selects one of the `Species` from `speciesPool`, determines its evolution, level, and strength.
* Then adds Pokemon to scene.
* @param speciesPool
* @param trainerSlot
* @param ignoreEvolution
* @param postProcess
*/
export function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void) {
return (scene: BattleScene, level: number, strength: PartyMemberStrength) => {
function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: TrainerSlot = TrainerSlot.TRAINER, ignoreEvolution: boolean = false, postProcess?: (enemyPokemon: EnemyPokemon) => void): PartyMemberFunc {
return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => {
let species = Utils.randSeedItem(speciesPool);
if (!ignoreEvolution) {
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex);
@ -2310,8 +2302,6 @@ export const trainerConfigs: TrainerConfigs = {
.setMoneyMultiplier(2)
.setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(2, PartyMemberStrength.STRONG))),
[TrainerType.BUG_TYPE_SUPERFAN]: new TrainerConfig(++t).setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER)
.setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE)),
[TrainerType.EXPERT_POKEMON_BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(3).setEncounterBgm(TrainerType.ACE_TRAINER)
.setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.STRONG))
.setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE))
};

View File

@ -28,6 +28,5 @@ export enum MysteryEncounterType {
BUG_TYPE_SUPERFAN,
FUN_AND_GAMES,
UNCOMMON_BREED,
GLOBAL_TRADE_SYSTEM,
THE_EXPERT_POKEMON_BREEDER
GLOBAL_TRADE_SYSTEM
}

View File

@ -107,7 +107,6 @@ export enum TrainerType {
VICKY,
VITO,
BUG_TYPE_SUPERFAN,
EXPERT_POKEMON_BREEDER,
BROCK = 200,
MISTY,

View File

@ -0,0 +1,5 @@
export enum VariantTier {
COMMON,
RARE,
EPIC
}

View File

@ -1,19 +1,19 @@
import BattleScene from "../battle-scene";
import { biomePokemonPools, BiomePoolTier, BiomeTierTrainerPools, biomeTrainerPools, PokemonPools } from "../data/biomes";
import { BiomePoolTier, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes";
import { Constructor } from "#app/utils";
import * as Utils from "../utils";
import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species";
import { getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage, Weather, WeatherType } from "../data/weather";
import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather";
import { CommonAnim } from "../data/battle-anims";
import { Type } from "../data/type";
import Move from "../data/move";
import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "../data/arena-tag";
import { BattlerIndex } from "../battle";
import { Terrain, TerrainType } from "../data/terrain";
import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "../data/ability";
import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability";
import Pokemon from "./pokemon";
import Overrides from "#app/overrides";
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "../events/arena";
import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "../events/arena";
import { ArenaTagType } from "#enums/arena-tag-type";
import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves";
@ -762,7 +762,7 @@ export class Arena {
case Biome.BEACH:
return 3.462;
case Biome.LAKE:
return 7.215;
return 5.350;
case Biome.SEABED:
return 2.600;
case Biome.MOUNTAIN:
@ -788,7 +788,7 @@ export class Arena {
case Biome.FACTORY:
return 4.985;
case Biome.RUINS:
return 0.000;
return 2.270;
case Biome.WASTELAND:
return 6.336;
case Biome.ABYSS:

View File

@ -1273,13 +1273,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param attrType {@linkcode AbAttr} The ability attribute to check for.
* @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active
* @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects
* @returns A list of all the ability attributes on this ability.
* @returns {AbAttr[]} A list of all the ability attributes on this ability.
*/
getAbilityAttrs<T extends AbAttr = AbAttr>(attrType: { new(...args: any[]): T }, canApply: boolean = true, ignoreOverride?: boolean): T[] {
const abilityAttrs: T[] = [];
getAbilityAttrs(attrType: { new(...args: any[]): AbAttr }, canApply: boolean = true, ignoreOverride?: boolean): AbAttr[] {
const abilityAttrs: AbAttr[] = [];
if (!canApply || this.canApplyAbility()) {
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs<T>(attrType));
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType));
}
if (!canApply || this.canApplyAbility(true)) {
@ -2322,61 +2322,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return accuracyMultiplier.value / evasionMultiplier.value;
}
/**
* Calculates the base damage of the given move against this Pokemon when attacked by the given source.
* Used during damage calculation and for Shell Side Arm's forecasting effect.
* @param source the attacking {@linkcode Pokemon}.
* @param move the {@linkcode Move} used in the attack.
* @param moveCategory the move's {@linkcode MoveCategory} after variable-category effects are applied.
* @param ignoreAbility if `true`, ignores this Pokemon's defensive ability effects (defaults to `false`).
* @param ignoreSourceAbility if `true`, ignore's the attacking Pokemon's ability effects (defaults to `false`).
* @param isCritical if `true`, calculates effective stats as if the hit were critical (defaults to `false`).
* @param simulated if `true`, suppresses changes to game state during calculation (defaults to `true`).
* @returns The move's base damage against this Pokemon when used by the source Pokemon.
*/
getBaseDamage(source: Pokemon, move: Move, moveCategory: MoveCategory, ignoreAbility: boolean = false, ignoreSourceAbility: boolean = false, isCritical: boolean = false, simulated: boolean = true): number {
const isPhysical = moveCategory === MoveCategory.PHYSICAL;
/** A base damage multiplier based on the source's level */
const levelMultiplier = (2 * source.level / 5 + 2);
/** The power of the move after power boosts from abilities, etc. have applied */
const power = move.calculateBattlePower(source, this, simulated);
/**
* The attacker's offensive stat for the given move's category.
* Critical hits cause negative stat stages to be ignored.
*/
const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, isCritical, simulated));
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk);
/**
* This Pokemon's defensive stat for the given move's category.
* Critical hits cause positive stat stages to be ignored.
*/
const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, isCritical, simulated));
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
/**
* The attack's base damage, as determined by the source's level, move power
* and Attack stat as well as this Pokemon's Defense stat
*/
const baseDamage = ((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2;
/** Debug message for non-simulated calls (i.e. when damage is actually dealt) */
if (!simulated) {
console.log("base damage", baseDamage, move.name, power, sourceAtk.value, targetDef.value);
}
return baseDamage;
}
/**
* Calculates the damage of an attack made by another Pokemon against this Pokemon
* @param source {@linkcode Pokemon} the attacking Pokemon
* @param move {@linkcode Pokemon} the move used in the attack
* @param ignoreAbility If `true`, ignores this Pokemon's defensive ability effects
* @param ignoreSourceAbility If `true`, ignores the attacking Pokemon's ability effects
* @param isCritical If `true`, calculates damage for a critical hit.
* @param simulated If `true`, suppresses changes to game state during the calculation.
* @returns a {@linkcode DamageCalculationResult} object with three fields:
@ -2445,11 +2395,35 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
};
}
// ----- BEGIN BASE DAMAGE MULTIPLIERS -----
/** A base damage multiplier based on the source's level */
const levelMultiplier = (2 * source.level / 5 + 2);
/** The power of the move after power boosts from abilities, etc. have applied */
const power = move.calculateBattlePower(source, this, simulated);
/**
* The attacker's offensive stat for the given move's category.
* Critical hits ignore negative stat stages.
*/
const sourceAtk = new Utils.NumberHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, ignoreSourceAbility, ignoreAbility, isCritical, simulated));
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk);
/**
* This Pokemon's defensive stat for the given move's category.
* Critical hits ignore positive stat stages.
*/
const targetDef = new Utils.NumberHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, ignoreAbility, ignoreSourceAbility, isCritical, simulated));
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
/**
* The attack's base damage, as determined by the source's level, move power
* and Attack stat as well as this Pokemon's Defense stat
*/
const baseDamage = this.getBaseDamage(source, move, moveCategory, ignoreAbility, ignoreSourceAbility, isCritical, simulated);
const baseDamage = ((levelMultiplier * power * sourceAtk.value / targetDef.value) / 50) + 2;
// ------ END BASE DAMAGE MULTIPLIERS ------
/** 25% damage debuff on moves hitting more than one non-fainted target (regardless of immunities) */
const { targets, multiple } = getMoveTargets(source, move.id);
@ -2575,7 +2549,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// debug message for when damage is applied (i.e. not simulated)
if (!simulated) {
console.log("damage", damage.value, move.name);
console.log("damage", damage.value, move.name, power, sourceAtk, targetDef);
}
let hitResult: HitResult;

View File

@ -3121,11 +3121,11 @@
},
"behemothBlade": {
"name": "Gigantenhieb",
"effect": "Der Anwender wird zu einem riesigen Schwert und greift das Ziel an."
"effect": "Der Anwender wird zu einem riesigen Schwert und greift das Ziel an. Dynamaximierte Ziele erleiden doppelten Schaden."
},
"behemothBash": {
"name": "Gigantenstoß",
"effect": "Der Anwender wird zu einem riesigen Schild und greift das Ziel an."
"effect": "Der Anwender wird zu einem riesigen Schild und greift das Ziel an. Dynamaximierte Ziele erleiden doppelten Schaden."
},
"auraWheel": {
"name": "Aura-Rad",

View File

@ -283,9 +283,5 @@
"INVERSE_BATTLE": {
"name": "Mirror rorriM",
"description": "Complete the Inverse Battle challenge.\n.egnellahc elttaB esrevnI eht etelpmoC"
},
"BREEDERS_IN_SPACE": {
"name": "Breeders in Space!",
"description": "Beat the Expert Pokémon Breeder in the Space Biome."
}
}

View File

@ -112,13 +112,13 @@
"island": "PMD EoS Craggy Coast",
"jungle": "Lmz - Jungle",
"laboratory": "Firel - Laboratory",
"lake": "Lmz - Lake",
"lake": "PMD EoS Crystal Cave",
"meadow": "PMD EoS Sky Peak Forest",
"metropolis": "Firel - Metropolis",
"mountain": "PMD EoS Mt. Horn",
"plains": "Firel - Route 888",
"power_plant": "Firel - The Klink",
"ruins": "Lmz - Ancient Ruins",
"ruins": "PMD EoS Deep Sealed Ruin",
"sea": "Andr06 - Marine Mystique",
"seabed": "Firel - Seabed",
"slum": "Andr06 - Sneaky Snom",
@ -151,6 +151,5 @@
"mystery_encounter_weird_dream": "PMD EoS Temporal Spire",
"mystery_encounter_fun_and_games": "PMD EoS Guildmaster Wigglytuff",
"mystery_encounter_gen_5_gts": "BW GTS",
"mystery_encounter_gen_6_gts": "XY GTS",
"mystery_encounter_delibirdy": "Firel - DeliDelivery!"
"mystery_encounter_gen_6_gts": "XY GTS"
}

View File

@ -84,7 +84,6 @@ import bugTypeSuperfan from "#app/locales/en/mystery-encounters/bug-type-superfa
import funAndGames from "#app/locales/en/mystery-encounters/fun-and-games-dialogue.json";
import uncommonBreed from "#app/locales/en/mystery-encounters/uncommon-breed-dialogue.json";
import globalTradeSystem from "#app/locales/en/mystery-encounters/global-trade-system-dialogue.json";
import expertPokemonBreeder from "#app/locales/en/mystery-encounters/the-expert-pokemon-breeder-dialogue.json";
/**
* Dialogue/Text token injection patterns that can be used:
@ -184,8 +183,7 @@ export const enConfig = {
bugTypeSuperfan,
funAndGames,
uncommonBreed,
globalTradeSystem,
expertPokemonBreeder
globalTradeSystem
},
mysteryEncounterMessages
};

View File

@ -1,7 +1,7 @@
{
"intro": "You're stopped by a rich looking boy.",
"speaker": "Rich Boy",
"intro_dialogue": "Good day to you.$I can't help but notice that your\n{{strongestPokemon}} looks positively divine!$I've always wanted to have a Pokémon like that!$I'd pay you handsomely,\nand also give you this old bauble!",
"intro_dialogue": "Good day to you.$I can't help but notice that your\n{{strongestPokemon}} looks positively divine!$I've always wanted to have a pet like that!$I'd pay you handsomely,\nand also give you this old bauble!",
"title": "An Offer You Can't Refuse",
"description": "You're being offered a @[TOOLTIP_TITLE]{Shiny Charm} and {{price, money}} for your {{strongestPokemon}}!\n\nIt's an extremely good deal, but can you really bear to part with such a strong team member?",
"query": "What will you do?",

View File

@ -17,9 +17,9 @@
"disabled_tooltip": "You need at least 1 Bug Type Pokémon on your team to select this.",
"selected": "You show the trainer all your Bug Type Pokémon...",
"selected_0_to_1": "Huh? You only have {{numBugTypes}}...$Guess I'm wasting my breath on someone like you...",
"selected_2_to_3": "Hey, you've got {{numBugTypes}}!\nNot bad.$Here, this might help you on your journey to catch more!",
"selected_4_to_5": "What? You have {{numBugTypes}}?\nNice!$You're not quite at my level, but I can see shades of myself in you!\n$Take this, my young apprentice!",
"selected_6": "Whoa! {{numBugTypes}}!\n$You must love Bug Types almost as much as I do!$Here, take this as a token of our camaraderie!"
"selected_2_to_3": "Hey, you've got {{numBugTypes}} Bug Types!\nNot bad.$Here, this might help you on your journey to catch more!",
"selected_4_to_5": "What? You have {{numBugTypes}} Bug Types?\nNice!$You're not quite at my level, but I can see shades of myself in you!\n$Take this, my young apprentice!",
"selected_6": "Whoa! {{numBugTypes}} Bug Types!\n$You must love Bug Types almost as much as I do!$Here, take this as a token of our camaraderie!"
},
"3": {
"label": "Gift a Bug Item",
@ -34,7 +34,5 @@
"battle_won": "Your knowledge and skill were perfect at exploiting our weaknesses!$In exchange for the valuable lesson,\nallow me to teach one of your Pokémon a Bug Type Move!",
"teach_move_prompt": "Select a move to teach a Pokémon.",
"confirm_no_teach": "You sure you don't want to learn one of these great moves?",
"outro": "I see great Bug Pokémon in your future!\nMay our paths cross again!$Bug out!",
"numBugTypes_one": "{{count}} Bug Type",
"numBugTypes_other": "{{count}} Bug Types"
"outro": "I see great Bug Pokémon in your future!\nMay our paths cross again!$Bug out!"
}

View File

@ -21,7 +21,7 @@
"label": "Sales Assistant",
"tooltip": "(-) Your {{option3PrimaryName}} uses {{option3PrimaryMove}}\n(+) Earn @[MONEY]{Money}",
"disabled_tooltip": "Your Pokémon need to know certain moves for this job",
"selected": "Your {{option3PrimaryName}} spends the day using {{option3PrimaryMove}} to draw customers to the business!"
"selected": "Your {{option3PrimaryName}} spends the day using {{option3PrimaryMove}} to attract customers to the business!"
}
},
"job_complete_good": "Thanks for the assistance!\nYour {{selectedPokemon}} was incredibly helpful!$Here's your check for the day.",

View File

@ -1,30 +0,0 @@
{
"intro": "It's a trainer carrying tons of Pokémon Eggs!",
"intro_dialogue": "Hey there, trainer!$It looks like some of your\npartner Pokémon are feeling a little down.$Why not have a battle with me to cheer them up?",
"title": "The Expert Pokémon Breeder",
"description": "You've been challenged to a battle where @[TOOLTIP_TITLE]{you can only use a single Pokémon}. It might be tough, but it would surely deepen the bond you have with the Pokémon you choose!\nThe breeder will also give you some @[TOOLTIP_TITLE]{Pokémon Eggs} if you win!",
"query": "Who will you battle with?",
"cleffa_1_nickname": "Ace",
"cleffa_2_nickname": "Clefablest",
"cleffa_3_nickname": "{{speciesName}} the Great",
"option": {
"1": {
"label": "{{pokemon1Name}}",
"tooltip_base": "(-) Tough Battle\n(+) Gain Friendship with {{pokemon1Name}}"
},
"2": {
"label": "{{pokemon2Name}}",
"tooltip_base": "(-) Tough Battle\n(+) Gain Friendship with {{pokemon2Name}}"
},
"3": {
"label": "{{pokemon3Name}}",
"tooltip_base": "(-) Tough Battle\n(+) Gain Friendship with {{pokemon3Name}}"
},
"selected": "Let's do this!"
},
"outro": "Look how happy your {{chosenPokemon}} is now!$Here, you can have these as well.",
"gained_eggs": "@s{item_fanfare}You received {{numEggs}}!",
"eggs_tooltip": "\n(+) Earn {{eggs}}",
"numEggs_one": "{{count}} {{rarity}} Egg",
"numEggs_other": "{{count}} {{rarity}} Eggs"
}

View File

@ -172,6 +172,5 @@
"vivi": "Vivi",
"vicky": "Vicky",
"vito": "Vito",
"bug_type_superfan": "Bug-Type Superfan",
"expert_pokemon_breeder": "Expert Pokémon Breeder"
"bug_type_superfan": "Bug-Type Superfan"
}

View File

@ -3121,11 +3121,11 @@
},
"behemothBlade": {
"name": "Gladius Maximus",
"effect": "Le lanceur se transforme en une immense épée et pourfend sa cible."
"effect": "Le lanceur se transforme en une immense épée et pourfend sa cible. Cette capacité inflige le double de dégâts aux Pokémon Dynamax."
},
"behemothBash": {
"name": "Aegis Maxima",
"effect": "Le lanceur se transforme en un immense bouclier et charge sa cible."
"effect": "Le lanceur se transforme en un immense bouclier et charge sa cible. Cette capacité inflige le double de dégâts aux Pokémon Dynamax."
},
"auraWheel": {
"name": "Roue Libre",

View File

@ -13,7 +13,7 @@
"ALL": "Tout",
"PASS_BATON": "Relais",
"UNPAUSE_EVOLUTION": "Réactiver Évolution",
"PAUSE_EVOLUTION": "Empêcher Évolution",
"PAUSE_EVOLUTION": "Interrompre Évolution",
"REVIVE": "Ranimer",
"RENAME": "Renommer",
"choosePokemon": "Sélectionnez un Pokémon.",

View File

@ -600,7 +600,7 @@ export class TerastallizeAccessModifier extends PersistentModifier {
export abstract class PokemonHeldItemModifier extends PersistentModifier {
public pokemonId: integer;
public isTransferable: boolean = true;
readonly isTransferrable: boolean = true;
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, stackCount);
@ -699,7 +699,7 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModifier {
protected battlesLeft: integer;
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor(type: ModifierTypes.ModifierType, pokemonId: integer, battlesLeft?: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -736,7 +736,7 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi
export class TerastallizeModifier extends LapsingPokemonHeldItemModifier {
public teraType: Type;
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor(type: ModifierTypes.TerastallizeModifierType, pokemonId: integer, teraType: Type, battlesLeft?: integer, stackCount?: integer) {
super(type, pokemonId, battlesLeft || 10, stackCount);
@ -799,7 +799,7 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier {
*/
export class BaseStatModifier extends PokemonHeldItemModifier {
protected stat: PermanentStat;
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor(type: ModifierType, pokemonId: integer, stat: PermanentStat, stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -843,7 +843,7 @@ export class BaseStatModifier extends PokemonHeldItemModifier {
export class EvoTrackerModifier extends PokemonHeldItemModifier {
protected species: Species;
protected required: integer;
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor(type: ModifierType, pokemonId: integer, species: Species, required: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -880,7 +880,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier {
*/
export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
private statModifier: integer;
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor(type: ModifierTypes.PokemonBaseStatTotalModifierType, pokemonId: integer, statModifier: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -929,7 +929,7 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
private statModifier: integer;
private stats: Stat[];
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor (type: ModifierType, pokemonId: integer, statModifier: integer, stats: Stat[], stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -979,7 +979,7 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
* Currently used by Macho Brace item
*/
export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier {
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -2346,7 +2346,7 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier {
export class PokemonFormChangeItemModifier extends PokemonHeldItemModifier {
public formChangeItem: FormChangeItem;
public active: boolean;
public isTransferable: boolean = false;
readonly isTransferrable: boolean = false;
constructor(type: ModifierTypes.FormChangeItemModifierType, pokemonId: integer, formChangeItem: FormChangeItem, active: boolean, stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -2691,7 +2691,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
const transferredModifierTypes: ModifierTypes.ModifierType[] = [];
const itemModifiers = pokemon.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.pokemonId === targetPokemon.id && m.isTransferable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[];
&& m.pokemonId === targetPokemon.id && m.isTransferrable, targetPokemon.isPlayer()) as PokemonHeldItemModifier[];
let highestItemTier = itemModifiers.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier!, highestTier), 0); // TODO: is this bang correct?
let tierItemModifiers = itemModifiers.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
@ -2736,7 +2736,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
* @see {@linkcode modifierTypes[MINI_BLACK_HOLE]}
*/
export class TurnHeldItemTransferModifier extends HeldItemTransferModifier {
isTransferable: boolean = true;
isTransferrable: boolean = true;
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
super(type, pokemonId, stackCount);
}
@ -2762,7 +2762,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier {
}
setTransferrableFalse(): void {
this.isTransferable = false;
this.isTransferrable = false;
}
}

View File

@ -6,7 +6,7 @@ import { PokeballType } from "#enums/pokeball";
import { Species } from "#enums/species";
import { StatusEffect } from "#enums/status-effect";
import { TimeOfDay } from "#enums/time-of-day";
import { VariantTier } from "#enums/variant-tier";
import { VariantTier } from "#enums/variant-tiers";
import { WeatherType } from "#enums/weather-type";
import { type PokeballCounts } from "./battle-scene";
import { Gender } from "./data/gender";

View File

@ -16,7 +16,6 @@ import i18next from "i18next";
import { FieldPhase } from "./field-phase";
import { SelectTargetPhase } from "./select-target-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { isNullOrUndefined } from "#app/utils";
export class CommandPhase extends FieldPhase {
protected fieldIndex: integer;
@ -180,16 +179,14 @@ export class CommandPhase extends FieldPhase {
case Command.POKEMON:
case Command.RUN:
const isSwitch = command === Command.POKEMON;
const { currentBattle, arena } = this.scene;
const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed;
if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) {
if (!isSwitch && this.scene.arena.biomeType === Biome.END) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => {
this.scene.ui.showText("", 0);
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
}, null, true);
} else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) {
} else if (!isSwitch && (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) {
this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex);
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => {
@ -200,12 +197,12 @@ export class CommandPhase extends FieldPhase {
const batonPass = isSwitch && args[0] as boolean;
const trappedAbMessages: string[] = [];
if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) {
currentBattle.turnCommands[this.fieldIndex] = isSwitch
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN };
success = true;
if (!isSwitch && this.fieldIndex) {
currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true;
}
} else if (trappedAbMessages.length > 0) {
if (!isSwitch) {
@ -222,7 +219,7 @@ export class CommandPhase extends FieldPhase {
// trapTag should be defined at this point, but just in case...
if (!trapTag) {
currentBattle.turnCommands[this.fieldIndex] = isSwitch
this.scene.currentBattle.turnCommands[this.fieldIndex] = isSwitch
? { command: Command.POKEMON, cursor: cursor, args: args }
: { command: Command.RUN };
break;

View File

@ -60,11 +60,6 @@ export class GameOverPhase extends BattlePhase {
this.scene.ui.fadeOut(1250).then(() => {
this.scene.reset();
this.scene.clearPhaseQueue();
// If this is a ME, clear any residual visual sprites before reloading
const encounter = this.scene.currentBattle.mysteryEncounter;
if (encounter?.introVisuals) {
this.scene.field.remove(encounter.introVisuals, true);
}
this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => {
this.scene.pushPhase(new EncounterPhase(this.scene, true));
@ -243,7 +238,7 @@ export class GameOverPhase extends BattlePhase {
gameVersion: this.scene.game.config.gameVersion,
timestamp: new Date().getTime(),
challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)),
mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1,
mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType,
mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData
} as SessionSaveData;
}

View File

@ -6,16 +6,14 @@ export class MessagePhase extends Phase {
private callbackDelay: integer | null;
private prompt: boolean | null;
private promptDelay: integer | null;
private speaker?: string;
constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null, speaker?: string) {
constructor(scene: BattleScene, text: string, callbackDelay?: integer | null, prompt?: boolean | null, promptDelay?: integer | null) {
super(scene);
this.text = text;
this.callbackDelay = callbackDelay!; // TODO: is this bang correct?
this.prompt = prompt!; // TODO: is this bang correct?
this.promptDelay = promptDelay!; // TODO: is this bang correct?
this.speaker = speaker;
}
start() {
@ -23,15 +21,11 @@ export class MessagePhase extends Phase {
if (this.text.indexOf("$") > -1) {
const pageIndex = this.text.indexOf("$");
this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay, this.speaker));
this.scene.unshiftPhase(new MessagePhase(this.scene, this.text.slice(pageIndex + 1), this.callbackDelay, this.prompt, this.promptDelay));
this.text = this.text.slice(0, pageIndex).trim();
}
if (this.speaker) {
this.scene.ui.showDialogue(this.text, this.speaker, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.promptDelay ?? 0);
} else {
this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay);
}
this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay);
}
end() {

View File

@ -3,7 +3,7 @@ import BattleScene from "../battle-scene";
import { Phase } from "../phase";
import { Mode } from "../ui/ui";
import { transitionMysteryEncounterIntroVisuals, OptionSelectSettings } from "../data/mystery-encounters/utils/encounter-phase-utils";
import MysteryEncounterOption, { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-encounter-option";
import MysteryEncounterOption, { OptionPhaseCallback } from "../data/mystery-encounters/mystery-encounter-option";
import { getCharVariantFromDialogue } from "../data/dialogue";
import { TrainerSlot } from "../data/trainer-config";
import { BattleSpec } from "#enums/battle-spec";

View File

@ -1,7 +1,7 @@
import BattleScene from "#app/battle-scene";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type";
import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { ExtraModifierModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler";
import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler";
import { Mode } from "#app/ui/ui";
@ -10,7 +10,7 @@ import * as Utils from "#app/utils";
import { BattlePhase } from "./battle-phase";
import Overrides from "#app/overrides";
import { CustomModifierSettings } from "#app/modifier/modifier-type";
import { isNullOrUndefined, NumberHolder } from "#app/utils";
import { isNullOrUndefined } from "#app/utils";
export class SelectModifierPhase extends BattlePhase {
private rerollCount: integer;
@ -69,11 +69,11 @@ export class SelectModifierPhase extends BattlePhase {
}
let modifierType: ModifierType;
let cost: integer;
const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers);
switch (rowCursor) {
case 0:
switch (cursor) {
case 0:
const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers);
if (rerollCost < 0 || this.scene.money < rerollCost) {
this.scene.ui.playError();
return false;
@ -94,7 +94,7 @@ export class SelectModifierPhase extends BattlePhase {
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => {
if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) {
const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
&& m.isTransferrable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex];
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
} else {
@ -108,11 +108,6 @@ export class SelectModifierPhase extends BattlePhase {
});
break;
case 3:
if (rerollCost < 0) {
// Reroll lock button is also disabled when reroll is disabled
this.scene.ui.playError();
return false;
}
this.scene.lockModifierTiers = !this.scene.lockModifierTiers;
const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers));
@ -138,10 +133,7 @@ export class SelectModifierPhase extends BattlePhase {
if (shopOption.type) {
modifierType = shopOption.type;
}
// Apply Black Sludge to healing item cost
const healingItemCost = new NumberHolder(shopOption.cost);
this.scene.applyModifier(HealShopCostModifier, true, healingItemCost);
cost = healingItemCost.value;
cost = shopOption.cost;
break;
}

View File

@ -6,7 +6,7 @@ import Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { ResetNegativeStatStageModifier } from "#app/modifier/modifier";
import { handleTutorial, Tutorial } from "#app/tutorial";
import { NumberHolder, BooleanHolder } from "#app/utils";
import * as Utils from "#app/utils";
import i18next from "i18next";
import { PokemonPhase } from "./pokemon-phase";
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
@ -42,23 +42,17 @@ export class StatStageChangePhase extends PokemonPhase {
return this.end();
}
const stages = new NumberHolder(this.stages);
if (!this.ignoreAbilities) {
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
}
let simulate = false;
const filteredStats = this.stats.filter(stat => {
const cancelled = new BooleanHolder(false);
const cancelled = new Utils.BooleanHolder(false);
if (!this.selfTarget && stages.value < 0) {
if (!this.selfTarget && this.stages < 0) {
// TODO: Include simulate boolean when tag applications can be simulated
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
}
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
if (!cancelled.value && !this.selfTarget && this.stages < 0) {
applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate);
}
@ -70,6 +64,12 @@ export class StatStageChangePhase extends PokemonPhase {
return !cancelled.value;
});
const stages = new Utils.IntegerHolder(this.stages);
if (!this.ignoreAbilities) {
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
}
const relLevels = filteredStats.map(s => (stages.value >= 1 ? Math.min(pokemon.getStatStage(s) + stages.value, 6) : Math.max(pokemon.getStatStage(s) + stages.value, -6)) - pokemon.getStatStage(s));
this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);

View File

@ -279,8 +279,6 @@ export function getAchievementDescription(localizationKey: string): string {
return i18next.t("achv:FRESH_START.description", { context: genderStr });
case "INVERSE_BATTLE":
return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr });
case "BREEDERS_IN_SPACE":
return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr });
default:
return "";
}
@ -358,7 +356,6 @@ export const achvs = {
MONO_FAIRY: new ChallengeAchv("MONO_FAIRY", "", "MONO_FAIRY.description", "fairy_feather", 100, (c, scene) => c instanceof SingleTypeChallenge && c.value === 18 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
FRESH_START: new ChallengeAchv("FRESH_START", "", "FRESH_START.description", "reviver_seed", 100, (c, scene) => c instanceof FreshStartChallenge && c.value > 0 && !scene.gameMode.challenges.some(c => c.id === Challenges.INVERSE_BATTLE && c.value > 0)),
INVERSE_BATTLE: new ChallengeAchv("INVERSE_BATTLE", "", "INVERSE_BATTLE.description", "inverse", 100, c => c instanceof InverseBattleChallenge && c.value > 0),
BREEDERS_IN_SPACE: new Achv("BREEDERS_IN_SPACE", "", "BREEDERS_IN_SPACE.description", "moon_stone", 100).setSecret(),
};
export function initAchievements() {

View File

@ -1,6 +1,6 @@
import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species";
import { VariantTier } from "#enums/variant-tier";
import { VariantTier } from "#enums/variant-tiers";
import { EGG_SEED, Egg } from "../data/egg";
import { EggSourceType } from "#app/enums/egg-source-types";

View File

@ -31,7 +31,7 @@ describe("Abilities - Contrary", () => {
});
it("should invert stat changes when applied", async() => {
await game.classicMode.startBattle([
await game.startBattle([
Species.SLOWBRO
]);
@ -39,39 +39,4 @@ describe("Abilities - Contrary", () => {
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000);
describe("With Clear Body", () => {
it("should apply positive effects", async () => {
game.override
.enemyPassiveAbility(Abilities.CLEAR_BODY)
.moveset([Moves.TAIL_WHIP]);
await game.classicMode.startBattle([Species.SLOWBRO]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
game.move.select(Moves.TAIL_WHIP);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(1);
});
it("should block negative effects", async () => {
game.override
.enemyPassiveAbility(Abilities.CLEAR_BODY)
.enemyMoveset([Moves.HOWL, Moves.HOWL, Moves.HOWL, Moves.HOWL])
.moveset([Moves.SPLASH]);
await game.classicMode.startBattle([Species.SLOWBRO]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
});
});

View File

@ -1,7 +1,7 @@
import { Egg, getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg";
import { EggSourceType } from "#app/enums/egg-source-types";
import { EggTier } from "#app/enums/egg-type";
import { VariantTier } from "#app/enums/variant-tier";
import { VariantTier } from "#app/enums/variant-tiers";
import EggData from "#app/system/egg-data";
import * as Utils from "#app/utils";
import { Species } from "#enums/species";
@ -136,9 +136,9 @@ describe("Egg Generation Tests", () => {
expect(result).toBe(expectedResult);
});
it("should return a shiny standard variant", () => {
it("should return a shiny common variant", () => {
const scene = game.scene;
const expectedVariantTier = VariantTier.STANDARD;
const expectedVariantTier = VariantTier.COMMON;
const result = new Egg({ scene, isShiny: true, variantTier: expectedVariantTier, species: Species.BULBASAUR }).generatePlayerPokemon(scene).variant;

View File

@ -1,87 +0,0 @@
import { BattlerIndex } from "#app/battle";
import { allMoves, ShellSideArmCategoryAttr } from "#app/data/move";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
describe("Moves - Shell Side Arm", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const TIMEOUT = 20 * 1000;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([Moves.SHELL_SIDE_ARM])
.battleType("single")
.startingLevel(100)
.enemyLevel(100)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("becomes a physical attack if forecasted to deal more damage as physical", async () => {
game.override.enemySpecies(Species.SNORLAX);
await game.classicMode.startBattle([Species.MANAPHY]);
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
vi.spyOn(shellSideArmAttr, "apply");
game.move.select(Moves.SHELL_SIDE_ARM);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(true);
}, TIMEOUT);
it("remains a special attack if forecasted to deal more damage as special", async () => {
game.override.enemySpecies(Species.SLOWBRO);
await game.classicMode.startBattle([Species.MANAPHY]);
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
vi.spyOn(shellSideArmAttr, "apply");
game.move.select(Moves.SHELL_SIDE_ARM);
await game.phaseInterceptor.to("MoveEffectPhase");
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);
}, TIMEOUT);
it("respects stat stage changes when forecasting base damage", async () => {
game.override
.enemySpecies(Species.SNORLAX)
.enemyMoveset(Moves.COTTON_GUARD);
await game.classicMode.startBattle([Species.MANAPHY]);
const shellSideArm = allMoves[Moves.SHELL_SIDE_ARM];
const shellSideArmAttr = shellSideArm.getAttrs(ShellSideArmCategoryAttr)[0];
vi.spyOn(shellSideArmAttr, "apply");
game.move.select(Moves.SHELL_SIDE_ARM);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("BerryPhase", false);
expect(shellSideArmAttr.apply).toHaveLastReturnedWith(false);
}, TIMEOUT);
});

View File

@ -1,58 +0,0 @@
import { BattlerIndex } from "#app/battle";
import { allMoves } from "#app/data/move";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { DamageCalculationResult } from "#app/field/pokemon";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Steamroller", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.moveset([Moves.STEAMROLLER]).battleType("single").enemyAbility(Abilities.BALL_FETCH);
});
it("should always hit a minimzed target with double damage", async () => {
game.override.enemySpecies(Species.DITTO).enemyMoveset(Moves.MINIMIZE);
await game.classicMode.startBattle([Species.IRON_BOULDER]);
const ditto = game.scene.getEnemyPokemon()!;
vi.spyOn(ditto, "getAttackDamage");
ditto.hp = 5000;
const steamroller = allMoves[Moves.STEAMROLLER];
vi.spyOn(steamroller, "calculateBattleAccuracy");
const ironBoulder = game.scene.getPlayerPokemon()!;
vi.spyOn(ironBoulder, "getAccuracyMultiplier");
// Turn 1
game.move.select(Moves.STEAMROLLER);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toNextTurn();
// Turn 2
game.move.select(Moves.STEAMROLLER);
await game.toNextTurn();
const [dmgCalcTurn1, dmgCalcTurn2]: DamageCalculationResult[] = vi
.mocked(ditto.getAttackDamage)
.mock.results.map((r) => r.value);
expect(dmgCalcTurn2.damage).toBeGreaterThanOrEqual(dmgCalcTurn1.damage * 2);
expect(ditto.getTag(BattlerTagType.MINIMIZED)).toBeDefined();
expect(steamroller.calculateBattleAccuracy).toHaveReturnedWith(-1);
});
});

View File

@ -1,53 +0,0 @@
import { BattlerIndex } from "#app/battle";
import { allMoves } from "#app/data/move";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
describe("Moves - Whirlwind", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.WHIRLWIND)
.enemySpecies(Species.PIDGEY);
});
it.each([
{ move: Moves.FLY, name: "Fly" },
{ move: Moves.BOUNCE, name: "Bounce" },
{ move: Moves.SKY_DROP, name: "Sky Drop" },
])("should not hit a flying target: $name (=$move)", async ({ move }) => {
game.override.moveset([move]);
await game.classicMode.startBattle([Species.STARAPTOR]);
const staraptor = game.scene.getPlayerPokemon()!;
const whirlwind = allMoves[Moves.WHIRLWIND];
vi.spyOn(whirlwind, "getFailedText");
game.move.select(move);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
await game.toNextTurn();
expect(staraptor.findTag((t) => t.tagType === BattlerTagType.FLYING)).toBeDefined();
expect(whirlwind.getFailedText).toHaveBeenCalledOnce();
});
});

View File

@ -69,6 +69,22 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(ATrainersTestEncounter.options.length).toBe(2);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.A_TRAINERS_TEST);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully ", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = ATrainersTestEncounter;

View File

@ -66,6 +66,22 @@ describe("Absolute Avarice - Mystery Encounter", () => {
expect(AbsoluteAvariceEncounter.options.length).toBe(3);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.ABSOLUTE_AVARICE);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should not spawn outside of proper biomes", async () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
game.override.startingBiome(Biome.VOLCANO);

View File

@ -80,6 +80,22 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully ", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = AnOfferYouCantRefuseEncounter;

View File

@ -69,6 +69,22 @@ describe("Berries Abound - Mystery Encounter", () => {
expect(BerriesAboundEncounter.options.length).toBe(3);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.BERRIES_ABOUND);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = BerriesAboundEncounter;
@ -82,6 +98,7 @@ describe("Berries Abound - Mystery Encounter", () => {
const config = BerriesAboundEncounter.enemyPartyConfigs[0];
expect(config).toBeDefined();
expect(config.levelAdditiveMultiplier).toBe(1);
expect(config.pokemonConfigs?.[0].isBoss).toBe(true);
expect(onInitResult).toBe(true);
});
@ -117,7 +134,7 @@ describe("Berries Abound - Mystery Encounter", () => {
});
// TODO: there is some severe test flakiness occurring for this file, needs to be looked at/addressed in separate issue
it("should reward the player with X berries based on wave", async () => {
it.skip("should reward the player with X berries based on wave", async () => {
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const numBerries = game.scene.currentBattle.mysteryEncounter!.misc.numBerries;

View File

@ -201,6 +201,22 @@ describe("Bug-Type Superfan - Mystery Encounter", () => {
expect(BugTypeSuperfanEncounter.options.length).toBe(3);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.BUG_TYPE_SUPERFAN);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = BugTypeSuperfanEncounter;

View File

@ -95,6 +95,14 @@ describe("Clowning Around - Mystery Encounter", () => {
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.CLOWNING_AROUND);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should initialize fully", async () => {
initSceneWithoutEncounterPhase(scene, defaultParty);
scene.currentBattle.mysteryEncounter = ClowningAroundEncounter;

View File

@ -69,6 +69,22 @@ describe("Dancing Lessons - Mystery Encounter", () => {
expect(DancingLessonsEncounter.options.length).toBe(3);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DANCING_LESSONS);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should not spawn outside of proper biomes", async () => {
game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT);
game.override.startingBiome(Biome.SPACE);

View File

@ -66,6 +66,22 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(DelibirdyEncounter.options.length).toBe(3);
});
it("should not run below wave 10", async () => {
game.override.startingWave(9);
await game.runToMysteryEncounter();
expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.DELIBIRDY);
});
it("should not run above wave 179", async () => {
game.override.startingWave(181);
await game.runToMysteryEncounter();
expect(scene.currentBattle.mysteryEncounter).toBeUndefined();
});
it("should not spawn if player does not have enough money", async () => {
scene.money = 0;

Some files were not shown because too many files have changed in this diff Show More