Merge branch 'beta' into localization/me-reward-count

This commit is contained in:
Enoch 2024-09-25 12:56:34 +09:00 committed by GitHub
commit 77650cc89c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
93 changed files with 606 additions and 416 deletions

View File

@ -2,7 +2,7 @@ import Phaser from "phaser";
import UI from "./ui/ui"; import UI from "./ui/ui";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon"; import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon";
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "./data/pokemon-species"; import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "./data/pokemon-species";
import { Constructor, isNullOrUndefined } from "#app/utils"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "./utils"; import * as Utils from "./utils";
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball"; import { PokeballType } from "./data/pokeball";
@ -1201,32 +1201,12 @@ export default class BattleScene extends SceneBase {
// Check for mystery encounter // Check for mystery encounter
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180 // Can only occur in place of a standard (non-boss) wild battle, waves 10-180
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves(); if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER || !isNullOrUndefined(mysteryEncounterType)) {
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < highestMysteryEncounterWave && newWaveIndex > lowestMysteryEncounterWave) {
const roll = Utils.randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well)
// Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (newWaveIndex - lowestMysteryEncounterWave);
const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length;
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER;
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
const canSpawn = encounteredEvents.length === 0 || (newWaveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);
if (canSpawn && roll < successRate) {
newBattleType = BattleType.MYSTERY_ENCOUNTER; newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset base spawn weight // Reset base spawn weight
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
} else { } else if (newBattleType === BattleType.WILD) {
this.mysteryEncounterSaveData.encounterSpawnChance = sessionEncounterRate + WEIGHT_INCREMENT_ON_SPAWN_MISS; this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
}
} }
} }
@ -1267,9 +1247,8 @@ export default class BattleScene extends SceneBase {
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) { if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter) // Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
this.currentBattle.double = false; this.currentBattle.double = false;
this.executeWithSeedOffset(() => { // Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounterType); this.currentBattle.mysteryEncounterType = mysteryEncounterType;
}, this.currentBattle.waveIndex << 4);
} }
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6)); //this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
@ -2657,8 +2636,7 @@ export default class BattleScene extends SceneBase {
modifier = mt.modifier as PokemonHeldItemModifier; modifier = mt.modifier as PokemonHeldItemModifier;
modifier.pokemonId = enemyPokemon.id; modifier.pokemonId = enemyPokemon.id;
} }
const stackCount = mt.stackCount ?? 1; modifier.stackCount = mt.stackCount ?? 1;
modifier.stackCount = stackCount;
modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable; modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable;
this.addEnemyModifier(modifier, true); this.addEnemyModifier(modifier, true);
}); });
@ -3097,6 +3075,51 @@ export default class BattleScene extends SceneBase {
} }
} }
/**
* Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}.
* Currently, the only modes that MEs are allowed in are Classic and Challenge.
* Additionally, MEs cannot spawn outside of waves 10-180 in those modes
*
* @param newBattleType
* @param waveIndex
* @param sessionDataEncounterType
*/
private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean {
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) {
// If ME type is already defined in session data, no need to roll RNG check
if (!isNullOrUndefined(sessionDataEncounterType)) {
return true;
}
// Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents;
// If total number of encounters is lower than expected for the run, slightly favor a new encounter spawn (reverse as well)
// Reduces occurrence of runs with total encounters significantly different from AVERAGE_ENCOUNTERS_PER_RUN_TARGET
const expectedEncountersByFloor = AVERAGE_ENCOUNTERS_PER_RUN_TARGET / (highestMysteryEncounterWave - lowestMysteryEncounterWave) * (waveIndex - lowestMysteryEncounterWave);
const currentRunDiffFromAvg = expectedEncountersByFloor - encounteredEvents.length;
const favoredEncounterRate = sessionEncounterRate + currentRunDiffFromAvg * ANTI_VARIANCE_WEIGHT_MODIFIER;
const successRate = isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE) ? favoredEncounterRate : Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE!;
// If the most recent ME was 3 or fewer waves ago, can never spawn a ME
const canSpawn = encounteredEvents.length === 0 || (waveIndex - encounteredEvents[encounteredEvents.length - 1].waveIndex) > 3 || !isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_RATE_OVERRIDE);
if (canSpawn) {
let roll = MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT;
// Always rolls the check on the same offset to ensure no RNG changes from reloading session
this.executeWithSeedOffset(() => {
roll = randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
}, waveIndex * 3 * 1000);
return roll < successRate;
}
}
return false;
}
/** /**
* Loads or generates a mystery encounter * Loads or generates a mystery encounter
* @param encounterType used to load session encounter when restarting game, etc. * @param encounterType used to load session encounter when restarting game, etc.
@ -3105,10 +3128,10 @@ export default class BattleScene extends SceneBase {
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter { getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
// Loading override or session encounter // Loading override or session encounter
let encounter: MysteryEncounter | null; let encounter: MysteryEncounter | null;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) { if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!]; encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
} else { } else {
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType!] : null; encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null;
} }
// Check for queued encounters first // Check for queued encounters first
@ -3151,7 +3174,7 @@ export default class BattleScene extends SceneBase {
let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > greatThreshold ? MysteryEncounterTier.GREAT : tierValue > ultraThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE; let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > greatThreshold ? MysteryEncounterTier.GREAT : tierValue > ultraThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE;
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) { if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE!; tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE;
} }
let availableEncounters: MysteryEncounter[] = []; let availableEncounters: MysteryEncounter[] = [];

View File

@ -18,6 +18,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { CustomModifierSettings } from "#app/modifier/modifier-type"; import { CustomModifierSettings } from "#app/modifier/modifier-type";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
export enum ClassicFixedBossWaves { export enum ClassicFixedBossWaves {
// TODO: other fixed wave battles should be added here // TODO: other fixed wave battles should be added here
@ -88,6 +89,7 @@ export default class Battle {
public playerFaintsHistory: FaintLogEntry[] = []; public playerFaintsHistory: FaintLogEntry[] = [];
public enemyFaintsHistory: FaintLogEntry[] = []; public enemyFaintsHistory: FaintLogEntry[] = [];
public mysteryEncounterType?: MysteryEncounterType;
/** If the current battle is a Mystery Encounter, this will always be defined */ /** If the current battle is a Mystery Encounter, this will always be defined */
public mysteryEncounter?: MysteryEncounter; public mysteryEncounter?: MysteryEncounter;

View File

@ -430,7 +430,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
scene.field.add(moveAnim.bgSprite); scene.field.add(moveAnim.bgSprite);
const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon(); const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon();
if (!isNullOrUndefined(priority)) { if (!isNullOrUndefined(priority)) {
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!); scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority);
} else if (fieldPokemon?.isOnField()) { } else if (fieldPokemon?.isOnField()) {
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon); scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
} }

View File

@ -15,6 +15,7 @@ import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:aTrainersTest"; const namespace = "mysteryEncounter:aTrainersTest";
@ -27,7 +28,7 @@ const namespace = "mysteryEncounter:aTrainersTest";
export const ATrainersTestEncounter: MysteryEncounter = export const ATrainersTestEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
.withEncounterTier(MysteryEncounterTier.ROGUE) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withSceneWaveRangeRequirement(100, 180) .withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
.withIntroSpriteConfigs([]) // These are set in onInit() .withIntroSpriteConfigs([]) // These are set in onInit()
.withIntroDialogue([ .withIntroDialogue([
{ {

View File

@ -10,7 +10,7 @@ import { PersistentModifierRequirement } from "#app/data/mystery-encounters/myst
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -159,12 +159,6 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
]) ])
.withHideWildIntroMessage(true) .withHideWildIntroMessage(true)
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withOnVisualsStart((scene: BattleScene) => {
doGreedentSpriteSteal(scene);
doBerrySpritePile(scene);
return true;
})
.withIntroDialogue([ .withIntroDialogue([
{ {
text: `${namespace}.intro`, text: `${namespace}.intro`,
@ -202,10 +196,15 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType; const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
bossModifierConfigs.push({ modifier: modifierType }); bossModifierConfigs.push({ modifier: modifierType });
} }
scene.removeModifier(berryMod);
}); });
// Do NOT remove the real berries yet or else it will be persisted in the session data
// SpDef buff below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
[Stat.SPDEF] :
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
// Calculate boss mon // Calculate boss mon
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
levelAdditiveModifier: 1, levelAdditiveModifier: 1,
@ -214,12 +213,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
species: getPokemonSpecies(Species.GREEDENT), species: getPokemonSpecies(Species.GREEDENT),
isBoss: true, isBoss: true,
bossSegments: 3, bossSegments: 3,
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF], moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH],
modifierConfigs: bossModifierConfigs, modifierConfigs: bossModifierConfigs,
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`); queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
} }
} }
], ],
@ -230,6 +229,21 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
return true; return true;
}) })
.withOnVisualsStart((scene: BattleScene) => {
doGreedentSpriteSteal(scene);
doBerrySpritePile(scene);
// Remove the berries from the party
// Session has been safely saved at this point, so data won't be lost
const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
berryItems.forEach(berryMod => {
scene.removeModifier(berryMod);
});
scene.updateModifiers(true);
return true;
})
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
@ -251,7 +265,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
const givePartyPokemonReviverSeeds = () => { const givePartyPokemonReviverSeeds = () => {
const party = scene.getParty(); const party = scene.getParty();
party.forEach(p => { party.forEach(p => {
if (revSeed) { const heldItems = p.getHeldItems();
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
const seedModifier = revSeed.newModifier(p); const seedModifier = revSeed.newModifier(p);
if (seedModifier) { if (seedModifier) {
encounter.setDialogueToken("foodReward", seedModifier.type.name); encounter.setDialogueToken("foodReward", seedModifier.type.name);

View File

@ -8,7 +8,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; 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 { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
@ -17,6 +17,14 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
const namespace = "mysteryEncounter:offerYouCantRefuse"; const namespace = "mysteryEncounter:offerYouCantRefuse";
/**
* Money offered starts at base value of Relic Gold, increasing linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased
* Starter value 1-3 -> Relic Gold
* Starter value 10 -> 3 * Relic Gold
*/
const MONEY_MINIMUM_MULTIPLIER = 10;
const MONEY_MAXIMUM_MULTIPLIER = 30;
/** /**
* An Offer You Can't Refuse encounter. * An Offer You Can't Refuse encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
@ -61,7 +69,11 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
.withOnInit((scene: BattleScene) => { .withOnInit((scene: BattleScene) => {
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true); const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true);
const price = scene.getWaveMoneyAmount(10);
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(true);
const starterValue: number = speciesStarters[baseSpecies] ?? 1;
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER);
const price = scene.getWaveMoneyAmount(multiplier);
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
encounter.setDialogueToken("price", price.toString()); encounter.setDialogueToken("price", price.toString());

View File

@ -135,7 +135,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
// Generate a random berry and give it to the first Pokemon with room for it // Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) { for (let i = 0; i < numBerries; i++) {
await tryGiveBerry(scene); tryGiveBerry(scene);
} }
}; };
@ -186,15 +186,20 @@ export const BerriesAboundEncounter: MysteryEncounter =
// Generate a random berry and give it to the first Pokemon with room for it // Generate a random berry and give it to the first Pokemon with room for it
for (let i = 0; i < numBerries; i++) { for (let i = 0; i < numBerries; i++) {
await tryGiveBerry(scene); tryGiveBerry(scene);
} }
}; };
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
[Stat.DEF, Stat.SPDEF, Stat.SPD] :
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON]; config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => { config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`); queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
}; };
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards); setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
await showEncounterText(scene, `${namespace}.option.2.selected_bad`); await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
@ -212,7 +217,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first) // Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
for (let i = 0; i < numBerriesGrabbed; i++) { for (let i = 0; i < numBerriesGrabbed; i++) {
await tryGiveBerry(scene, fastestPokemon); tryGiveBerry(scene, fastestPokemon);
} }
}; };
@ -242,7 +247,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
) )
.build(); .build();
async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) { function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType; const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType; const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType;
@ -254,7 +259,7 @@ async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokem
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; && m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) { if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry); applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
return; return;
} }
} }
@ -265,7 +270,7 @@ async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokem
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier; && m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) { if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
await applyModifierTypeToPlayerPokemon(scene, pokemon, berry); applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
return; return;
} }
} }

View File

@ -492,7 +492,7 @@ function getTrainerConfigForWave(waveIndex: number) {
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex!; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
@ -515,14 +515,14 @@ function getTrainerConfigForWave(waveIndex: number) {
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex!; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})) }))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon2.formIndex)) { if (!isNullOrUndefined(pool3Mon2.formIndex)) {
p.formIndex = pool3Mon2.formIndex!; p.formIndex = pool3Mon2.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
@ -543,7 +543,7 @@ function getTrainerConfigForWave(waveIndex: number) {
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true)) .setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex!; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
@ -566,14 +566,14 @@ function getTrainerConfigForWave(waveIndex: number) {
})) }))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(2, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex!; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }
})) }))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
if (!isNullOrUndefined(pool3Mon.formIndex)) { if (!isNullOrUndefined(pool3Mon.formIndex)) {
p.formIndex = pool3Mon.formIndex!; p.formIndex = pool3Mon.formIndex;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.generateName(); p.generateName();
} }

View File

@ -10,7 +10,7 @@ import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement }
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import i18next from "#app/plugins/i18n"; import i18next from "#app/plugins/i18n";
@ -33,7 +33,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
"PokemonBaseStatTotalModifier" "PokemonBaseStatTotalModifier"
]; ];
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 1.5; const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
/** /**
* Delibird-y encounter. * Delibird-y encounter.
@ -122,9 +122,9 @@ export const DelibirdyEncounter: MysteryEncounter =
return true; return true;
}) })
.withOptionPhase(async (scene: BattleScene) => { .withOptionPhase(async (scene: BattleScene) => {
// Give the player an Ability Charm // Give the player an Amulet Coin
// Check if the player has max stacks of that item already // Check if the player has max stacks of that item already
const existing = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier; const existing = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
// At max stacks, give the first party pokemon a Shell Bell instead // At max stacks, give the first party pokemon a Shell Bell instead
@ -133,7 +133,7 @@ export const DelibirdyEncounter: MysteryEncounter =
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
} else { } else {
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM)); scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.AMULET_COIN));
} }
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);

View File

@ -63,7 +63,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
// Choose TMs // Choose TMs
const modifiers: ModifierTypeFunc[] = []; const modifiers: ModifierTypeFunc[] = [];
let i = 0; let i = 0;
while (i < 4) { while (i < 5) {
// 2/2/1 weight on TM rarity // 2/2/1 weight on TM rarity
const roll = randSeedInt(5); const roll = randSeedInt(5);
if (roll < 2) { if (roll < 2) {

View File

@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:fieldTrip";
export const FieldTripEncounter: MysteryEncounter = export const FieldTripEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
{ {
spriteKey: "preschooler_m", spriteKey: "preschooler_m",

View File

@ -189,7 +189,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
} }
// Burn random member // Burn random member
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.NONE); const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE);
if (burnable?.length > 0) { if (burnable?.length > 0) {
const roll = randSeedInt(burnable.length); const roll = randSeedInt(burnable.length);
const chosenPokemon = burnable[roll]; const chosenPokemon = burnable[roll];

View File

@ -11,9 +11,10 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import * as Utils from "#app/utils";
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils"; import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import i18next from "i18next"; import i18next from "i18next";
@ -28,6 +29,11 @@ import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-en
/** the i18n namespace for the encounter */ /** the i18n namespace for the encounter */
const namespace = "mysteryEncounter:globalTradeSystem"; const namespace = "mysteryEncounter:globalTradeSystem";
/** Base shiny chance of 512/65536 -> 1/128 odds, affected by events and Shiny Charms. Cannot exceed 1/16 odds. */
const WONDER_TRADE_SHINY_CHANCE = 512;
/** Max shiny chance of 4096/65536 -> 1/16 odds. */
const MAX_WONDER_TRADE_SHINY_CHANCE = 4096;
const LEGENDARY_TRADE_POOLS = { const LEGENDARY_TRADE_POOLS = {
1: [Species.RATTATA, Species.PIDGEY, Species.WEEDLE], 1: [Species.RATTATA, Species.PIDGEY, Species.WEEDLE],
2: [Species.SENTRET, Species.HOOTHOOT, Species.LEDYBA], 2: [Species.SENTRET, Species.HOOTHOOT, Species.LEDYBA],
@ -153,7 +159,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
return true; return true;
}, },
onHover: () => { onHover: () => {
const formName = tradePokemon.species.forms?.[pokemon.formIndex]?.formName; const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[pokemon.formIndex].formName : null;
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : ""); const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : ""); const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false); showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false);
@ -221,21 +227,47 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false); const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
// Extra shiny roll at 1/128 odds (boosted by events and charms) // Extra shiny roll at 1/128 odds (boosted by events and charms)
if (!tradePokemon.shiny) { if (!tradePokemon.shiny) {
// 512/65536 -> 1/128 const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE);
tradePokemon.trySetShinySeed(512, true); if (scene.eventManager.isEventActive()) {
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
}
scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
// Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms
// Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that
const shinyChance = Math.min(shinyThreshold.value, MAX_WONDER_TRADE_SHINY_CHANCE);
tradePokemon.trySetShinySeed(shinyChance, false);
} }
// Extra HA roll at base 1/64 odds (boosted by events and charms) // Extra HA roll at base 1/64 odds (boosted by events and charms)
if (pokemon.species.abilityHidden) { const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
const hiddenIndex = pokemon.species.ability2 ? 2 : 1; if (tradePokemon.species.abilityHidden) {
if (pokemon.abilityIndex < hiddenIndex) { if (tradePokemon.abilityIndex < hiddenIndex) {
const hiddenAbilityChance = new IntegerHolder(64); const hiddenAbilityChance = new IntegerHolder(64);
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
if (hasHiddenAbility) { if (hasHiddenAbility) {
pokemon.abilityIndex = hiddenIndex; tradePokemon.abilityIndex = hiddenIndex;
}
}
}
// If Pokemon is still not shiny or with HA, give the Pokemon a random Common egg move in its moveset
if (!tradePokemon.shiny && (!tradePokemon.species.abilityHidden || tradePokemon.abilityIndex < hiddenIndex)) {
const eggMoves = tradePokemon.getEggMoves();
if (eggMoves) {
// Cannot gen the rare egg move, only 1 of the first 3 common moves
const eggMove = eggMoves[randSeedInt(3)];
if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) {
if (tradePokemon.moveset.length < 4) {
tradePokemon.moveset.push(new PokemonMove(eggMove));
} else {
const eggMoveIndex = randSeedInt(4);
tradePokemon.moveset[eggMoveIndex] = new PokemonMove(eggMove);
}
} }
} }
} }
@ -450,7 +482,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?:
if (validSpecies?.length > 20) { if (validSpecies?.length > 20) {
validSpecies = randSeedShuffle(validSpecies); validSpecies = randSeedShuffle(validSpecies);
newSpecies = validSpecies.pop(); newSpecies = validSpecies.pop();
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) { while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) {
newSpecies = validSpecies.pop(); newSpecies = validSpecies.pop();
} }
} else { } else {

View File

@ -19,10 +19,11 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
const namespace = "mysteryEncounter:mysteriousChest"; const namespace = "mysteryEncounter:mysteriousChest";
const RAND_LENGTH = 100; const RAND_LENGTH = 100;
const COMMON_REWARDS_WEIGHT = 20; // 20% const TRAP_PERCENT = 35;
const ULTRA_REWARDS_WEIGHT = 50; // 30% const COMMON_REWARDS_PERCENT = 20;
const ROGUE_REWARDS_WEIGHT = 60; // 10% const ULTRA_REWARDS_PERCENT = 30;
const MASTER_REWARDS_WEIGHT = 65; // 5% const ROGUE_REWARDS_PERCENT = 10;
const MASTER_REWARDS_PERCENT = 5;
/** /**
* Mysterious Chest encounter. * Mysterious Chest encounter.
@ -83,6 +84,11 @@ export const MysteriousChestEncounter: MysteryEncounter =
encounter.enemyPartyConfigs = [config]; encounter.enemyPartyConfigs = [config];
encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName()); encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName());
encounter.setDialogueToken("trapPercent", TRAP_PERCENT.toString());
encounter.setDialogueToken("commonPercent", COMMON_REWARDS_PERCENT.toString());
encounter.setDialogueToken("ultraPercent", ULTRA_REWARDS_PERCENT.toString());
encounter.setDialogueToken("roguePercent", ROGUE_REWARDS_PERCENT.toString());
encounter.setDialogueToken("masterPercent", MASTER_REWARDS_PERCENT.toString());
return true; return true;
}) })
@ -109,7 +115,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
roll roll
}; };
if (roll >= MASTER_REWARDS_WEIGHT) { if (roll < TRAP_PERCENT) {
// Chest is springing trap, change to red chest sprite // Chest is springing trap, change to red chest sprite
const blueChestSprites = introVisuals.getSpriteAtIndex(0); const blueChestSprites = introVisuals.getSpriteAtIndex(0);
const redChestSprites = introVisuals.getSpriteAtIndex(1); const redChestSprites = introVisuals.getSpriteAtIndex(1);
@ -124,7 +130,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Open the chest // Open the chest
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
const roll = encounter.misc.roll; const roll = encounter.misc.roll;
if (roll < COMMON_REWARDS_WEIGHT) { if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
// Choose between 2 COMMON / 2 GREAT tier items (20%) // Choose between 2 COMMON / 2 GREAT tier items (20%)
setEncounterRewards(scene, { setEncounterRewards(scene, {
guaranteedModifierTiers: [ guaranteedModifierTiers: [
@ -137,7 +143,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.normal`); queueEncounterMessage(scene, `${namespace}.option.1.normal`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} else if (roll < ULTRA_REWARDS_WEIGHT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
// Choose between 3 ULTRA tier items (30%) // Choose between 3 ULTRA tier items (30%)
setEncounterRewards(scene, { setEncounterRewards(scene, {
guaranteedModifierTiers: [ guaranteedModifierTiers: [
@ -149,13 +155,13 @@ export const MysteriousChestEncounter: MysteryEncounter =
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.good`); queueEncounterMessage(scene, `${namespace}.option.1.good`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} else if (roll < ROGUE_REWARDS_WEIGHT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
// Choose between 2 ROGUE tier items (10%) // Choose between 2 ROGUE tier items (10%)
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE] }); setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE] });
// Display result message then proceed to rewards // Display result message then proceed to rewards
queueEncounterMessage(scene, `${namespace}.option.1.great`); queueEncounterMessage(scene, `${namespace}.option.1.great`);
leaveEncounterWithoutBattle(scene); leaveEncounterWithoutBattle(scene);
} else if (roll < MASTER_REWARDS_WEIGHT) { } else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) {
// Choose 1 MASTER tier item (5%) // Choose 1 MASTER tier item (5%)
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER] }); setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER] });
// Display result message then proceed to rewards // Display result message then proceed to rewards

View File

@ -27,6 +27,8 @@ const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
const SAFARI_MONEY_MULTIPLIER = 2; const SAFARI_MONEY_MULTIPLIER = 2;
const NUM_SAFARI_ENCOUNTERS = 3;
/** /**
* Safari Zone encounter. * Safari Zone encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800}
@ -55,6 +57,10 @@ export const SafariZoneEncounter: MysteryEncounter =
.withTitle(`${namespace}.title`) .withTitle(`${namespace}.title`)
.withDescription(`${namespace}.description`) .withDescription(`${namespace}.description`)
.withQuery(`${namespace}.query`) .withQuery(`${namespace}.query`)
.withOnInit((scene: BattleScene) => {
scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
return true;
})
.withOption(MysteryEncounterOptionBuilder .withOption(MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive .withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
@ -72,7 +78,7 @@ export const SafariZoneEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
encounter.continuousEncounter = true; encounter.continuousEncounter = true;
encounter.misc = { encounter.misc = {
safariPokemonRemaining: 3 safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS
}; };
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
// Load bait/mud assets // Load bait/mud assets

View File

@ -39,7 +39,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.withEncounterTier(MysteryEncounterTier.COMMON) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave .withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
.withSceneRequirement(new MoneyRequirement(undefined, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost .withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withCatchAllowed(true) .withCatchAllowed(true)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
@ -73,7 +73,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
.withOption( .withOption(
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withSceneMoneyRequirement(undefined, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost .withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}.option.1.tooltip`,
@ -172,7 +172,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true); const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon)); encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
// Defense/Spd buffs below wave 50, Atk/Def/Spd buffs otherwise // Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ? const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
[Stat.DEF, Stat.SPDEF, Stat.SPD] : [Stat.DEF, Stat.SPDEF, Stat.SPD] :
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]; [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];

View File

@ -34,7 +34,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
.withEncounterTier(MysteryEncounterTier.ULTRA) .withEncounterTier(MysteryEncounterTier.ULTRA)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withSceneRequirement(new MoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay .withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
.withAutoHideIntroVisuals(false) .withAutoHideIntroVisuals(false)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
{ {
@ -59,11 +59,12 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
const encounter = scene.currentBattle.mysteryEncounter!; const encounter = scene.currentBattle.mysteryEncounter!;
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false)); let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
const tries = 0; let tries = 0;
// Reroll any species that don't have HAs // Reroll any species that don't have HAs
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) { while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false)); species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
tries++;
} }
let pokemon: PlayerPokemon; let pokemon: PlayerPokemon;
@ -71,7 +72,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
// If no HA mon found or you roll 1%, give shiny Magikarp // If no HA mon found or you roll 1%, give shiny Magikarp
species = getPokemonSpecies(Species.MAGIKARP); species = getPokemonSpecies(Species.MAGIKARP);
const hiddenIndex = species.ability2 ? 2 : 1; const hiddenIndex = species.ability2 ? 2 : 1;
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true); pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true, 0);
} else { } else {
const hiddenIndex = species.ability2 ? 2 : 1; const hiddenIndex = species.ability2 ? 2 : 1;
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex); pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
@ -113,7 +114,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
MysteryEncounterOptionBuilder MysteryEncounterOptionBuilder
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT) .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
.withHasDexProgress(true) .withHasDexProgress(true)
.withSceneMoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2 .withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
.withDialogue({ .withDialogue({
buttonLabel: `${namespace}.option.1.label`, buttonLabel: `${namespace}.option.1.label`,
buttonTooltip: `${namespace}.option.1.tooltip`, buttonTooltip: `${namespace}.option.1.tooltip`,

View File

@ -33,7 +33,7 @@ const BST_INCREASE_VALUE = 10;
*/ */
export const TheStrongStuffEncounter: MysteryEncounter = export const TheStrongStuffEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
.withEncounterTier(MysteryEncounterTier.GREAT) .withEncounterTier(MysteryEncounterTier.COMMON)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party .withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
.withMaxAllowedEncounters(1) .withMaxAllowedEncounters(1)

View File

@ -324,21 +324,18 @@ export const TrainingSessionEncounter: MysteryEncounter =
const abilityIndex = encounter.misc.abilityIndex; const abilityIndex = encounter.misc.abilityIndex;
if (!!playerPokemon.getFusionSpeciesForm()) { if (!!playerPokemon.getFusionSpeciesForm()) {
playerPokemon.fusionAbilityIndex = abilityIndex; playerPokemon.fusionAbilityIndex = abilityIndex;
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies!.speciesId)) { if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies.speciesId)) {
scene.gameData.starterData[playerPokemon.fusionSpecies!.speciesId] scene.gameData.starterData[playerPokemon.fusionSpecies.speciesId]
.abilityAttr |= .abilityAttr |=
abilityIndex !== 1 || playerPokemon.fusionSpecies!.ability2 abilityIndex !== 1 || playerPokemon.fusionSpecies.ability2
? Math.pow(2, playerPokemon.fusionAbilityIndex) ? Math.pow(2, playerPokemon.fusionAbilityIndex)
: AbilityAttr.ABILITY_HIDDEN; : AbilityAttr.ABILITY_HIDDEN;
} }
} else { } else {
playerPokemon.abilityIndex = abilityIndex; playerPokemon.abilityIndex = abilityIndex;
if ( if (speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)) {
speciesStarters.hasOwnProperty(playerPokemon.species.speciesId) scene.gameData.starterData[playerPokemon.species.speciesId]
) { .abilityAttr |=
scene.gameData.starterData[
playerPokemon.species.speciesId
].abilityAttr |=
abilityIndex !== 1 || playerPokemon.species.ability2 abilityIndex !== 1 || playerPokemon.species.ability2
? Math.pow(2, playerPokemon.abilityIndex) ? Math.pow(2, playerPokemon.abilityIndex)
: AbilityAttr.ABILITY_HIDDEN; : AbilityAttr.ABILITY_HIDDEN;

View File

@ -16,7 +16,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/field/pokemon";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
/** the i18n namespace for this encounter */ /** the i18n namespace for this encounter */
@ -24,6 +23,9 @@ const namespace = "mysteryEncounter:trashToTreasure";
const SOUND_EFFECT_WAIT_TIME = 700; const SOUND_EFFECT_WAIT_TIME = 700;
// Items will cost 2.5x as much for remainder of the run
const SHOP_ITEM_COST_MULTIPLIER = 2.5;
/** /**
* Trash to Treasure encounter. * Trash to Treasure encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809} * @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809}
@ -79,6 +81,8 @@ export const TrashToTreasureEncounter: MysteryEncounter =
scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav"); scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav"); scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString());
return true; return true;
}) })
.withOption( .withOption(
@ -102,8 +106,14 @@ export const TrashToTreasureEncounter: MysteryEncounter =
transitionMysteryEncounterIntroVisuals(scene); transitionMysteryEncounterIntroVisuals(scene);
await tryApplyDigRewardItems(scene); await tryApplyDigRewardItems(scene);
// Give the player the Black Sludge curse const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [SHOP_ITEM_COST_MULTIPLIER]);
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE)); const modifier = blackSludge?.newModifier();
if (modifier) {
await scene.addModifier(modifier, false, false, false, true);
scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true);
}
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
}) })
.build() .build()
@ -180,7 +190,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
} }
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), null, undefined, true); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2x " + leftovers.name }), null, undefined, true);
// First Shell bell // First Shell bell
for (const pokemon of party) { for (const pokemon of party) {
@ -207,7 +217,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
} }
scene.playSound("item_fanfare"); scene.playSound("item_fanfare");
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), null, undefined, true); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2x " + shellBell.name }), null, undefined, true);
} }
async function doGarbageDig(scene: BattleScene) { async function doGarbageDig(scene: BattleScene) {

View File

@ -12,7 +12,6 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { TrainerSlot } from "#app/data/trainer-config"; import { TrainerSlot } from "#app/data/trainer-config";
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { speciesEggMoves } from "#app/data/egg-moves";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#app/battle";
@ -53,11 +52,10 @@ export const UncommonBreedEncounter: MysteryEncounter =
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2; const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
const species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true); const species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true); const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true);
const speciesRootForm = pokemon.species.getRootSpeciesId();
// Pokemon will always have one of its egg moves in its moveset // Pokemon will always have one of its egg moves in its moveset
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) { const eggMoves = pokemon.getEggMoves();
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm]; if (eggMoves) {
const eggMoveIndex = randSeedInt(4); const eggMoveIndex = randSeedInt(4);
const randomEggMove: Moves = eggMoves[eggMoveIndex]; const randomEggMove: Moves = eggMoves[eggMoveIndex];
encounter.misc = { encounter.misc = {
@ -72,6 +70,11 @@ export const UncommonBreedEncounter: MysteryEncounter =
encounter.misc.pokemon = pokemon; encounter.misc.pokemon = pokemon;
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
[Stat.DEF, Stat.SPDEF, Stat.SPD] :
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
const config: EnemyPartyConfig = { const config: EnemyPartyConfig = {
pokemonConfigs: [{ pokemonConfigs: [{
level: level, level: level,
@ -81,7 +84,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
mysteryEncounterBattleEffects: (pokemon: Pokemon) => { mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`); queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1)); pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
} }
}], }],
}; };
@ -193,20 +196,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
const pokemon = encounter.misc.pokemon; const pokemon = encounter.misc.pokemon;
// Give 1 additional egg move // Give 1 additional egg move
const previousEggMove = encounter.misc.eggMove; givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
const speciesRootForm = pokemon.species.getRootSpeciesId();
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
while (randomEggMove === previousEggMove) {
randomEggMove = eggMoves[randSeedInt(4)];
}
if (pokemon.moveset.length < 4) {
pokemon.moveset.push(new PokemonMove(randomEggMove));
} else {
pokemon.moveset[1] = new PokemonMove(randomEggMove);
}
}
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false); await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
setEncounterRewards(scene, { fillRemaining: true }); setEncounterRewards(scene, { fillRemaining: true });
@ -235,20 +225,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
const pokemon = encounter.misc.pokemon; const pokemon = encounter.misc.pokemon;
// Give 1 additional egg move // Give 1 additional egg move
const previousEggMove = encounter.misc.eggMove; givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
const speciesRootForm = pokemon.species.getRootSpeciesId();
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
while (randomEggMove === previousEggMove) {
randomEggMove = eggMoves[randSeedInt(4)];
}
if (pokemon.moveset.length < 4) {
pokemon.moveset.push(new PokemonMove(randomEggMove));
} else {
pokemon.moveset[1] = new PokemonMove(randomEggMove);
}
}
// Roll IVs a second time // Roll IVs a second time
pokemon.ivs = pokemon.ivs.map(iv => { pokemon.ivs = pokemon.ivs.map(iv => {
@ -266,3 +243,18 @@ export const UncommonBreedEncounter: MysteryEncounter =
.build() .build()
) )
.build(); .build();
function givePokemonExtraEggMove(pokemon: EnemyPokemon, previousEggMove: Moves) {
const eggMoves = pokemon.getEggMoves();
if (eggMoves) {
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
while (randomEggMove === previousEggMove) {
randomEggMove = eggMoves[randSeedInt(4)];
}
if (pokemon.moveset.length < 4) {
pokemon.moveset.push(new PokemonMove(randomEggMove));
} else {
pokemon.moveset[1] = new PokemonMove(randomEggMove);
}
}
}

View File

@ -12,7 +12,6 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { achvs } from "#app/system/achv"; 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 { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
@ -105,7 +104,7 @@ const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [40, 50];
export const WeirdDreamEncounter: MysteryEncounter = export const WeirdDreamEncounter: MysteryEncounter =
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
.withEncounterTier(MysteryEncounterTier.ROGUE) .withEncounterTier(MysteryEncounterTier.ROGUE)
.withDisallowedChallenges(Challenges.SINGLE_TYPE) .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION)
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
.withIntroSpriteConfigs([ .withIntroSpriteConfigs([
{ {
@ -216,7 +215,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
pokemon.levelExp = 0; pokemon.levelExp = 0;
pokemon.calculateStats(); pokemon.calculateStats();
pokemon.updateInfo(); await pokemon.updateInfo();
} }
leaveEncounterWithoutBattle(scene, true); leaveEncounterWithoutBattle(scene, true);
@ -346,6 +345,9 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
// If the previous pokemon had pokerus, transfer to new pokemon // If the previous pokemon had pokerus, transfer to new pokemon
newPokemon.pokerus = previousPokemon.pokerus; newPokemon.pokerus = previousPokemon.pokerus;
// Transfer previous Pokemon's luck value
newPokemon.luck = previousPokemon.getLuck();
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock) // If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
newPokemon.ivs = newPokemon.ivs.map((iv, index) => { newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv; return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
@ -358,44 +360,15 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species // Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species
newPokemon.generateAndPopulateMoveset(); newPokemon.generateAndPopulateMoveset();
// Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move
// Try to find a favored STAB move const newPokemonGeneratedMoveset = newPokemon.moveset;
let favoredMove;
for (const move of newPokemon.moveset) {
// Needs to match first type, second type will be replaced
if (move?.getMove().type === newPokemon.getTypes()[0]) {
favoredMove = move;
break;
}
}
// If was unable to find a move, uses first move in moveset (typically a high power STAB move)
favoredMove = favoredMove ?? newPokemon.moveset[0];
newPokemon.moveset = previousPokemon.moveset; newPokemon.moveset = previousPokemon.moveset;
let eggMoveIndex: null | number = null;
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
const eggMoves = speciesEggMoves[speciesRootForm];
const randomEggMoveIndex = randSeedInt(4);
const randomEggMove = eggMoves[randomEggMoveIndex];
if (newPokemon.moveset.length < 4) {
newPokemon.moveset.push(new PokemonMove(randomEggMove));
} else {
eggMoveIndex = randSeedInt(4);
newPokemon.moveset[eggMoveIndex] = new PokemonMove(randomEggMove);
}
// For pokemon that the player owns (including ones just caught), unlock the egg move
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
}
}
if (favoredMove) {
let favoredMoveIndex = randSeedInt(4);
while (favoredMoveIndex === eggMoveIndex) {
favoredMoveIndex = randSeedInt(4);
}
newPokemon.moveset[favoredMoveIndex] = favoredMove; const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm);
}
// Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset)
addFavoredMoveToNewPokemonMoveset(scene, newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex);
// Randomize the second type of the pokemon // Randomize the second type of the pokemon
// If the pokemon does not normally have a second type, it will gain 1 // If the pokemon does not normally have a second type, it will gain 1
@ -412,7 +385,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
for (const item of transformation.heldItems) { for (const item of transformation.heldItems) {
item.pokemonId = newPokemon.id; item.pokemonId = newPokemon.id;
scene.addModifier(item, false, false, false, true); await scene.addModifier(item, false, false, false, true);
} }
// Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef // Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef
@ -423,11 +396,12 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK); stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
// Def or SpDef // Def or SpDef
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF); stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
// const mod = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().newModifier(newPokemon, 20, stats); const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU()
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(scene.getParty(), [20, stats]); .generateType(scene.getParty(), [20, stats])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
const modifier = modType?.newModifier(newPokemon); const modifier = modType?.newModifier(newPokemon);
if (modifier) { if (modifier) {
scene.addModifier(modifier); await scene.addModifier(modifier, false, false, false, true);
} }
} }
@ -435,13 +409,15 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
newPokemon.passive = previousPokemon.passive; newPokemon.passive = previousPokemon.passive;
newPokemon.calculateStats(); newPokemon.calculateStats();
newPokemon.initBattleInfo(); await newPokemon.updateInfo();
} }
// One random pokemon will get its passive unlocked // One random pokemon will get its passive unlocked
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive); const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
if (passiveDisabledPokemon?.length > 0) { if (passiveDisabledPokemon?.length > 0) {
passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)].passive = true; const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
enablePassiveMon.passive = true;
await enablePassiveMon.updateInfo(true);
} }
// If at least one new starter was unlocked, play 1 fanfare // If at least one new starter was unlocked, play 1 fanfare
@ -471,7 +447,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
if (validSpecies?.length > 20) { if (validSpecies?.length > 20) {
validSpecies = randSeedShuffle(validSpecies); validSpecies = randSeedShuffle(validSpecies);
newSpecies = validSpecies.pop(); newSpecies = validSpecies.pop();
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) { while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) {
newSpecies = validSpecies.pop(); newSpecies = validSpecies.pop();
} }
} else { } else {
@ -481,7 +457,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
} }
} }
return newSpecies!; return newSpecies;
} }
function doShowDreamBackground(scene: BattleScene) { function doShowDreamBackground(scene: BattleScene) {
@ -566,3 +542,83 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo
} }
}); });
} }
/**
* Returns index of the new egg move within the Pokemon's moveset (not the index of the move in `speciesEggMoves`)
* @param scene
* @param newPokemon
* @param speciesRootForm
*/
async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species): Promise<number | null> {
let eggMoveIndex: null | number = null;
const eggMoves = newPokemon.getEggMoves()?.slice(0);
if (eggMoves) {
const eggMoveIndices = [0, 1, 2, 3];
randSeedShuffle(eggMoveIndices);
let randomEggMoveIndex = eggMoveIndices.pop();
let randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex] : null;
let retries = 0;
while (retries < 3 && (!randomEggMove || newPokemon.moveset.some(m => m?.moveId === randomEggMove))) {
// If Pokemon already knows this move, roll for another egg move
randomEggMoveIndex = eggMoveIndices.pop();
randomEggMove = !isNullOrUndefined(randomEggMoveIndex) ? eggMoves[randomEggMoveIndex] : null;
retries++;
}
if (randomEggMove) {
if (!newPokemon.moveset.some(m => m?.moveId === randomEggMove)) {
if (newPokemon.moveset.length < 4) {
newPokemon.moveset.push(new PokemonMove(randomEggMove));
} else {
eggMoveIndex = randSeedInt(4);
newPokemon.moveset[eggMoveIndex] = new PokemonMove(randomEggMove);
}
}
// For pokemon that the player owns (including ones just caught), unlock the egg move
if (!isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) {
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
}
}
}
return eggMoveIndex;
}
/**
* Returns index of the new egg move within the Pokemon's moveset (not the index of the move in `speciesEggMoves`)
* @param scene
* @param newPokemon
* @param newPokemonGeneratedMoveset
* @param newEggMoveIndex
*/
function addFavoredMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, newPokemonGeneratedMoveset: (PokemonMove | null)[], newEggMoveIndex: number | null) {
let favoredMove: PokemonMove | null = null;
for (const move of newPokemonGeneratedMoveset) {
// Needs to match first type, second type will be replaced
if (move?.getMove().type === newPokemon.getTypes()[0] && !newPokemon.moveset.some(m => m?.moveId === move?.moveId)) {
favoredMove = move;
break;
}
}
// If was unable to find a favored move, uses first move in moveset that isn't already known (typically a high power STAB move)
// Otherwise, it gains no favored move
if (!favoredMove) {
for (const move of newPokemonGeneratedMoveset) {
// Needs to match first type, second type will be replaced
if (!newPokemon.moveset.some(m => m?.moveId === move?.moveId)) {
favoredMove = move;
break;
}
}
}
// Finally, assign favored move to random index that isn't the new egg move index
if (favoredMove) {
let favoredMoveIndex = randSeedInt(4);
while (newEggMoveIndex !== null && favoredMoveIndex === newEggMoveIndex) {
favoredMoveIndex = randSeedInt(4);
}
newPokemon.moveset[favoredMoveIndex] = favoredMove;
}
}

View File

@ -208,7 +208,7 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
return Object.assign(this, { requirements: this.requirements }); return Object.assign(this, { requirements: this.requirements });
} }
withSceneMoneyRequirement(requiredMoney?: number, scalingMultiplier?: number) { withSceneMoneyRequirement(requiredMoney: number, scalingMultiplier?: number) {
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier)); return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
} }

View File

@ -165,9 +165,9 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
} }
override meetsRequirement(scene: BattleScene): boolean { override meetsRequirement(scene: BattleScene): boolean {
if (!isNullOrUndefined(this.waveRange) && this.waveRange?.[0] <= this.waveRange?.[1]) { if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) {
const waveIndex = scene.currentBattle.waveIndex; const waveIndex = scene.currentBattle.waveIndex;
if (waveIndex >= 0 && (this.waveRange?.[0] >= 0 && this.waveRange?.[0] > waveIndex) || (this.waveRange?.[1] >= 0 && this.waveRange?.[1] < waveIndex)) { if (waveIndex >= 0 && (this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) || (this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)) {
return false; return false;
} }
} }
@ -251,7 +251,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
const currentWeather = scene.arena.weather?.weatherType; const currentWeather = scene.arena.weather?.weatherType;
let token = ""; let token = "";
if (!isNullOrUndefined(currentWeather)) { if (!isNullOrUndefined(currentWeather)) {
token = WeatherType[currentWeather!].replace("_", " ").toLocaleLowerCase(); token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase();
} }
return ["weather", token]; return ["weather", token];
} }
@ -274,9 +274,9 @@ export class PartySizeRequirement extends EncounterSceneRequirement {
} }
override meetsRequirement(scene: BattleScene): boolean { override meetsRequirement(scene: BattleScene): boolean {
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) { if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) {
const partySize = this.excludeDisallowedPokemon ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length; const partySize = this.excludeDisallowedPokemon ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
if (partySize >= 0 && (this.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) { if (partySize >= 0 && (this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) || (this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)) {
return false; return false;
} }
} }
@ -326,7 +326,7 @@ export class MoneyRequirement extends EncounterSceneRequirement {
requiredMoney: number; // Static value requiredMoney: number; // Static value
scalingMultiplier: number; // Calculates required money based off wave index scalingMultiplier: number; // Calculates required money based off wave index
constructor(requiredMoney?: number, scalingMultiplier?: number) { constructor(requiredMoney: number, scalingMultiplier?: number) {
super(); super();
this.requiredMoney = requiredMoney ?? 0; this.requiredMoney = requiredMoney ?? 0;
this.scalingMultiplier = scalingMultiplier ?? 0; this.scalingMultiplier = scalingMultiplier ?? 0;
@ -418,8 +418,8 @@ export class NatureRequirement extends EncounterPokemonRequirement {
} }
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon!.nature)) { if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) {
return ["nature", Nature[pokemon!.nature]]; return ["nature", Nature[pokemon.nature]];
} }
return ["nature", ""]; return ["nature", ""];
} }
@ -620,7 +620,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
return this.requiredStatusEffect.some((statusEffect) => { return this.requiredStatusEffect.some((statusEffect) => {
if (statusEffect === StatusEffect.NONE) { if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status // StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect; return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect;
} else { } else {
return pokemon.status?.effect === statusEffect; return pokemon.status?.effect === statusEffect;
} }
@ -628,12 +628,11 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
}); });
} else { } else {
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects // for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects
// return partyPokemon.filter((pokemon) => this.requiredStatusEffect.filter((statusEffect) => pokemon.status?.effect === statusEffect).length === 0);
return partyPokemon.filter((pokemon) => { return partyPokemon.filter((pokemon) => {
return !this.requiredStatusEffect.some((statusEffect) => { return !this.requiredStatusEffect.some((statusEffect) => {
if (statusEffect === StatusEffect.NONE) { if (statusEffect === StatusEffect.NONE) {
// StatusEffect.NONE also checks for null or undefined status // StatusEffect.NONE also checks for null or undefined status
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect; return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect;
} else { } else {
return pokemon.status?.effect === statusEffect; return pokemon.status?.effect === statusEffect;
} }
@ -645,7 +644,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
const reqStatus = this.requiredStatusEffect.filter((a) => { const reqStatus = this.requiredStatusEffect.filter((a) => {
if (a === StatusEffect.NONE) { if (a === StatusEffect.NONE) {
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon!.status!.effect) || pokemon!.status!.effect === a; return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a;
} }
return pokemon!.status?.effect === a; return pokemon!.status?.effect === a;
}); });
@ -988,8 +987,9 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
} }
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
if (!isNullOrUndefined(pokemon?.getHpRatio())) { const hpRatio = pokemon?.getHpRatio();
return ["healthRatio", Math.floor(pokemon!.getHpRatio() * 100).toString() + "%"]; if (!isNullOrUndefined(hpRatio)) {
return ["healthRatio", Math.floor(hpRatio * 100).toString() + "%"];
} }
return ["healthRatio", ""]; return ["healthRatio", ""];
} }

View File

@ -32,6 +32,7 @@ import { FunAndGamesEncounter } from "#app/data/mystery-encounters/encounters/fu
import { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-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 { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter"; import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
import { getBiomeName } from "#app/data/biomes";
/** /**
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT * Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
@ -362,11 +363,16 @@ export function initMysteryEncounters() {
}); });
// Add ANY biome encounters to biome map // Add ANY biome encounters to biome map
mysteryEncountersByBiome.forEach(biomeEncounters => { let encounterBiomeTableLog = "";
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
anyBiomeEncounters.forEach(encounter => { anyBiomeEncounters.forEach(encounter => {
if (!biomeEncounters.includes(encounter)) { if (!biomeEncounters.includes(encounter)) {
biomeEncounters.push(encounter); biomeEncounters.push(encounter);
} }
}); });
encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`;
}); });
console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog);
} }

View File

@ -17,7 +17,7 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima
return null; return null;
} }
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString!); let textString: string | null = getTextWithDialogueTokens(scene, keyOrString);
// Can only color the text if a Primary Style is defined // Can only color the text if a Primary Style is defined
// primaryStyle is applied to all text that does not have its own specified style // primaryStyle is applied to all text that does not have its own specified style

View File

@ -135,7 +135,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
scene.currentBattle.trainer.destroy(); scene.currentBattle.trainer.destroy();
} }
trainerConfig = partyConfig?.trainerConfig ? partyConfig?.trainerConfig : trainerConfigs[trainerType!]; trainerConfig = partyTrainerConfig ? partyTrainerConfig : trainerConfigs[trainerType!];
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle); const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
doubleBattle = doubleTrainer; doubleBattle = doubleTrainer;
@ -166,7 +166,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// This can be amplified or counteracted by setting levelAdditiveModifier in config // 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. // levelAdditiveModifier value of 0.5 will halve the modifier scaling, 2 will double it, etc.
// Leaving null/undefined will disable level scaling // Leaving null/undefined will disable level scaling
const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier! : 0; const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier : 0;
const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0); const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0);
battle.enemyLevels = battle.enemyLevels.map(level => level + additive); battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
@ -226,7 +226,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// Set form // Set form
if (!isNullOrUndefined(config.nickname)) { if (!isNullOrUndefined(config.nickname)) {
enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname!))); enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname)));
} }
// Generate new id, reset status and HP in case using data source // Generate new id, reset status and HP in case using data source
@ -236,29 +236,29 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// Set form // Set form
if (!isNullOrUndefined(config.formIndex)) { if (!isNullOrUndefined(config.formIndex)) {
enemyPokemon.formIndex = config.formIndex!; enemyPokemon.formIndex = config.formIndex;
} }
// Set shiny // Set shiny
if (!isNullOrUndefined(config.shiny)) { if (!isNullOrUndefined(config.shiny)) {
enemyPokemon.shiny = config.shiny!; enemyPokemon.shiny = config.shiny;
} }
// Set Variant // Set Variant
if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) { if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) {
enemyPokemon.variant = config.variant!; enemyPokemon.variant = config.variant;
} }
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) { if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) {
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData!; enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData;
} }
// Set Boss // Set Boss
if (config.isBoss) { if (config.isBoss) {
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true); let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true);
if (!isNullOrUndefined(config.bossSegmentModifier)) { if (!isNullOrUndefined(config.bossSegmentModifier)) {
segments += config.bossSegmentModifier!; segments += config.bossSegmentModifier;
} }
enemyPokemon.setBoss(true, segments); enemyPokemon.setBoss(true, segments);
} }
@ -294,18 +294,18 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
// Set ability // Set ability
if (!isNullOrUndefined(config.abilityIndex)) { if (!isNullOrUndefined(config.abilityIndex)) {
enemyPokemon.abilityIndex = config.abilityIndex!; enemyPokemon.abilityIndex = config.abilityIndex;
} }
// Set gender // Set gender
if (!isNullOrUndefined(config.gender)) { if (!isNullOrUndefined(config.gender)) {
enemyPokemon.gender = config.gender!; enemyPokemon.gender = config.gender!;
enemyPokemon.summonData.gender = config.gender!; enemyPokemon.summonData.gender = config.gender;
} }
// Set AI type // Set AI type
if (!isNullOrUndefined(config.aiType)) { if (!isNullOrUndefined(config.aiType)) {
enemyPokemon.aiType = config.aiType!; enemyPokemon.aiType = config.aiType;
} }
// Set moves // Set moves

View File

@ -218,7 +218,7 @@ export function getRandomSpeciesByStarterTier(starterTiers: number | [number, nu
.map(s => [getPokemonSpecies(s[0]), s[1]]); .map(s => [getPokemonSpecies(s[0]), s[1]]);
if (types && types.length > 0) { if (types && types.length > 0) {
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2!))); filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2)));
} }
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are // If no filtered mons exist at specified starter tiers, will expand starter search range until there are
@ -311,7 +311,9 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h
* @param value * @param value
*/ */
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) { export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE().generateType(pokemon.scene.getParty(), [value]); const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
.generateType(pokemon.scene.getParty(), [value])
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
const modifier = modType?.newModifier(pokemon); const modifier = modType?.newModifier(pokemon);
if (modifier) { if (modifier) {
await pokemon.scene.addModifier(modifier, false, false, false, true); await pokemon.scene.addModifier(modifier, false, false, false, true);
@ -780,8 +782,7 @@ export function getGoldenBugNetSpecies(): PokemonSpecies {
*/ */
export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) { export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) {
const currentBattle = scene.currentBattle; const currentBattle = scene.currentBattle;
// Default to use the first generated level from enemyLevels, or generate a new one if it DNE const baseLevel = currentBattle.getLevelForWave();
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 // Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier
return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0); return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0);

View File

@ -44,6 +44,10 @@ function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): stri
} }
export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined, pokemonNameWithAffix: string, sourceText?: string): string { export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined, pokemonNameWithAffix: string, sourceText?: string): string {
if (statusEffect === StatusEffect.NONE) {
return "";
}
if (!sourceText) { if (!sourceText) {
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtain`as ParseKeys; const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.obtain`as ParseKeys;
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix }); return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
@ -53,21 +57,33 @@ export function getStatusEffectObtainText(statusEffect: StatusEffect | undefined
} }
export function getStatusEffectActivationText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string { export function getStatusEffectActivationText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string {
if (statusEffect === StatusEffect.NONE) {
return "";
}
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.activation` as ParseKeys; const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.activation` as ParseKeys;
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix }); return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
} }
export function getStatusEffectOverlapText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string { export function getStatusEffectOverlapText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string {
if (statusEffect === StatusEffect.NONE) {
return "";
}
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.overlap` as ParseKeys; const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.overlap` as ParseKeys;
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix }); return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
} }
export function getStatusEffectHealText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string { export function getStatusEffectHealText(statusEffect: StatusEffect, pokemonNameWithAffix: string): string {
if (statusEffect === StatusEffect.NONE) {
return "";
}
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.heal` as ParseKeys; const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.heal` as ParseKeys;
return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix }); return i18next.t(i18nKey, { pokemonNameWithAffix: pokemonNameWithAffix });
} }
export function getStatusEffectDescriptor(statusEffect: StatusEffect): string { export function getStatusEffectDescriptor(statusEffect: StatusEffect): string {
if (statusEffect === StatusEffect.NONE) {
return "";
}
const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.description` as ParseKeys; const i18nKey = `${getStatusEffectMessageKey(statusEffect)}.description` as ParseKeys;
return i18next.t(i18nKey); return i18next.t(i18nKey);
} }

View File

@ -86,7 +86,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
}; };
if (!isNullOrUndefined(result.species)) { if (!isNullOrUndefined(result.species)) {
const keys = getSpriteKeysFromSpecies(result.species!); const keys = getSpriteKeysFromSpecies(result.species);
result.spriteKey = keys.spriteKey; result.spriteKey = keys.spriteKey;
result.fileRoot = keys.fileRoot; result.fileRoot = keys.fileRoot;
result.isPokemon = true; result.isPokemon = true;

View File

@ -1258,8 +1258,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
} }
if (this.isFusion()) { if (this.isFusion()) {
if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData!.ability !== -1) { if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) {
return allAbilities[this.fusionMysteryEncounterPokemonData!.ability]; return allAbilities[this.fusionMysteryEncounterPokemonData.ability];
} else { } else {
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
} }
@ -1799,8 +1799,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* *
* @returns list of egg moves * @returns list of egg moves
*/ */
getEggMoves() : Moves[] { getEggMoves() : Moves[] | undefined {
return speciesEggMoves[this.species.speciesId]; return speciesEggMoves[this.getSpeciesForm().getRootSpeciesId(true)];
} }
setMove(moveIndex: integer, moveId: Moves): void { setMove(moveIndex: integer, moveId: Moves): void {

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "None", "name": "None"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "Gift", "name": "Gift",

View File

@ -74,5 +74,5 @@
"substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!", "substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!",
"substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!", "substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!",
"substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!", "substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!",
"autotomizeOnAdd": "{{pokemonNameWIthAffix}} became nimble!" "autotomizeOnAdd": "{{pokemonNameWithAffix}} became nimble!"
} }

View File

@ -31,7 +31,7 @@
"option": { "option": {
"1": { "1": {
"label": "Accept the Challenge", "label": "Accept the Challenge",
"tooltip": "(-) Tough Battle\n(+) Gain a @[TOOLTIP_TITLE]{Very Rare Egg}" "tooltip": "(-) Extremely Tough Battle\n(+) Gain a @[TOOLTIP_TITLE]{Very Rare Egg}"
}, },
"2": { "2": {
"label": "Refuse the Challenge", "label": "Refuse the Challenge",

View File

@ -8,7 +8,7 @@
"option": { "option": {
"1": { "1": {
"label": "Offer to Battle", "label": "Offer to Battle",
"tooltip": "(-) Challenging Battle\n(+) Teach a Pokémon a Bug Type Move", "tooltip": "(-) Challenging Battle\n(+) Teach any Pokémon a Bug Type Move",
"selected": "A challenge, eh?\nMy bugs are more than ready for you!" "selected": "A challenge, eh?\nMy bugs are more than ready for you!"
}, },
"2": { "2": {

View File

@ -8,23 +8,23 @@
"option": { "option": {
"1": { "1": {
"label": "Battle the Clown", "label": "Battle the Clown",
"tooltip": "(-) Strange Battle\n(?) Affects Pokémon Abilities", "tooltip": "(-) Strange Battle\n(?) Affects One Pokémon's Ability",
"selected": "Your pitiful Pokémon are poised for a pathetic performance!", "selected": "Your pitiful Pokémon are poised for a pathetic performance!",
"apply_ability_dialogue": "A sensational showcase!\nYour savvy suits a sensational skill as spoils!", "apply_ability_dialogue": "A sensational showcase!\nYour savvy suits a special skill as spoils!",
"apply_ability_message": "The clown is offering to permanently Skill Swap one of your Pokémon's ability to {{ability}}!", "apply_ability_message": "The clown is offering to permanently Skill Swap one of your Pokémon's ability to {{ability}}!",
"ability_prompt": "Would you like to permanently teach a Pokémon the {{ability}} ability?", "ability_prompt": "Would you like to permanently teach a Pokémon the {{ability}} ability?",
"ability_gained": "@s{level_up_fanfare}{{chosenPokemon}} gained the {{ability}} ability!" "ability_gained": "@s{level_up_fanfare}{{chosenPokemon}} gained the {{ability}} ability!"
}, },
"2": { "2": {
"label": "Remain Unprovoked", "label": "Remain Unprovoked",
"tooltip": "(-) Upsets the Clown\n(?) Affects Pokémon Items", "tooltip": "(?) Affects One Pokémon's Items",
"selected": "Dismal dodger, you deny a delightful duel?\nFeel my fury!", "selected": "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
"selected_2": "The clown's {{blacephalonName}} uses Trick!\nAll of your {{switchPokemon}}'s items were randomly swapped!", "selected_2": "The clown's {{blacephalonName}} uses Trick!\nAll of your {{switchPokemon}}'s items were randomly swapped!",
"selected_3": "Flustered fool, fall for my flawless deception!" "selected_3": "Flustered fool, fall for my flawless deception!"
}, },
"3": { "3": {
"label": "Return the Insults", "label": "Return the Insults",
"tooltip": "(-) Upsets the Clown\n(?) Affects Pokémon Types", "tooltip": "(?) Affects Your Pokémons' Types",
"selected": "Dismal dodger, you deny a delightful duel?\nFeel my fury!", "selected": "Dismal dodger, you deny a delightful duel?\nFeel my fury!",
"selected_2": "The clown's {{blacephalonName}} uses a strange move!\nAll of your team's types were randomly swapped!", "selected_2": "The clown's {{blacephalonName}} uses a strange move!\nAll of your team's types were randomly swapped!",
"selected_3": "Flustered fool, fall for my flawless deception!" "selected_3": "Flustered fool, fall for my flawless deception!"

View File

@ -1,7 +1,7 @@
{ {
"intro": "An {{oricorioName}} dances sadly alone, without a partner.", "intro": "An {{oricorioName}} dances sadly alone, without a partner.",
"title": "Dancing Lessons", "title": "Dancing Lessons",
"description": "The {{oricorioName}} doesn't seem aggressive, if anything it seems sad.\n\nMaybe it just wants someone to dance with...", "description": "The {{oricorioName}} doesn't seem aggressive, if anything it seems despondent.\n\nPerhaps it just wants someone to dance with...",
"query": "What will you do?", "query": "What will you do?",
"option": { "option": {
"1": { "1": {
@ -12,7 +12,7 @@
}, },
"2": { "2": {
"label": "Learn Its Dance", "label": "Learn Its Dance",
"tooltip": "(+) Teach a Pokémon Revelation Dance", "tooltip": "(+) Teach any Pokémon Revelation Dance",
"selected": "You watch the {{oricorioName}} closely as it performs its dance...$@s{level_up_fanfare}Your {{selectedPokemon}} learned from the {{oricorioName}}!" "selected": "You watch the {{oricorioName}} closely as it performs its dance...$@s{level_up_fanfare}Your {{selectedPokemon}} learned from the {{oricorioName}}!"
}, },
"3": { "3": {

View File

@ -1,7 +1,7 @@
{ {
"intro": "A pack of {{delibirdName}} have appeared!", "intro": "A flock of {{delibirdName}} have appeared!",
"title": "Delibir-dy", "title": "Delibir-dy",
"description": "The {{delibirdName}}s are looking at you expectantly, as if they want something. Perhaps giving them an item or some money would satisfy them?", "description": "The {{delibirdName}}s are looking at you expectantly, as if they want something. Perhaps giving them an item or some money would satisfy them?",
"query": "What will you give them?", "query": "What will you give them?",
@ -25,5 +25,5 @@
"selected": "You toss the {{chosenItem}} to the {{delibirdName}}s,\nwho chatter amongst themselves excitedly.$They turn back to you and happily give you a present!" "selected": "You toss the {{chosenItem}} to the {{delibirdName}}s,\nwho chatter amongst themselves excitedly.$They turn back to you and happily give you a present!"
} }
}, },
"outro": "The {{delibirdName}} pack happily waddles off into the distance.$What a curious little exchange!" "outro": "The {{delibirdName}} flock happily waddles off into the distance.$What a curious little exchange!"
} }

View File

@ -11,13 +11,13 @@
}, },
"2": { "2": {
"label": "Wonder Trade", "label": "Wonder Trade",
"tooltip": "(+) Send one of your Pokémon to the GTS and get a random Pokémon in return" "tooltip": "(+) Send one of your Pokémon to the GTS and get a random special Pokémon in return"
}, },
"3": { "3": {
"label": "Trade an Item", "label": "Trade an Item",
"trade_options_prompt": "Select an item to send.", "trade_options_prompt": "Select an item to send.",
"invalid_selection": "This Pokémon doesn't have legal items to trade.", "invalid_selection": "This Pokémon doesn't have legal items to trade.",
"tooltip": "(+) Send one of your Items to the GTS and get a random new Item" "tooltip": "(+) Send one of your Items to the GTS and get a random improved Item"
}, },
"4": { "4": {
"label": "Leave", "label": "Leave",

View File

@ -6,7 +6,7 @@
"option": { "option": {
"1": { "1": {
"label": "Open It", "label": "Open It",
"tooltip": "@[SUMMARY_BLUE]{(35%) Something terrible}\n@[SUMMARY_GREEN]{(40%) Okay Rewards}\n@[SUMMARY_GREEN]{(20%) Good Rewards}\n@[SUMMARY_GREEN]{(4%) Great Rewards}\n@[SUMMARY_GREEN]{(1%) Amazing Rewards}", "tooltip": "@[SUMMARY_BLUE]{({{trapPercent}}%) Something terrible}\n@[SUMMARY_GREEN]{({{commonPercent}}%) Okay Rewards}\n@[SUMMARY_GREEN]{({{ultraPercent}}%) Good Rewards}\n@[SUMMARY_GREEN]{({{roguePercent}}%) Great Rewards}\n@[SUMMARY_GREEN]{({{masterPercent}}%) Amazing Rewards}",
"selected": "You open the chest to find...", "selected": "You open the chest to find...",
"normal": "Just some normal tools and items.", "normal": "Just some normal tools and items.",
"good": "Some pretty nice tools and items.", "good": "Some pretty nice tools and items.",

View File

@ -1,7 +1,7 @@
{ {
"intro": "It's a safari zone!", "intro": "It's a safari zone!",
"title": "The Safari Zone", "title": "The Safari Zone",
"description": "There are all kinds of rare and special Pokémon that can be found here!\nIf you choose to enter, you'll have a time limit of 3 wild encounters where you can try to catch these special Pokémon.\n\nBeware, though. These Pokémon may flee before you're able to catch them!", "description": "There are all kinds of rare and special Pokémon that can be found here!\nIf you choose to enter, you'll have a time limit of @[TOOLTIP_TITLE]{{{numEncounters}} wild encounters} where you can try to catch these special Pokémon.\n\nBeware, though. These Pokémon may flee before you're able to catch them!",
"query": "Would you like to enter?", "query": "Would you like to enter?",
"option": { "option": {
"1": { "1": {

View File

@ -25,7 +25,7 @@
"outro": "Look how happy your {{chosenPokemon}} is now!$Here, you can have these as well.", "outro": "Look how happy your {{chosenPokemon}} is now!$Here, you can have these as well.",
"outro_failed": "How disappointing...$It looks like you still have a long way\nto go to earn your Pokémon's trust!", "outro_failed": "How disappointing...$It looks like you still have a long way\nto go to earn your Pokémon's trust!",
"gained_eggs": "@s{item_fanfare}You received {{numEggs}}!", "gained_eggs": "@s{item_fanfare}You received {{numEggs}}!",
"eggs_tooltip": "\n(+) Earn {{eggs}}", "eggs_tooltip": "\n(+) Earn @[TOOLTIP_TITLE]{{{eggs}}}",
"numEggs_one": "{{count}} {{rarity}} Egg", "numEggs_one": "{{count}} {{rarity}} Egg",
"numEggs_other": "{{count}} {{rarity}} Eggs" "numEggs_other": "{{count}} {{rarity}} Eggs"
} }

View File

@ -3,8 +3,8 @@
"speaker": "Gentleman", "speaker": "Gentleman",
"intro_dialogue": "Hello there! Have I got a deal just for YOU!", "intro_dialogue": "Hello there! Have I got a deal just for YOU!",
"title": "The Pokémon Salesman", "title": "The Pokémon Salesman",
"description": "\"This {{purchasePokemon}} is extremely unique and carries an ability not normally found in its species! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"", "description": "\"This {{purchasePokemon}} is extremely unique and @[TOOLTIP_TITLE]{carries an ability not normally found in its species}! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"",
"description_shiny": "\"This {{purchasePokemon}} is extremely unique and has a pigment not normally found in its species! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"", "description_shiny": "\"This {{purchasePokemon}} is extremely unique and @[TOOLTIP_TITLE]{has a pigment not normally found in its species}! I'll let you have this swell {{purchasePokemon}} for just {{price, money}}!\"\n\n\"What do you say?\"",
"query": "What will you do?", "query": "What will you do?",
"option": { "option": {
"1": { "1": {

View File

@ -1,7 +1,7 @@
{ {
"intro": "It's a massive {{shuckleName}} and what appears\nto be a large stash of... juice?", "intro": "It's a massive {{shuckleName}} and what appears\nto be a large stash of... juice?",
"title": "The Strong Stuff", "title": "The Strong Stuff",
"description": "The {{shuckleName}} that blocks your path looks incredibly strong. Meanwhile, the juice next to it is emanating power of some kind.\n\nThe {{shuckleName}} extends its feelers in your direction. It seems like it wants to do something...", "description": "The {{shuckleName}} that blocks your path looks formidable. Meanwhile, the juice next to it emanates power of some kind.\n\nThe {{shuckleName}} extends its feelers in your direction. It seems like it wants to do something...",
"query": "What will you do?", "query": "What will you do?",
"option": { "option": {
"1": { "1": {

View File

@ -3,12 +3,12 @@
"speaker": "The Winstrates", "speaker": "The Winstrates",
"intro_dialogue": "We're the Winstrates!$What do you say to taking on our family in a series of Pokémon battles?", "intro_dialogue": "We're the Winstrates!$What do you say to taking on our family in a series of Pokémon battles?",
"title": "The Winstrate Challenge", "title": "The Winstrate Challenge",
"description": "The Winstrates are a family of 5 trainers, and they want to battle! If you beat all of them back-to-back, they'll give you a grand prize. But can you handle the heat?", "description": "The Winstrates are a family of @[TOOLTIP_TITLE]{5 trainers}, and they want to battle! If you beat all of them back-to-back, they'll give you a grand prize. But can you handle the heat?",
"query": "What will you do?", "query": "What will you do?",
"option": { "option": {
"1": { "1": {
"label": "Accept the Challenge", "label": "Accept the Challenge",
"tooltip": "(-) Brutal Battle\n(+) Special Item Reward", "tooltip": "(-) Brutal Battle Against 5 Trainers\n(+) Special Item Reward",
"selected": "Let the challenge begin!" "selected": "Let the challenge begin!"
}, },
"2": { "2": {

View File

@ -1,24 +1,24 @@
{ {
"intro": "You've come across some\ntraining tools and supplies.", "intro": "You've come across some\ntraining tools and supplies.",
"title": "Training Session", "title": "Training Session",
"description": "These supplies look like they could be used to train a member of your party! There are a few ways you could train your Pokémon, by battling against it with the rest of your team.", "description": "These supplies look like they could be used to train a member of your party! There are a few ways you could train your Pokémon, by @[TOOLTIP_TITLE]{battling and defeating it with the rest of your team}.",
"query": "How should you train?", "query": "How should you train?",
"invalid_selection": "Pokémon must be healthy enough.", "invalid_selection": "Pokémon must be healthy enough.",
"option": { "option": {
"1": { "1": {
"label": "Light Training", "label": "Light Training",
"tooltip": "(-) Light Battle\n(+) Improve 2 Random IVs of Pokémon", "tooltip": "(-) Light Battle with Chosen Pokémon\n(+) Permanently Improve 2 Random IVs of Chosen Pokémon",
"finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its {{stat1}} and {{stat2}} IVs were improved!" "finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its {{stat1}} and {{stat2}} IVs were improved!"
}, },
"2": { "2": {
"label": "Moderate Training", "label": "Moderate Training",
"tooltip": "(-) Moderate Battle\n(+) Change Pokémon's Nature", "tooltip": "(-) Moderate Battle with Chosen Pokémon\n(+) Permanently Change Chosen Pokémon's Nature",
"select_prompt": "Select a new nature\nto train your Pokémon in.", "select_prompt": "Select a new nature\nto train your Pokémon in.",
"finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its nature was changed to {{nature}}!" "finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its nature was changed to {{nature}}!"
}, },
"3": { "3": {
"label": "Heavy Training", "label": "Heavy Training",
"tooltip": "(-) Harsh Battle\n(+) Change Pokémon's Ability", "tooltip": "(-) Harsh Battle with Chosen Pokémon\n(+) Permanently Change Chosen Pokémon's Ability",
"select_prompt": "Select a new ability\nto train your Pokémon in.", "select_prompt": "Select a new ability\nto train your Pokémon in.",
"finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its ability was changed to {{ability}}!" "finished": "{{selectedPokemon}} returns, feeling\nworn out but accomplished!$Its ability was changed to {{ability}}!"
}, },

View File

@ -6,7 +6,7 @@
"option": { "option": {
"1": { "1": {
"label": "Dig for Valuables", "label": "Dig for Valuables",
"tooltip": "(-) Items in Shops Cost 3x\n(+) Gain Amazing Items", "tooltip": "(-) Items in Shops Will Cost {{costMultiplier}}x\n(+) Gain Amazing Items",
"selected": "You wade through the garbage pile, becoming mired in filth.$There's no way any respectable shopkeeper would\nsell you items at the normal rate in your grimy state!$You'll have to pay extra for items now.$However, you found some incredible items in the garbage!" "selected": "You wade through the garbage pile, becoming mired in filth.$There's no way any respectable shopkeeper would\nsell you items at the normal rate in your grimy state!$You'll have to pay extra for items now.$However, you found some incredible items in the garbage!"
}, },
"2": { "2": {

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "None", "name": "None"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "Poison", "name": "Poison",

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "Ninguno", "name": "Ninguno"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "Envenenamiento", "name": "Envenenamiento",

View File

@ -21,9 +21,9 @@
"decline": "Oh, pas de combat ?\nPas grave !$Je vais quand même soigner ton équipe !" "decline": "Oh, pas de combat ?\nPas grave !$Je vais quand même soigner ton équipe !"
}, },
"riley": { "riley": {
"intro_dialogue": "Enchanté, je mappelle Armand.$Jai une proposition un peu étange\npour une personne de ton calibre.$Je poste deux Œufs de Pokémon avec moi\nmais jaimerais en donner un à quelquun dautre.$Si tu prouves en être digne,\nje te donnerai le plus rare !", "intro_dialogue": "Enchanté, je mappelle Armand.$Jai une proposition un peu étrange\npour une personne de ton calibre.$Je porte deux Œufs de Pokémon avec moi\nmais jaimerais en donner un à quelquun dautre.$Si tu prouves en être digne,\nje te donnerai le plus rare !",
"accept": "Ce regard…\nBattons-nous.", "accept": "Ce regard…\nBattons-nous.",
"decline": "Je comprends, ton équpie ma lair lessivée.$Laisse-moi taider." "decline": "Je comprends, ton équipe ma lair lessivée.$Laisse-moi taider."
}, },
"title": "Prouver sa valeur", "title": "Prouver sa valeur",
"description": "Cette personne semble déterminée à vous donner un Œuf, quimporte votre décision. Cependant, si vous parvenez à la battre, vous recevrez lŒuf le plus rare.", "description": "Cette personne semble déterminée à vous donner un Œuf, quimporte votre décision. Cependant, si vous parvenez à la battre, vous recevrez lŒuf le plus rare.",

View File

@ -18,7 +18,7 @@
}, },
"3": { "3": {
"label": "Lui laisser les Baies", "label": "Lui laisser les Baies",
"tooltip": "(-) Baies définitement perdues\n(?) {{greedentName}} vous apprécie", "tooltip": "(-) Baies définitivement perdues\n(?) {{greedentName}} vous apprécie",
"selected": "Le {{greedentName}} engloutit lintégralité\ndes Baies en un éclair !$Il vous regarde avec tendresse\nen se tapotant le ventre.$Peut-être pourriez-vous lui en donner encore\nplus pendant votre périple…$@s{level_up_fanfare}Le {{greedentName}} veut rejoindre votre équipe !" "selected": "Le {{greedentName}} engloutit lintégralité\ndes Baies en un éclair !$Il vous regarde avec tendresse\nen se tapotant le ventre.$Peut-être pourriez-vous lui en donner encore\nplus pendant votre périple…$@s{level_up_fanfare}Le {{greedentName}} veut rejoindre votre équipe !"
} }
} }

View File

@ -1,9 +1,9 @@
{ {
"intro": "Un jeune garçon aux airs très bougeois vous arrête.", "intro": "Un jeune garçon aux airs très bourgeois vous arrête.",
"speaker": "Richard", "speaker": "Richard",
"intro_dialogue": "Bonchour-haann !$Je ne puis carrément pas ignorer que votre\n{{strongestPokemon}} ma lair fa-bu-leux-han !$Jai toujours désiré posséder un tel Pokémon !$Je peux vous payer grassement,\nainsi que vous donner petite babiole-han !", "intro_dialogue": "Bonchour-haann !$Je ne puis carrément pas ignorer que votre\n{{strongestPokemon}} ma lair fa-bu-leux-han !$Jai toujours désiré posséder un tel Pokémon !$Je peux vous payer grassement,\nainsi que vous donner petite babiole-han !",
"title": "Laffaire du siècle", "title": "Laffaire du siècle",
"description": "Un fils à papa vous offre un @[TOOLTIP_TITLE]{Charme Chroma} et {{price, money}} en échange de votre {{strongestPokemon}} !\n\nÇa semble être une bonne affaire, mais pourrez vous supporter de priver votre équipe dun tel atout ?", "description": "Un fils à papa vous offre un @[TOOLTIP_TITLE]{Charme Chroma} et {{price, money}} en échange de votre {{strongestPokemon}} !\n\nÇa semble être une bonne affaire, mais pourriez-vous supporter de priver votre équipe dun tel atout ?",
"query": "Que voulez-vous faire ?", "query": "Que voulez-vous faire ?",
"option": { "option": {
"1": { "1": {

View File

@ -3,7 +3,7 @@
"title": "Baies à gogo", "title": "Baies à gogo",
"description": "Un Pokémon a lair de monter la garde de ce buisson à Baies. Vous pourriez aller frontalement au combat, mais il a lair costaud. Un Pokémon rapide pourrait peut-être en attraper quelques-unes sans se faire prendre ?", "description": "Un Pokémon a lair de monter la garde de ce buisson à Baies. Vous pourriez aller frontalement au combat, mais il a lair costaud. Un Pokémon rapide pourrait peut-être en attraper quelques-unes sans se faire prendre ?",
"query": "Que voulez-vous faire ?", "query": "Que voulez-vous faire ?",
"berries": "Des Baies !", "berries": "Baies ",
"option": { "option": {
"1": { "1": {
"label": "Laffronter", "label": "Laffronter",

View File

@ -13,7 +13,7 @@
}, },
"2": { "2": {
"label": "Montrer vos types Insecte", "label": "Montrer vos types Insecte",
"tooltip": "(+) Revecez un objet en cadeau", "tooltip": "(+) Recevez un objet en cadeau",
"disabled_tooltip": "Vous devez avoir au moins un Pokémon Insecte dans votre équipe pour choisir cette option.", "disabled_tooltip": "Vous devez avoir au moins un Pokémon Insecte dans votre équipe pour choisir cette option.",
"selected": "Vous lui montrez tous les types Insecte de votre équipe…", "selected": "Vous lui montrez tous les types Insecte de votre équipe…",
"selected_0_to_1": "Oh ? Tas qu{{numBugTypes}}…$Pourquoi je gaspille ma salive à te parler…", "selected_0_to_1": "Oh ? Tas qu{{numBugTypes}}…$Pourquoi je gaspille ma salive à te parler…",
@ -23,7 +23,7 @@
}, },
"3": { "3": {
"label": "Donner un objet Insecte", "label": "Donner un objet Insecte",
"tooltip": "(-) Lui donner lobjet {{requiredBugItems}}\n(+) Revecez un objet en cadeau", "tooltip": "(-) Lui donner lobjet {{requiredBugItems}}\n(+) Recevez un objet en cadeau",
"disabled_tooltip": "Vous avez besoin dun objet {{requiredBugItems}} pour choisir cette option.", "disabled_tooltip": "Vous avez besoin dun objet {{requiredBugItems}} pour choisir cette option.",
"select_prompt": "Choisissez un objet à donner.", "select_prompt": "Choisissez un objet à donner.",
"invalid_selection": "Ce Pokémon ne porte pas ce genre dobjet.", "invalid_selection": "Ce Pokémon ne porte pas ce genre dobjet.",

View File

@ -23,7 +23,7 @@
"selected_3": "Sombre imbécile, tombe dans mon piège !" "selected_3": "Sombre imbécile, tombe dans mon piège !"
}, },
"3": { "3": {
"label": "Retouner les insultes", "label": "Retourner les insultes",
"tooltip": "(-) Agace le Clown\n(?) Affecte les types de vos Pokémon", "tooltip": "(-) Agace le Clown\n(?) Affecte les types de vos Pokémon",
"selected": "Ça se défile lâchement dun duel exceptionnel ?\nDans ce cas, tâte à ma colère !", "selected": "Ça se défile lâchement dun duel exceptionnel ?\nDans ce cas, tâte à ma colère !",
"selected_2": "Le {{blacephalonName}} du Clown utilise\nune étrange capacité !$Tous les types de votre équipe\nsont échangés au hasard !", "selected_2": "Le {{blacephalonName}} du Clown utilise\nune étrange capacité !$Tous les types de votre équipe\nsont échangés au hasard !",

View File

@ -5,7 +5,7 @@
"speaker": "Savant fou", "speaker": "Savant fou",
"intro_dialogue": "Hé, toi !$Je travaille sur un dispositif qui permet déveiller\nla vraie puissance dun Pokémon !$Il restructure complètement les atomes du Pokémon\npour en faire une version bien plus puissante.$Héhé…@d{64} Je nai besoin que de sac-@d{32}\nEuuh, sujets tests, pour prouver son fonctionnement.", "intro_dialogue": "Hé, toi !$Je travaille sur un dispositif qui permet déveiller\nla vraie puissance dun Pokémon !$Il restructure complètement les atomes du Pokémon\npour en faire une version bien plus puissante.$Héhé…@d{64} Je nai besoin que de sac-@d{32}\nEuuh, sujets tests, pour prouver son fonctionnement.",
"title": "LExpérience interdite", "title": "LExpérience interdite",
"description": "Ce scientifique à lair un peu taré tient dans ses mains quelques Poké Balls.\n« Tinquites pas, je gère ! Voilà quelques Poké Balls plutôt efficaces en caution, tout ce dont jai besoin, cest un de tes Pokémon ! Héhé… »", "description": "Ce scientifique à lair un peu taré tient dans ses mains quelques Poké Balls.\n« Tinquiètes pas, je gère ! Voilà quelques Poké Balls plutôt efficaces en caution, tout ce dont jai besoin, cest un de tes Pokémon ! Héhé… »",
"query": "Que voulez-vous faire ?", "query": "Que voulez-vous faire ?",
"option": { "option": {
"1": { "1": {

View File

@ -1,7 +1,7 @@
{ {
"intro": "Cest une enseignante avec ses élèves !", "intro": "Cest une enseignante avec ses élèves !",
"speaker": "Enseignante", "speaker": "Enseignante",
"intro_dialogue": "Hé, bonjour !\nAurais-tu une minute à accorder à mes élèves ?$Je leur apprends ce que sont les capacités et\njadorerais que tu leur fasse une démosntration.$Tu serais daccord pour nous montrer ça\navec un de tes Pokémon ?", "intro_dialogue": "Hé, bonjour !\nAurais-tu une minute à accorder à mes élèves ?$Je leur apprends ce que sont les capacités et\njadorerais que tu leur fasse une démonstration.$Tu serais daccord pour nous montrer ça\navec un de tes Pokémon ?",
"title": "Sortie scolaire", "title": "Sortie scolaire",
"description": "Une enseignante vous demande de faire la démonstration dune capacité dun de vos Pokémon.\nEn fonction de la capacité, elle pourrait vous donner quelque chose dutile.", "description": "Une enseignante vous demande de faire la démonstration dune capacité dun de vos Pokémon.\nEn fonction de la capacité, elle pourrait vous donner quelque chose dutile.",
"query": "Utiliser quelle catégorie de capacité ?", "query": "Utiliser quelle catégorie de capacité ?",

View File

@ -1,7 +1,7 @@
{ {
"intro": "Un vent incandescent de fumées et de cendres\nvous fonce dessus !", "intro": "Un vent incandescent de fumées et de cendres\nvous fonce dessus !",
"title": "Fait chaud là, non ?", "title": "Fait chaud là, non ?",
"description": "La visiblité est quasi nulle à cause des cendres et les braises tourbillonnantes. Il doit forcément y avoir une quelconque source responsable de ces conditions. Mais que pourrait causer un phénomène dune telle ampleur ?", "description": "La visibilité est quasi nulle à cause des cendres et des braises tourbillonnantes. Il doit forcément y avoir une quelconque source responsable de ces conditions. Mais que pourrait causer un phénomène dune telle ampleur ?",
"query": "Que voulez-vous faire ?", "query": "Que voulez-vous faire ?",
"option": { "option": {
"1": { "1": {

View File

@ -1,5 +1,5 @@
{ {
"intro_dialogue": "Approchez mesdames et messieurs !$Tentez votre chance au tout nouveau\nRipost-o-matic de {{wobbuffetName}} !", "intro_dialogue": "Approchez mesdames et messieurs !$Tentez votre chance au tout nouveau\nQulbut-o-matic de {{wobbuffetName}} !",
"speaker": "Animateur", "speaker": "Animateur",
"title": "Du rire et des jeux !", "title": "Du rire et des jeux !",
"description": "Vous rencontrez un forain avec une mailloche ! Vous disposez de @[TOOLTIP_TITLE]{3 tours} pour amener {{wobbuffetName}} le plus près possible de @[TOOLTIP_TITLE]{1 PV} pour ainsi charger la Riposte la plus puissante possible, @[TOOLTIP_TITLE]{sans le mettre K.O.} .\nMais attention ! Si {{wobbuffetName}} est mis K.O., vous devrez payer pour le ranimer !", "description": "Vous rencontrez un forain avec une mailloche ! Vous disposez de @[TOOLTIP_TITLE]{3 tours} pour amener {{wobbuffetName}} le plus près possible de @[TOOLTIP_TITLE]{1 PV} pour ainsi charger la Riposte la plus puissante possible, @[TOOLTIP_TITLE]{sans le mettre K.O.} .\nMais attention ! Si {{wobbuffetName}} est mis K.O., vous devrez payer pour le ranimer !",
@ -7,7 +7,7 @@
"option": { "option": {
"1": { "1": {
"label": "Jouer", "label": "Jouer",
"tooltip": "(-) Payer {{option1Money, money}}\n(+) Jouer au Ripost-o-matic", "tooltip": "(-) Payer {{option1Money, money}}\n(+) Jouer au Qulbut-o-matic",
"selected": "Vous tentez votre chance !" "selected": "Vous tentez votre chance !"
}, },
"2": { "2": {

View File

@ -21,7 +21,7 @@
"3": { "3": {
"label": "Naviguer au jugé", "label": "Naviguer au jugé",
"tooltip": "(-) Votre équipe perd {{damagePercentage}}% de ses PV totaux", "tooltip": "(-) Votre équipe perd {{damagePercentage}}% de ses PV totaux",
"selected": "Vous vous laissez porter sans but par les vagues, quand soudainement vous remarquez un lieu famillier.$Vous et vos Pokémon êtes compètement rincés\nde fatigue par cette épreuve." "selected": "Vous vous laissez porter sans but par les vagues, quand soudainement vous remarquez un lieu familier.$Vous et vos Pokémon êtes complètement rincés\nde fatigue par cette épreuve."
} }
}, },
"outro": "Vous retrouvez un cap clair." "outro": "Vous retrouvez un cap clair."

View File

@ -1,5 +1,5 @@
{ {
"intro": "De mystérieux adversaires apparissent !", "intro": "De mystérieux adversaires apparaissent !",
"title": "De mystérieux adversaires", "title": "De mystérieux adversaires",
"description": "Si vous défaites un de ces adversaires, vous pourriez peut-être recevoir une récompense si vous les avez impressionnés.\nCertains ont cependant lair bien coriaces. Acceptez-vous le défi ?", "description": "Si vous défaites un de ces adversaires, vous pourriez peut-être recevoir une récompense si vous les avez impressionnés.\nCertains ont cependant lair bien coriaces. Acceptez-vous le défi ?",
"query": "Qui voulez-vous affronter ?", "query": "Qui voulez-vous affronter ?",

View File

@ -1,7 +1,7 @@
{ {
"intro": "Vous tombez sur…@d{32} un coffre ?", "intro": "Vous tombez sur…@d{32} un coffre ?",
"title": "Un étrange coffre", "title": "Un étrange coffre",
"description": "Un mangifique coffre orné est posé en travers du chemin.\nIl doit forcément contenir quelque chose… pas vrai ?", "description": "Un magnifique coffre orné est posé en travers du chemin.\nIl doit forcément contenir quelque chose… pas vrai ?",
"query": "Voulez-vous ouvrir le coffre ?", "query": "Voulez-vous ouvrir le coffre ?",
"option": { "option": {
"1": { "1": {

View File

@ -25,7 +25,7 @@
} }
}, },
"job_complete_good": "Merci pour votre aide !\nVotre {{selectedPokemon}} nous a été dun grand renfort !$Voici votre salaire.", "job_complete_good": "Merci pour votre aide !\nVotre {{selectedPokemon}} nous a été dun grand renfort !$Voici votre salaire.",
"job_complete_bad": "Votre {{selectedPokemon}} nous a plutot bien aidé !$Voici votre salaire.", "job_complete_bad": "Votre {{selectedPokemon}} nous a plutôt bien aidé !$Voici votre salaire.",
"pokemon_tired": "Votre {{selectedPokemon}} est épuisé !\nLes PP de toutes ses capacités baissent de 2 !", "pokemon_tired": "Votre {{selectedPokemon}} est épuisé !\nLes PP de toutes ses capacités baissent de 2 !",
"outro": "Reviens nous aider à loccasion !" "outro": "Reviens nous aider à loccasion !"
} }

View File

@ -12,7 +12,7 @@
"2": { "2": {
"label": "Attendre quil bouge", "label": "Attendre quil bouge",
"tooltip": "(-) Attendre longtemps\n(+) Équipe soignée", "tooltip": "(-) Attendre longtemps\n(+) Équipe soignée",
"selected": ".@d{32}.@d{32}.@d{32}$Vous attendez un long moment et les baillements de {{snorlaxName}} endorment votre équipe…", "selected": ".@d{32}.@d{32}.@d{32}$Vous attendez un long moment et les bâillements de {{snorlaxName}} endorment votre équipe…",
"rest_result": "À votre réveil le {{snorlaxName}} a disparu,\net toute votre équipe est soignée !" "rest_result": "À votre réveil le {{snorlaxName}} a disparu,\net toute votre équipe est soignée !"
}, },
"3": { "3": {

View File

@ -23,7 +23,7 @@
"selected": "Daccord, faisons ça !" "selected": "Daccord, faisons ça !"
}, },
"outro": "Ton {{chosenPokemon}} et toi avez\nlair très heureux !$Tiens, prends ça aussi.", "outro": "Ton {{chosenPokemon}} et toi avez\nlair très heureux !$Tiens, prends ça aussi.",
"outro_failed": "Voilà qui est bien décevant…$Tas encore visiblement bien du chemin à faire\npour acquir la confiance de tes Pokémon !", "outro_failed": "Voilà qui est bien décevant…$Tas encore visiblement bien du chemin à faire\npour acquérir la confiance de tes Pokémon !",
"gained_eggs": "@s{item_fanfare}Vous recevez\n{{numEggs}} !", "gained_eggs": "@s{item_fanfare}Vous recevez\n{{numEggs}} !",
"eggs_tooltip": "\n(+) Recevez {{eggs}}", "eggs_tooltip": "\n(+) Recevez {{eggs}}",
"numEggs_one": "{{count}} Œuf {{rarity}}", "numEggs_one": "{{count}} Œuf {{rarity}}",

View File

@ -8,7 +8,7 @@
"label": "Approcher {{shuckleName}}", "label": "Approcher {{shuckleName}}",
"tooltip": "(?) Quelque chose de terrible ou dincroyable peut se produire", "tooltip": "(?) Quelque chose de terrible ou dincroyable peut se produire",
"selected": "Vous perdez subitement connaissance.", "selected": "Vous perdez subitement connaissance.",
"selected_2": "@f{150}À votre réveil, le {{shuckleName}} est parti et\nle bol de jus est complètement vide.${{highBstPokemon1}} et {{highBstPokemon2}}\nsont vicitmes dune forte léthargie !$Leurs stats de base sont baissées de {{reductionValue}} !$Mais par contre, vos autres Pokémon sont envahis\ndune vigueur encore jamais vue !$Leurs stats de base sont augmentées de {{increaseValue}} !" "selected_2": "@f{150}À votre réveil, le {{shuckleName}} est parti et\nle bol de jus est complètement vide.${{highBstPokemon1}} et {{highBstPokemon2}}\nsont victimes dune forte léthargie !$Leurs stats de base sont baissées de {{reductionValue}} !$Mais par contre, vos autres Pokémon sont envahis\ndune vigueur encore jamais vue !$Leurs stats de base sont augmentées de {{increaseValue}} !"
}, },
"2": { "2": {
"label": "Affronter {{shuckleName}}", "label": "Affronter {{shuckleName}}",
@ -17,5 +17,5 @@
"stat_boost": "Le jus de {{shuckleName}} augmente ses stats !" "stat_boost": "Le jus de {{shuckleName}} augmente ses stats !"
} }
}, },
"outro": "Quelle étrange tournure des évènements." "outro": "Quelle étrange tournure des évènements"
} }

View File

@ -17,6 +17,6 @@
"selected": "Cest bien dommage. Hé, ton équipe a quand même lair bien au bout, ça te dirait de rester te reposer un peu ?" "selected": "Cest bien dommage. Hé, ton équipe a quand même lair bien au bout, ça te dirait de rester te reposer un peu ?"
} }
}, },
"victory": "Félicitations pour avoir relevé notre défi !$Tout dabord, nous aimerions toffir ce Coupon.", "victory": "Félicitations pour avoir relevé notre défi !$Tout dabord, nous aimerions toffrir ce Coupon.",
"victory_2": "Mais aussi, notre famille utilise ce Bracelet Macho\npour entrainer plus efficacement nos Pokémon.$Il ne te sera peut-être daucune utilité vu que tu nous as tous battus, mais on serait ravis que tu laccepte !" "victory_2": "Mais aussi, notre famille utilise ce Bracelet Macho\npour entrainer plus efficacement nos Pokémon.$Il ne te sera peut-être daucune utilité vu que tu nous as tous battus, mais on serait ravis que tu laccepte !"
} }

View File

@ -29,5 +29,5 @@
}, },
"selected": "{{selectedPokemon}} va de lautre côté\ndu terrain pour vous faire face…" "selected": "{{selectedPokemon}} va de lautre côté\ndu terrain pour vous faire face…"
}, },
"outro": "Cet entrainement a vraiement été très stimulant !" "outro": "Cet entrainement a vraiment été très stimulant !"
} }

View File

@ -1,7 +1,7 @@
{ {
"intro": "Cest un Pokémon tout ce quil y a de plus banal !", "intro": "Cest un Pokémon tout ce quil y a de plus banal !",
"title": "Une forme peu commune", "title": "Une forme peu commune",
"description": "Ce {{enemyPokemon}} a lair speical par rapport au reste de son espèce. @[TOOLTIP_TITLE]{Peut-être connait-il une capacité particulière ?} Vous pourriez décider de laffronter sur-le-champ, mais il y a peut-être moyen den faire un nouvel ami.", "description": "Ce {{enemyPokemon}} a lair spécial par rapport au reste de son espèce. @[TOOLTIP_TITLE]{Peut-être connait-il une capacité particulière ?} Vous pourriez décider de laffronter sur-le-champ, mais il y a peut-être moyen den faire un nouvel ami.",
"query": "Que voulez-vous faire ?", "query": "Que voulez-vous faire ?",
"option": { "option": {
"1": { "1": {
@ -12,9 +12,9 @@
}, },
"2": { "2": {
"label": "Le nourrir", "label": "Le nourrir",
"disabled_tooltip": "Vous avez besoin de 4 Baies pour chosir cette option", "disabled_tooltip": "Vous avez besoin de 4 Baies pour choisir cette option",
"tooltip": "(-) Donner 4 Baies\n(+) Le {{enemyPokemon}} vous apprécie", "tooltip": "(-) Donner 4 Baies\n(+) Le {{enemyPokemon}} vous apprécie",
"selected": "Vous lancer quelques Baies\nau {{enemyPokemon}} !$Il les englouit avec joie !$Le {{enemyPokemon}} veut se joindre\nà votre équipe !" "selected": "Vous lancer quelques Baies\nau {{enemyPokemon}} !$Il les engloutit avec joie !$Le {{enemyPokemon}} veut se joindre\nà votre équipe !"
}, },
"3": { "3": {
"label": "Devenir amis", "label": "Devenir amis",

View File

@ -9,9 +9,9 @@
"1": { "1": {
"label":  Je les vois. »", "label":  Je les vois. »",
"tooltip": "@[SUMMARY_GREEN]{(?) Affecte vos Pokémon}", "tooltip": "@[SUMMARY_GREEN]{(?) Affecte vos Pokémon}",
"selected": "Sa main sapproche de vous et vous touche.\nTout devient subitement sombre.$Puis enfin…@d{64} La lumière.\nChaque espace-temps. Chacune de vos incarnations.$Tout ce qui a composé, compose\net composersa votre être…@d{64}", "selected": "Sa main sapproche de vous et vous touche.\nTout devient subitement sombre.$Puis enfin…@d{64} La lumière.\nChaque espace-temps. Chacune de vos incarnations.$Tout ce qui a composé, compose\net composera votre être…@d{64}",
"cutscene": "Vous percevez vos Pokémon,@d{32} convergeant de chaque\nréalité pour former quelque chose de nouveau…@d{64}", "cutscene": "Vous percevez vos Pokémon,@d{32} convergeant de chaque\nréalité pour former quelque chose de nouveau…@d{64}",
"dream_complete": "Vous vous réveillez, mais la femme a disparu…\nOu bien netait-elle quune hallucination ?$.@d{32}.@d{32}.@d{32}$Les Pokémon de votre équipe ont changé…$Mais alors, comment peuvent-ils quand même\ntoujours vous sembler si familiers ?" "dream_complete": "Vous vous réveillez, mais la femme a disparu…\nOu bien nétait-elle quune hallucination ?$.@d{32}.@d{32}.@d{32}$Les Pokémon de votre équipe ont changé…$Mais alors, comment peuvent-ils quand même\ntoujours vous sembler si familiers ?"
}, },
"2": { "2": {
"label": "Partir en courant", "label": "Partir en courant",

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "Aucun", "name": "Aucun"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "Empoisonnement", "name": "Empoisonnement",

View File

@ -1,11 +1,5 @@
{ {
"none": { "none": {
"name": "None", "name": "None"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
} }
} }

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "なし", "name": "なし"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "どく", "name": "どく",

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "없음", "name": "없음"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "독", "name": "독",

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "Nenhum", "name": "Nenhum"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "Envenenamento", "name": "Envenenamento",

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "无", "name": "无"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "中毒", "name": "中毒",

View File

@ -1,12 +1,6 @@
{ {
"none": { "none": {
"name": "無", "name": "無"
"description": "",
"obtain": "",
"obtainSource": "",
"activation": "",
"overlap": "",
"heal": ""
}, },
"poison": { "poison": {
"name": "中毒", "name": "中毒",

View File

@ -1577,17 +1577,22 @@ export const modifierTypes = {
MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { MYSTERY_ENCOUNTER_SHUCKLE_JUICE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs) { if (pregenArgs) {
return new PokemonBaseStatTotalModifierType(pregenArgs[0] as integer); return new PokemonBaseStatTotalModifierType(pregenArgs[0] as number);
} }
return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20)); return new PokemonBaseStatTotalModifierType(Utils.randSeedInt(20));
}), }),
MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => { MYSTERY_ENCOUNTER_OLD_GATEAU: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs) { if (pregenArgs) {
return new PokemonBaseStatFlatModifierType(pregenArgs[0] as integer, pregenArgs[1] as Stat[]); return new PokemonBaseStatFlatModifierType(pregenArgs[0] as number, pregenArgs[1] as Stat[]);
} }
return new PokemonBaseStatFlatModifierType(Utils.randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]); return new PokemonBaseStatFlatModifierType(Utils.randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]);
}), }),
MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type)), MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs) {
return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type, pregenArgs[0] as number));
}
return new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.HealShopCostModifier(type, 2.5));
}),
MYSTERY_ENCOUNTER_MACHO_BRACE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", "macho_brace", (type, args) => new Modifiers.PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id)), MYSTERY_ENCOUNTER_MACHO_BRACE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", "macho_brace", (type, args) => new Modifiers.PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id)),
MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET", "golden_net", (type, _args) => new Modifiers.BoostBugSpawnModifier(type)), MYSTERY_ENCOUNTER_GOLDEN_BUG_NET: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_GOLDEN_BUG_NET", "golden_net", (type, _args) => new Modifiers.BoostBugSpawnModifier(type)),
}; };

View File

@ -2576,8 +2576,12 @@ export class LockModifierTiersModifier extends PersistentModifier {
* Black Sludge item * Black Sludge item
*/ */
export class HealShopCostModifier extends PersistentModifier { export class HealShopCostModifier extends PersistentModifier {
constructor(type: ModifierType, stackCount?: integer) { public readonly shopMultiplier: number;
constructor(type: ModifierType, shopMultiplier: number, stackCount?: integer) {
super(type, stackCount); super(type, stackCount);
this.shopMultiplier = shopMultiplier;
} }
match(modifier: Modifier): boolean { match(modifier: Modifier): boolean {
@ -2585,11 +2589,11 @@ export class HealShopCostModifier extends PersistentModifier {
} }
clone(): HealShopCostModifier { clone(): HealShopCostModifier {
return new HealShopCostModifier(this.type, this.stackCount); return new HealShopCostModifier(this.type, this.shopMultiplier, this.stackCount);
} }
apply(args: any[]): boolean { apply(args: any[]): boolean {
(args[0] as Utils.IntegerHolder).value *= Math.pow(3, this.getStackCount()); (args[0] as Utils.IntegerHolder).value *= this.shopMultiplier;
return true; return true;
} }
@ -2608,7 +2612,7 @@ export class BoostBugSpawnModifier extends PersistentModifier {
return modifier instanceof BoostBugSpawnModifier; return modifier instanceof BoostBugSpawnModifier;
} }
clone(): HealShopCostModifier { clone(): BoostBugSpawnModifier {
return new BoostBugSpawnModifier(this.type, this.stackCount); return new BoostBugSpawnModifier(this.type, this.stackCount);
} }

View File

@ -62,7 +62,13 @@ export class EncounterPhase extends BattlePhase {
const battle = this.scene.currentBattle; const battle = this.scene.currentBattle;
// Init Mystery Encounter if there is one // Generate and Init Mystery Encounter
if (battle.battleType === BattleType.MYSTERY_ENCOUNTER && !battle.mysteryEncounter) {
this.scene.executeWithSeedOffset(() => {
const currentSessionEncounterType = battle.mysteryEncounterType;
battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType);
}, battle.waveIndex << 4);
}
const mysteryEncounter = battle.mysteryEncounter; const mysteryEncounter = battle.mysteryEncounter;
if (mysteryEncounter) { if (mysteryEncounter) {
// If ME has an onInit() function, call it // If ME has an onInit() function, call it
@ -152,13 +158,10 @@ export class EncounterPhase extends BattlePhase {
if (battle.battleType === BattleType.TRAINER) { if (battle.battleType === BattleType.TRAINER) {
loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct? loadEnemyAssets.push(battle.trainer?.loadAssets().then(() => battle.trainer?.initSprite())!); // TODO: is this bang correct?
} else if (battle.battleType === BattleType.MYSTERY_ENCOUNTER) { } else if (battle.battleType === BattleType.MYSTERY_ENCOUNTER) {
if (!battle.mysteryEncounter) { if (battle.mysteryEncounter?.introVisuals) {
battle.mysteryEncounter = this.scene.getMysteryEncounter(mysteryEncounter?.encounterType);
}
if (battle.mysteryEncounter.introVisuals) {
loadEnemyAssets.push(battle.mysteryEncounter.introVisuals.loadAssets().then(() => battle.mysteryEncounter!.introVisuals!.initSprite())); loadEnemyAssets.push(battle.mysteryEncounter.introVisuals.loadAssets().then(() => battle.mysteryEncounter!.introVisuals!.initSprite()));
} }
if (battle.mysteryEncounter.loadAssets.length > 0) { if (battle.mysteryEncounter?.loadAssets && battle.mysteryEncounter.loadAssets.length > 0) {
loadEnemyAssets.push(...battle.mysteryEncounter.loadAssets); loadEnemyAssets.push(...battle.mysteryEncounter.loadAssets);
} }
// Load Mystery Encounter Exclamation bubble and sfx // Load Mystery Encounter Exclamation bubble and sfx

View File

@ -252,13 +252,13 @@ export class SelectModifierPhase extends BattlePhase {
let multiplier = 1; let multiplier = 1;
if (!isNullOrUndefined(this.customModifierSettings?.rerollMultiplier)) { if (!isNullOrUndefined(this.customModifierSettings?.rerollMultiplier)) {
if (this.customModifierSettings!.rerollMultiplier! < 0) { if (this.customModifierSettings.rerollMultiplier < 0) {
// Completely overrides reroll cost to -1 and early exits // Completely overrides reroll cost to -1 and early exits
return -1; return -1;
} }
// Otherwise, continue with custom multiplier // Otherwise, continue with custom multiplier
multiplier = this.customModifierSettings!.rerollMultiplier!; multiplier = this.customModifierSettings.rerollMultiplier;
} }
return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER);
} }

View File

@ -18,44 +18,45 @@ describe("status-effect", () => {
mockI18next(); mockI18next();
const text = getStatusEffectObtainText(statusEffect, pokemonName); const text = getStatusEffectObtainText(statusEffect, pokemonName);
expect(text).toBe("statusEffect:none.obtain"); console.log("text:", text);
expect(text).toBe("");
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, ""); const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
expect(emptySourceText).toBe("statusEffect:none.obtain"); expect(emptySourceText).toBe("");
}); });
it("should return the source-obtain text", () => { it("should return the source-obtain text", () => {
mockI18next(); mockI18next();
const text = getStatusEffectObtainText(statusEffect, pokemonName, sourceText); const text = getStatusEffectObtainText(statusEffect, pokemonName, sourceText);
expect(text).toBe("statusEffect:none.obtainSource"); expect(text).toBe("");
const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, ""); const emptySourceText = getStatusEffectObtainText(statusEffect, pokemonName, "");
expect(emptySourceText).not.toBe("statusEffect:none.obtainSource"); expect(emptySourceText).toBe("");
}); });
it("should return the activation text", () => { it("should return the activation text", () => {
mockI18next(); mockI18next();
const text = getStatusEffectActivationText(statusEffect, pokemonName); const text = getStatusEffectActivationText(statusEffect, pokemonName);
expect(text).toBe("statusEffect:none.activation"); expect(text).toBe("");
}); });
it("should return the overlap text", () => { it("should return the overlap text", () => {
mockI18next(); mockI18next();
const text = getStatusEffectOverlapText(statusEffect, pokemonName); const text = getStatusEffectOverlapText(statusEffect, pokemonName);
expect(text).toBe("statusEffect:none.overlap"); expect(text).toBe("");
}); });
it("should return the heal text", () => { it("should return the heal text", () => {
mockI18next(); mockI18next();
const text = getStatusEffectHealText(statusEffect, pokemonName); const text = getStatusEffectHealText(statusEffect, pokemonName);
expect(text).toBe("statusEffect:none.heal"); expect(text).toBe("");
}); });
it("should return the descriptor", () => { it("should return the descriptor", () => {
mockI18next(); mockI18next();
const text = getStatusEffectDescriptor(statusEffect); const text = getStatusEffectDescriptor(statusEffect);
expect(text).toBe("statusEffect:none.description"); expect(text).toBe("");
}); });
}); });

View File

@ -114,7 +114,7 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN
} }
if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) { if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) {
await handleSecondaryOptionSelect(game, secondaryOptionSelect!.pokemonNo, secondaryOptionSelect!.optionNo); await handleSecondaryOptionSelect(game, secondaryOptionSelect.pokemonNo, secondaryOptionSelect.optionNo);
} else { } else {
uiHandler.processInput(Button.ACTION); uiHandler.processInput(Button.ACTION);
} }

View File

@ -128,7 +128,7 @@ describe("Absolute Avarice - Mystery Encounter", () => {
expect(enemyField[0].species.speciesId).toBe(Species.GREEDENT); expect(enemyField[0].species.speciesId).toBe(Species.GREEDENT);
const moveset = enemyField[0].moveset.map(m => m?.moveId); const moveset = enemyField[0].moveset.map(m => m?.moveId);
expect(moveset?.length).toBe(4); expect(moveset?.length).toBe(4);
expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF]); expect(moveset).toEqual([Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH]);
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
expect(movePhases.length).toBe(1); expect(movePhases.length).toBe(1);

View File

@ -17,6 +17,7 @@ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encount
import * as EncounterDialogueUtils from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import * as EncounterDialogueUtils from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import { Abilities } from "#enums/abilities";
const namespace = "mysteryEncounter:berriesAbound"; const namespace = "mysteryEncounter:berriesAbound";
const defaultParty = [Species.PYUKUMUKU, Species.MAGIKARP, Species.PIKACHU]; const defaultParty = [Species.PYUKUMUKU, Species.MAGIKARP, Species.PIKACHU];
@ -35,13 +36,15 @@ describe("Berries Abound - Mystery Encounter", () => {
beforeEach(async () => { beforeEach(async () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
scene = game.scene; scene = game.scene;
game.override.mysteryEncounterChance(100); game.override.mysteryEncounterChance(100)
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); .mysteryEncounterTier(MysteryEncounterTier.COMMON)
game.override.startingWave(defaultWave); .startingWave(defaultWave)
game.override.startingBiome(defaultBiome); .startingBiome(defaultBiome)
game.override.disableTrainerWaves(); .disableTrainerWaves()
game.override.startingModifier([]); .startingModifier([])
game.override.startingHeldItems([]); .startingHeldItems([])
.enemyAbility(Abilities.BALL_FETCH)
.enemyPassiveAbility(Abilities.BALL_FETCH);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([ new Map<Biome, MysteryEncounterType[]>([
@ -168,7 +171,30 @@ describe("Berries Abound - Mystery Encounter", () => {
}); });
}); });
it("should start battle if fastest pokemon is slower than boss", async () => { it("should start battle if fastest pokemon is slower than boss below wave 50", async () => {
game.override.startingWave(41);
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
// Setting enemy's level arbitrarily high to outspeed
config.pokemonConfigs![0].dataSource!.level = 1000;
await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
// Should be enraged
expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 1, 0, 0]);
expect(encounterTextSpy).toHaveBeenCalledWith(expect.any(BattleScene), `${namespace}.option.2.selected_bad`);
});
it("should start battle if fastest pokemon is slower than boss above wave 50", async () => {
game.override.startingWave(57);
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);

View File

@ -11,7 +11,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter"; import { DelibirdyEncounter } from "#app/data/mystery-encounters/encounters/delibirdy-encounter";
import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { BerryModifier, HealingBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier";
import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
@ -104,35 +104,35 @@ describe("Delibird-y - Mystery Encounter", () => {
expect(scene.money).toBe(initialMoney - price); expect(scene.money).toBe(initialMoney - price);
}); });
it("Should give the player a Hidden Ability Charm", async () => { it("Should give the player an Amulet Coin", async () => {
scene.money = 200000; scene.money = 200000;
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
await runMysteryEncounterToEnd(game, 1); await runMysteryEncounterToEnd(game, 1);
const itemModifier = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier; const itemModifier = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
expect(itemModifier).toBeDefined(); expect(itemModifier).toBeDefined();
expect(itemModifier?.stackCount).toBe(1); expect(itemModifier?.stackCount).toBe(1);
}); });
it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => { it("Should give the player a Shell Bell if they have max stacks of Amulet Coins", async () => {
scene.money = 200000; scene.money = 200000;
await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty);
// 5 Healing Charms // Max Amulet Coins
scene.modifiers = []; scene.modifiers = [];
const abilityCharm = generateModifierType(scene, modifierTypes.ABILITY_CHARM)!.newModifier() as HiddenAbilityRateBoosterModifier; const amuletCoin = generateModifierType(scene, modifierTypes.AMULET_COIN)!.newModifier() as MoneyMultiplierModifier;
abilityCharm.stackCount = 4; amuletCoin.stackCount = 5;
await scene.addModifier(abilityCharm, true, false, false, true); await scene.addModifier(amuletCoin, true, false, false, true);
await scene.updateModifiers(true); await scene.updateModifiers(true);
await runMysteryEncounterToEnd(game, 1); await runMysteryEncounterToEnd(game, 1);
const abilityCharmAfter = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier); const amuletCoinAfter = scene.findModifier(m => m instanceof MoneyMultiplierModifier);
const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier);
expect(abilityCharmAfter).toBeDefined(); expect(amuletCoinAfter).toBeDefined();
expect(abilityCharmAfter?.stackCount).toBe(4); expect(amuletCoinAfter?.stackCount).toBe(5);
expect(shellBellAfter).toBeDefined(); expect(shellBellAfter).toBeDefined();
expect(shellBellAfter?.stackCount).toBe(1); expect(shellBellAfter?.stackCount).toBe(1);
}); });

View File

@ -98,7 +98,7 @@ describe("Department Store Sale - Mystery Encounter", () => {
expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT);
const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler;
expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options.length).toEqual(5);
for (const option of modifierSelectHandler.options) { for (const option of modifierSelectHandler.options) {
expect(option.modifierTypeOption.type.id).toContain("TM_"); expect(option.modifierTypeOption.type.id).toContain("TM_");
} }

View File

@ -42,8 +42,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
.startingWave(defaultWave) .startingWave(defaultWave)
.startingBiome(defaultBiome) .startingBiome(defaultBiome)
.disableTrainerWaves() .disableTrainerWaves()
.enemyPassiveAbility(Abilities.BALL_FETCH) .enemyAbility(Abilities.BALL_FETCH)
.enemyAbility(Abilities.BALL_FETCH); .enemyPassiveAbility(Abilities.BALL_FETCH);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([ new Map<Biome, MysteryEncounterType[]>([
@ -258,8 +258,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
it("should start a battle against an extra enraged boss above wave 50", { retry: 5 }, async () => { it("should start a battle against an extra enraged boss above wave 50", { retry: 5 }, async () => {
game.override.startingWave(56); game.override.startingWave(56);
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [Species.PIKACHU]);
await runMysteryEncounterToEnd(game, 1, undefined, true); await runMysteryEncounterToEnd(game, 2, undefined, true);
const enemyField = scene.getEnemyField(); const enemyField = scene.getEnemyField();
expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 1, 0, 0]); expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 1, 0, 0]);
expect(enemyField[0].isBoss()).toBe(true); expect(enemyField[0].isBoss()).toBe(true);

View File

@ -70,7 +70,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.THE_STRONG_STUFF, defaultParty);
expect(TheStrongStuffEncounter.encounterType).toBe(MysteryEncounterType.THE_STRONG_STUFF); expect(TheStrongStuffEncounter.encounterType).toBe(MysteryEncounterType.THE_STRONG_STUFF);
expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.GREAT); expect(TheStrongStuffEncounter.encounterTier).toBe(MysteryEncounterTier.COMMON);
expect(TheStrongStuffEncounter.dialogue).toBeDefined(); expect(TheStrongStuffEncounter.dialogue).toBeDefined();
expect(TheStrongStuffEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]); expect(TheStrongStuffEncounter.dialogue.intro).toStrictEqual([{ text: `${namespace}.intro` }]);
expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`); expect(TheStrongStuffEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}.title`);

View File

@ -24,6 +24,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Abilities } from "#enums/abilities";
const namespace = "mysteryEncounter:uncommonBreed"; const namespace = "mysteryEncounter:uncommonBreed";
const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA];
@ -42,11 +43,13 @@ describe("Uncommon Breed - Mystery Encounter", () => {
beforeEach(async () => { beforeEach(async () => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
scene = game.scene; scene = game.scene;
game.override.mysteryEncounterChance(100); game.override.mysteryEncounterChance(100)
game.override.mysteryEncounterTier(MysteryEncounterTier.COMMON); .mysteryEncounterTier(MysteryEncounterTier.COMMON)
game.override.startingWave(defaultWave); .startingWave(defaultWave)
game.override.startingBiome(defaultBiome); .startingBiome(defaultBiome)
game.override.disableTrainerWaves(); .disableTrainerWaves()
.enemyAbility(Abilities.BALL_FETCH)
.enemyPassiveAbility(Abilities.BALL_FETCH);
vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(
new Map<Biome, MysteryEncounterType[]>([ new Map<Biome, MysteryEncounterType[]>([
@ -107,7 +110,34 @@ describe("Uncommon Breed - Mystery Encounter", () => {
}); });
}); });
it.skip("should start a fight against the boss", async () => { it.skip("should start a fight against the boss below wave 50", async () => {
const phaseSpy = vi.spyOn(scene, "pushPhase");
const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase");
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
await runMysteryEncounterToEnd(game, 1, undefined, true);
const enemyField = scene.getEnemyField();
expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name);
expect(enemyField.length).toBe(1);
expect(enemyField[0].species.speciesId).toBe(speciesToSpawn);
const statStagePhases = unshiftPhaseSpy.mock.calls.filter(p => p[0] instanceof StatStageChangePhase)[0][0] as any;
expect(statStagePhases.stats).toEqual([Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD]);
// Should have used its egg move pre-battle
const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]);
expect(movePhases.length).toBe(1);
const eggMoves: Moves[] = speciesEggMoves[getPokemonSpecies(speciesToSpawn).getRootSpeciesId()];
const usedMove = (movePhases[0] as MovePhase).move.moveId;
expect(eggMoves.includes(usedMove)).toBe(true);
});
it.skip("should start a fight against the boss above wave 50", async () => {
game.override.startingWave(57);
const phaseSpy = vi.spyOn(scene, "pushPhase"); const phaseSpy = vi.spyOn(scene, "pushPhase");
const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase"); const unshiftPhaseSpy = vi.spyOn(scene, "unshiftPhase");
await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty); await game.runToMysteryEncounter(MysteryEncounterType.UNCOMMON_BREED, defaultParty);

View File

@ -195,7 +195,7 @@ export default class GameManager {
async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) { async runToMysteryEncounter(encounterType?: MysteryEncounterType, species?: Species[]) {
if (!isNullOrUndefined(encounterType)) { if (!isNullOrUndefined(encounterType)) {
this.override.disableTrainerWaves(); this.override.disableTrainerWaves();
this.override.mysteryEncounter(encounterType!); this.override.mysteryEncounter(encounterType);
} }
await this.runToTitle(); await this.runToTitle();

View File

@ -95,8 +95,8 @@ export default class MysteryEncounterUiHandler extends UiHandler {
super.show(args); super.show(args);
this.overrideSettings = args[0] as OptionSelectSettings ?? {}; this.overrideSettings = args[0] as OptionSelectSettings ?? {};
const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) ? true : !this.overrideSettings?.hideDescription; const showDescriptionContainer = isNullOrUndefined(this.overrideSettings?.hideDescription) ? true : !this.overrideSettings.hideDescription;
const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) ? true : this.overrideSettings?.slideInDescription; const slideInDescription = isNullOrUndefined(this.overrideSettings?.slideInDescription) ? true : this.overrideSettings.slideInDescription;
const startingCursorIndex = this.overrideSettings?.startingCursorIndex ?? 0; const startingCursorIndex = this.overrideSettings?.startingCursorIndex ?? 0;
this.cursorContainer.setVisible(true); this.cursorContainer.setVisible(true);

View File

@ -584,7 +584,7 @@ export function capitalizeString(str: string, sep: string, lowerFirstChar: boole
* Returns if an object is null or undefined * Returns if an object is null or undefined
* @param object * @param object
*/ */
export function isNullOrUndefined(object: any): boolean { export function isNullOrUndefined(object: any): object is undefined | null {
return null === object || undefined === object; return null === object || undefined === object;
} }