Merge branch 'beta' into Snow-Worker-Dialogue-
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "berry_bush.png",
|
||||
"image": "berries_abound_bush.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 49,
|
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 719 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "mad_scientist_m.png",
|
||||
"image": "dark_deal_scientist.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 46,
|
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 920 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "b2w2_lady.png",
|
||||
"image": "department_store_sale_lady.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 399,
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "teacher.png",
|
||||
"image": "field_trip_teacher.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 43,
|
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 727 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "carnival_game.png",
|
||||
"image": "fun_and_games_game.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 38,
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 517 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "carnival_man.png",
|
||||
"image": "fun_and_games_man.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 50,
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 833 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "carnival_wobbuffet.png",
|
||||
"image": "fun_and_games_wobbuffet.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 45,
|
Before Width: | Height: | Size: 772 B After Width: | Height: | Size: 772 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "chest_blue.png",
|
||||
"image": "mysterious_chest_blue.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 54,
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "chest_red.png",
|
||||
"image": "mysterious_chest_red.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 54,
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "warehouse_crate.png",
|
||||
"image": "part_timer_crate.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 71,
|
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 868 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "bait.png",
|
||||
"image": "safari_zone_bait.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 14,
|
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 277 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "mud.png",
|
||||
"image": "safari_zone_mud.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 14,
|
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "b2w2_veteran_m.png",
|
||||
"image": "shady_vitamin_dealer.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 424,
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "teleporter.png",
|
||||
"image": "teleporting_hijinks_teleporter.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 74,
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "training_gear.png",
|
||||
"image": "training_session_gear.png",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 76,
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 7.3 KiB |
@ -1,10 +1,10 @@
|
||||
import Phaser from "phaser";
|
||||
import UI from "./ui/ui";
|
||||
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
|
||||
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined } from "#app/utils";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "./data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import * as Utils from "./utils";
|
||||
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, TurnHeldItemTransferModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems, PokemonIncrementingStatModifier, ExpShareModifier, ExpBalanceModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } 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 { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims";
|
||||
import { Phase } from "./phase";
|
||||
@ -13,20 +13,9 @@ import { Arena, ArenaBase } from "./field/arena";
|
||||
import { GameData } from "./system/game-data";
|
||||
import { addTextObject, getTextColor, TextStyle } from "./ui/text";
|
||||
import { allMoves } from "./data/move";
|
||||
import {
|
||||
ModifierPoolType,
|
||||
getDefaultModifierTypeForTier,
|
||||
getEnemyModifierTypesForWave,
|
||||
getLuckString,
|
||||
getLuckTextTint,
|
||||
getModifierPoolForType,
|
||||
getModifierType,
|
||||
getPartyLuckValue,
|
||||
modifierTypes, PokemonHeldItemModifierType
|
||||
} from "./modifier/modifier-type";
|
||||
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "./modifier/modifier-type";
|
||||
import AbilityBar from "./ui/ability-bar";
|
||||
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, ChangeMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
||||
import { allAbilities } from "./data/ability";
|
||||
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, BlockItemTheftAbAttr, ChangeMovePriorityAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr } from "./data/ability";
|
||||
import Battle, { BattleType, FixedBattleConfig } from "./battle";
|
||||
import { GameMode, GameModes, getGameMode } from "./game-mode";
|
||||
import FieldSpritePipeline from "./pipelines/field-sprite";
|
||||
@ -46,7 +35,7 @@ import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
|
||||
import { addUiThemeOverrides } from "./ui/ui-theme";
|
||||
import PokemonData from "./system/pokemon-data";
|
||||
import { Nature } from "./data/nature";
|
||||
import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem, SpeciesFormChange } from "./data/pokemon-forms";
|
||||
import { FormChangeItem, pokemonFormChanges, SpeciesFormChange, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger } from "./data/pokemon-forms";
|
||||
import { FormChangePhase } from "./phases/form-change-phase";
|
||||
import { getTypeRgb } from "./data/type";
|
||||
import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler";
|
||||
@ -1081,6 +1070,11 @@ export default class BattleScene extends SceneBase {
|
||||
p.destroy();
|
||||
}
|
||||
|
||||
// If this is a ME, clear any residual visual sprites before reloading
|
||||
if (this.currentBattle?.mysteryEncounter?.introVisuals) {
|
||||
this.field.remove(this.currentBattle.mysteryEncounter?.introVisuals, true);
|
||||
}
|
||||
|
||||
//@ts-ignore - allowing `null` for currentBattle causes a lot of trouble
|
||||
this.currentBattle = null; // TODO: resolve ts-ignore
|
||||
|
||||
@ -1111,6 +1105,8 @@ export default class BattleScene extends SceneBase {
|
||||
this.trainer.setPosition(406, 186);
|
||||
this.trainer.setVisible(true);
|
||||
|
||||
this.mysteryEncounterSaveData = new MysteryEncounterSaveData();
|
||||
|
||||
this.updateGameInfo();
|
||||
|
||||
if (reloadI18n) {
|
||||
@ -1205,32 +1201,12 @@ export default class BattleScene extends SceneBase {
|
||||
|
||||
// Check for mystery encounter
|
||||
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
|
||||
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
||||
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;
|
||||
// Reset base spawn weight
|
||||
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
} else {
|
||||
this.mysteryEncounterSaveData.encounterSpawnChance = sessionEncounterRate + WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||
}
|
||||
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER || !isNullOrUndefined(mysteryEncounterType)) {
|
||||
newBattleType = BattleType.MYSTERY_ENCOUNTER;
|
||||
// Reset base spawn weight
|
||||
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
|
||||
} else if (newBattleType === BattleType.WILD) {
|
||||
this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1271,9 +1247,8 @@ export default class BattleScene extends SceneBase {
|
||||
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
|
||||
this.currentBattle.double = false;
|
||||
this.executeWithSeedOffset(() => {
|
||||
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounterType);
|
||||
}, this.currentBattle.waveIndex << 4);
|
||||
// Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome
|
||||
this.currentBattle.mysteryEncounterType = mysteryEncounterType;
|
||||
}
|
||||
|
||||
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
|
||||
@ -2661,8 +2636,7 @@ export default class BattleScene extends SceneBase {
|
||||
modifier = mt.modifier as PokemonHeldItemModifier;
|
||||
modifier.pokemonId = enemyPokemon.id;
|
||||
}
|
||||
const stackCount = mt.stackCount ?? 1;
|
||||
modifier.stackCount = stackCount;
|
||||
modifier.stackCount = mt.stackCount ?? 1;
|
||||
modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable;
|
||||
this.addEnemyModifier(modifier, true);
|
||||
});
|
||||
@ -3101,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
|
||||
* @param encounterType used to load session encounter when restarting game, etc.
|
||||
@ -3109,10 +3128,10 @@ export default class BattleScene extends SceneBase {
|
||||
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
|
||||
// Loading override or session encounter
|
||||
let encounter: MysteryEncounter | null;
|
||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) {
|
||||
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!];
|
||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
|
||||
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
|
||||
} else {
|
||||
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType!] : null;
|
||||
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null;
|
||||
}
|
||||
|
||||
// Check for queued encounters first
|
||||
@ -3155,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;
|
||||
|
||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
|
||||
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE!;
|
||||
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE;
|
||||
}
|
||||
|
||||
let availableEncounters: MysteryEncounter[] = [];
|
||||
@ -3173,11 +3192,17 @@ export default class BattleScene extends SceneBase {
|
||||
if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier
|
||||
return false;
|
||||
}
|
||||
const disabledModes = encounterCandidate.disabledGameModes;
|
||||
if (disabledModes && disabledModes.length > 0
|
||||
&& disabledModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
|
||||
const disallowedGameModes = encounterCandidate.disallowedGameModes;
|
||||
if (disallowedGameModes && disallowedGameModes.length > 0
|
||||
&& disallowedGameModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
|
||||
return false;
|
||||
}
|
||||
if (this.gameMode.modeId === GameModes.CHALLENGE) { // Encounter is enabled for challenges
|
||||
const disallowedChallenges = encounterCandidate.disallowedChallenges;
|
||||
if (disallowedChallenges && disallowedChallenges.length > 0 && this.gameMode.challenges.some(challenge => disallowedChallenges.includes(challenge.id))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!encounterCandidate.meetsRequirements(this)) { // Meets encounter requirements
|
||||
return false;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { CustomModifierSettings } from "#app/modifier/modifier-type";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
|
||||
export enum ClassicFixedBossWaves {
|
||||
// TODO: other fixed wave battles should be added here
|
||||
@ -88,6 +89,7 @@ export default class Battle {
|
||||
public playerFaintsHistory: FaintLogEntry[] = [];
|
||||
public enemyFaintsHistory: FaintLogEntry[] = [];
|
||||
|
||||
public mysteryEncounterType?: MysteryEncounterType;
|
||||
/** If the current battle is a Mystery Encounter, this will always be defined */
|
||||
public mysteryEncounter?: MysteryEncounter;
|
||||
|
||||
|
72
src/data/ability.ts
Executable file → Normal file
@ -366,6 +366,10 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
getImmuneType(): Type | null {
|
||||
return this.immuneType;
|
||||
}
|
||||
|
||||
override getCondition(): AbAttrCondition | null {
|
||||
return this.condition;
|
||||
}
|
||||
@ -1798,6 +1802,61 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for defining all {@linkcode Ability} Attributes after a status effect has been set.
|
||||
* @see {@linkcode applyPostSetStatus()}.
|
||||
*/
|
||||
export class PostSetStatusAbAttr extends AbAttr {
|
||||
/**
|
||||
* Does nothing after a status condition is set.
|
||||
* @param pokemon {@linkcode Pokemon} that status condition was set on.
|
||||
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is `null` if status was not set by a Pokemon.
|
||||
* @param passive Whether this ability is a passive.
|
||||
* @param effect {@linkcode StatusEffect} that was set.
|
||||
* @param args Set of unique arguments needed by this attribute.
|
||||
* @returns `true` if application of the ability succeeds.
|
||||
*/
|
||||
applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]) : boolean | Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If another Pokemon burns, paralyzes, poisons, or badly poisons this Pokemon,
|
||||
* that Pokemon receives the same non-volatile status condition as part of this
|
||||
* ability attribute. For Synchronize ability.
|
||||
*/
|
||||
export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr {
|
||||
/**
|
||||
* If the `StatusEffect` that was set is Burn, Paralysis, Poison, or Toxic, and the status
|
||||
* was set by a source Pokemon, set the source Pokemon's status to the same `StatusEffect`.
|
||||
* @param pokemon {@linkcode Pokemon} that status condition was set on.
|
||||
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon.
|
||||
* @param passive Whether this ability is a passive.
|
||||
* @param effect {@linkcode StatusEffect} that was set.
|
||||
* @param args Set of unique arguments needed by this attribute.
|
||||
* @returns `true` if application of the ability succeeds.
|
||||
*/
|
||||
override applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]): boolean {
|
||||
/** Synchronizable statuses */
|
||||
const syncStatuses = new Set<StatusEffect>([
|
||||
StatusEffect.BURN,
|
||||
StatusEffect.PARALYSIS,
|
||||
StatusEffect.POISON,
|
||||
StatusEffect.TOXIC
|
||||
]);
|
||||
|
||||
if (sourcePokemon && syncStatuses.has(effect)) {
|
||||
if (!simulated) {
|
||||
sourcePokemon.trySetStatus(effect, true, pokemon);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostVictoryAbAttr extends AbAttr {
|
||||
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
return false;
|
||||
@ -4241,6 +4300,10 @@ export class ReduceBerryUseThresholdAbAttr extends AbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ability attribute used for abilites that change the ability owner's weight
|
||||
* Used for Heavy Metal (doubling weight) and Light Metal (halving weight)
|
||||
*/
|
||||
export class WeightMultiplierAbAttr extends AbAttr {
|
||||
private multiplier: integer;
|
||||
|
||||
@ -4677,6 +4740,10 @@ export function applyStatMultiplierAbAttrs(attrType: Constructor<StatMultiplierA
|
||||
pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise<void> {
|
||||
return applyAbAttrsInternal<StatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), args);
|
||||
}
|
||||
export function applyPostSetStatusAbAttrs(attrType: Constructor<PostSetStatusAbAttr>,
|
||||
pokemon: Pokemon, effect: StatusEffect, sourcePokemon?: Pokemon | null, simulated: boolean = false, ...args: any[]): Promise<void> {
|
||||
return applyAbAttrsInternal<PostSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, false, simulated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a field Stat multiplier attribute
|
||||
@ -4907,7 +4974,8 @@ export function initAbilities() {
|
||||
.attr(EffectSporeAbAttr),
|
||||
new Ability(Abilities.SYNCHRONIZE, 3)
|
||||
.attr(SyncEncounterNatureAbAttr)
|
||||
.unimplemented(),
|
||||
.attr(SynchronizeStatusAbAttr)
|
||||
.partial(), // interaction with psycho shift needs work, keeping to old Gen interaction for now
|
||||
new Ability(Abilities.CLEAR_BODY, 3)
|
||||
.attr(ProtectStatAbAttr)
|
||||
.ignorable(),
|
||||
@ -5879,6 +5947,6 @@ export function initAbilities() {
|
||||
new Ability(Abilities.POISON_PUPPETEER, 9)
|
||||
.attr(UncopiableAbilityAbAttr)
|
||||
.attr(UnswappableAbilityAbAttr)
|
||||
.conditionalAttr(pokemon => pokemon.species.speciesId===Species.PECHARUNT, ConfusionOnStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
||||
.attr(ConfusionOnStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
||||
);
|
||||
}
|
||||
|
@ -430,7 +430,7 @@ class AnimTimedAddBgEvent extends AnimTimedBgEvent {
|
||||
scene.field.add(moveAnim.bgSprite);
|
||||
const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon();
|
||||
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()) {
|
||||
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
|
||||
}
|
||||
@ -743,16 +743,21 @@ export abstract class BattleAnim {
|
||||
public target: Pokemon | null;
|
||||
public sprites: Phaser.GameObjects.Sprite[];
|
||||
public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle;
|
||||
public playOnEmptyField: boolean;
|
||||
/**
|
||||
* Will attempt to play as much of an animation as possible, even if not all targets are on the field.
|
||||
* Will also play the animation, even if the user has selected "Move Animations" OFF in Settings.
|
||||
* Exclusively used by MEs atm, for visual animations at the start of an encounter.
|
||||
*/
|
||||
public playRegardlessOfIssues: boolean;
|
||||
|
||||
private srcLine: number[];
|
||||
private dstLine: number[];
|
||||
|
||||
constructor(user?: Pokemon, target?: Pokemon, playOnEmptyField: boolean = false) {
|
||||
constructor(user?: Pokemon, target?: Pokemon, playRegardlessOfIssues: boolean = false) {
|
||||
this.user = user ?? null;
|
||||
this.target = target ?? null;
|
||||
this.sprites = [];
|
||||
this.playOnEmptyField = playOnEmptyField;
|
||||
this.playRegardlessOfIssues = playRegardlessOfIssues;
|
||||
}
|
||||
|
||||
abstract getAnim(): AnimConfig | null;
|
||||
@ -829,7 +834,7 @@ export abstract class BattleAnim {
|
||||
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
|
||||
const target = !isOppAnim ? this.target! : this.user!;
|
||||
|
||||
if (!target?.isOnField() && !this.playOnEmptyField) {
|
||||
if (!target?.isOnField() && !this.playRegardlessOfIssues) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
@ -896,7 +901,7 @@ export abstract class BattleAnim {
|
||||
}
|
||||
};
|
||||
|
||||
if (!scene.moveAnimations) {
|
||||
if (!scene.moveAnimations && !this.playRegardlessOfIssues) {
|
||||
return cleanUpAndComplete();
|
||||
}
|
||||
|
||||
@ -910,12 +915,12 @@ export abstract class BattleAnim {
|
||||
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
|
||||
this.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ];
|
||||
|
||||
let r = anim!.frames.length; // TODO: is this bang correct?
|
||||
let r = anim?.frames.length ?? 0;
|
||||
let f = 0;
|
||||
|
||||
scene.tweens.addCounter({
|
||||
duration: Utils.getFrameMs(3),
|
||||
repeat: anim!.frames.length, // TODO: is this bang correct?
|
||||
repeat: anim?.frames.length ?? 0,
|
||||
onRepeat: () => {
|
||||
if (!f) {
|
||||
userSprite.setVisible(false);
|
||||
@ -932,7 +937,7 @@ export abstract class BattleAnim {
|
||||
const isUser = frame.target === AnimFrameTarget.USER;
|
||||
if (isUser && target === user) {
|
||||
continue;
|
||||
} else if (this.playOnEmptyField && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
|
||||
} else if (this.playRegardlessOfIssues && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
|
||||
continue;
|
||||
}
|
||||
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];
|
||||
@ -1145,7 +1150,7 @@ export abstract class BattleAnim {
|
||||
}
|
||||
};
|
||||
|
||||
if (!scene.moveAnimations) {
|
||||
if (!scene.moveAnimations && !this.playRegardlessOfIssues) {
|
||||
return cleanUpAndComplete();
|
||||
}
|
||||
|
||||
@ -1259,7 +1264,7 @@ export class CommonBattleAnim extends BattleAnim {
|
||||
}
|
||||
|
||||
getAnim(): AnimConfig | null {
|
||||
return this.commonAnim ? commonAnims.get(this.commonAnim)! : null; // TODO: is this bang correct?
|
||||
return this.commonAnim ? commonAnims.get(this.commonAnim) ?? null : null;
|
||||
}
|
||||
|
||||
isOppAnim(): boolean {
|
||||
@ -1279,7 +1284,7 @@ export class MoveAnim extends BattleAnim {
|
||||
getAnim(): AnimConfig {
|
||||
return moveAnims.get(this.move) instanceof AnimConfig
|
||||
? moveAnims.get(this.move) as AnimConfig
|
||||
: moveAnims.get(this.move)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct?
|
||||
: moveAnims.get(this.move)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig;
|
||||
}
|
||||
|
||||
isOppAnim(): boolean {
|
||||
@ -1311,7 +1316,7 @@ export class MoveChargeAnim extends MoveAnim {
|
||||
getAnim(): AnimConfig {
|
||||
return chargeAnims.get(this.chargeAnim) instanceof AnimConfig
|
||||
? chargeAnims.get(this.chargeAnim) as AnimConfig
|
||||
: chargeAnims.get(this.chargeAnim)![this.user?.isPlayer() ? 0 : 1] as AnimConfig; // TODO: is this bang correct?
|
||||
: chargeAnims.get(this.chargeAnim)?.[this.user?.isPlayer() ? 0 : 1] as AnimConfig;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2281,6 +2281,36 @@ export class TarShotTag extends BattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Battler Tag that keeps track of how many times the user has Autotomized
|
||||
* Each count of Autotomization reduces the weight by 100kg
|
||||
*/
|
||||
export class AutotomizedTag extends BattlerTag {
|
||||
public autotomizeCount: number = 0;
|
||||
constructor(sourceMove: Moves = Moves.AUTOTOMIZE) {
|
||||
super(BattlerTagType.AUTOTOMIZED, BattlerTagLapseType.CUSTOM, 1, sourceMove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an autotomize count to the Pokemon. Each stack reduces weight by 100kg
|
||||
* If the Pokemon is over 0.1kg it also displays a message.
|
||||
* @param pokemon The Pokemon that is being autotomized
|
||||
*/
|
||||
onAdd(pokemon: Pokemon): void {
|
||||
const minWeight = 0.1;
|
||||
if (pokemon.getWeight() > minWeight) {
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:autotomizeOnAdd", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)
|
||||
}));
|
||||
}
|
||||
this.autotomizeCount += 1;
|
||||
}
|
||||
|
||||
onOverlap(pokemon: Pokemon): void {
|
||||
this.onAdd(pokemon);
|
||||
}
|
||||
}
|
||||
|
||||
export class SubstituteTag extends BattlerTag {
|
||||
/** The substitute's remaining HP. If HP is depleted, the Substitute fades. */
|
||||
public hp: number;
|
||||
@ -2568,6 +2598,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||
return new GorillaTacticsTag();
|
||||
case BattlerTagType.SUBSTITUTE:
|
||||
return new SubstituteTag(sourceMove, sourceId);
|
||||
case BattlerTagType.AUTOTOMIZED:
|
||||
return new AutotomizedTag();
|
||||
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
||||
return new MysteryEncounterPostSummonTag();
|
||||
case BattlerTagType.HEAL_BLOCK:
|
||||
|
@ -172,11 +172,9 @@ export abstract class Challenge {
|
||||
* @param overrideValue {@link integer} The value to check for. If undefined, gets the current value.
|
||||
* @returns {@link string} The localised name for the current value.
|
||||
*/
|
||||
getValue(overrideValue?: integer): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
return i18next.t(`challenges:${this.geti18nKey()}.value.${this.value}`);
|
||||
getValue(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
return i18next.t(`challenges:${this.geti18nKey()}.value.${value}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,11 +182,9 @@ export abstract class Challenge {
|
||||
* @param overrideValue {@link integer} The value to check for. If undefined, gets the current value.
|
||||
* @returns {@link string} The localised description for the current value.
|
||||
*/
|
||||
getDescription(overrideValue?: integer): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${this.value}`, `challenges:${this.geti18nKey()}.desc`])}`;
|
||||
getDescription(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc`])}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -451,30 +447,6 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean {
|
||||
let trainerTypes: TrainerType[] = [];
|
||||
switch (waveIndex) {
|
||||
case 35:
|
||||
trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ];
|
||||
break;
|
||||
case 62:
|
||||
trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ];
|
||||
break;
|
||||
case 64:
|
||||
trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ];
|
||||
break;
|
||||
case 66:
|
||||
trainerTypes = [ Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.TABITHA, TrainerType.COURTNEY, TrainerType.MATT, TrainerType.SHELLY ]), Utils.randSeedItem([ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ]), Utils.randSeedItem([ TrainerType.ZINZOLIN, TrainerType.ROOD ]), Utils.randSeedItem([ TrainerType.XEROSIC, TrainerType.BRYONY ]), Utils.randSeedItem([ TrainerType.FABA, TrainerType.PLUMERIA ]), TrainerType.OLEANA, Utils.randSeedItem([ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]) ];
|
||||
break;
|
||||
case 112:
|
||||
trainerTypes = [ TrainerType.ROCKET_GRUNT, TrainerType.ROCKET_GRUNT, Utils.randSeedItem([ TrainerType.MAGMA_GRUNT, TrainerType.AQUA_GRUNT ]), TrainerType.GALACTIC_GRUNT, TrainerType.PLASMA_GRUNT, TrainerType.FLARE_GRUNT, Utils.randSeedItem([ TrainerType.AETHER_GRUNT, TrainerType.SKULL_GRUNT ]), TrainerType.MACRO_GRUNT, TrainerType.STAR_GRUNT ];
|
||||
break;
|
||||
case 114:
|
||||
trainerTypes = [ Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.ARCHER, TrainerType.ARIANA, TrainerType.PROTON, TrainerType.PETREL ]), Utils.randSeedItem([ TrainerType.TABITHA, TrainerType.COURTNEY, TrainerType.MATT, TrainerType.SHELLY ]), Utils.randSeedItem([ TrainerType.JUPITER, TrainerType.MARS, TrainerType.SATURN ]), Utils.randSeedItem([ TrainerType.ZINZOLIN, TrainerType.ROOD ]), Utils.randSeedItem([ TrainerType.XEROSIC, TrainerType.BRYONY ]), Utils.randSeedItem([ TrainerType.FABA, TrainerType.PLUMERIA ]), TrainerType.OLEANA, Utils.randSeedItem([ TrainerType.GIACOMO, TrainerType.MELA, TrainerType.ATTICUS, TrainerType.ORTEGA, TrainerType.ERI ]) ];
|
||||
break;
|
||||
case 115:
|
||||
trainerTypes = [ TrainerType.ROCKET_BOSS_GIOVANNI_1, TrainerType.ROCKET_BOSS_GIOVANNI_1, Utils.randSeedItem([ TrainerType.MAXIE, TrainerType.ARCHIE ]), TrainerType.CYRUS, TrainerType.GHETSIS, TrainerType.LYSANDRE, Utils.randSeedItem([ TrainerType.LUSAMINE, TrainerType.GUZMA ]), TrainerType.ROSE, TrainerType.PENNY ];
|
||||
break;
|
||||
case 165:
|
||||
trainerTypes = [ TrainerType.ROCKET_BOSS_GIOVANNI_2, TrainerType.ROCKET_BOSS_GIOVANNI_2, Utils.randSeedItem([ TrainerType.MAXIE_2, TrainerType.ARCHIE_2 ]), TrainerType.CYRUS_2, TrainerType.GHETSIS_2, TrainerType.LYSANDRE_2, Utils.randSeedItem([ TrainerType.LUSAMINE_2, TrainerType.GUZMA_2 ]), TrainerType.ROSE_2, TrainerType.PENNY_2 ];
|
||||
break;
|
||||
case 182:
|
||||
trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ];
|
||||
break;
|
||||
@ -511,14 +483,12 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||
* @returns {string} The localised name for the current value.
|
||||
*/
|
||||
getValue(overrideValue?: integer): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
if (this.value === 0) {
|
||||
getValue(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
if (value === 0) {
|
||||
return i18next.t("settings:off");
|
||||
}
|
||||
return i18next.t(`starterSelectUiHandler:gen${this.value}`);
|
||||
return i18next.t(`starterSelectUiHandler:gen${value}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -526,14 +496,12 @@ export class SingleGenerationChallenge extends Challenge {
|
||||
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||
* @returns {string} The localised description for the current value.
|
||||
*/
|
||||
getDescription(overrideValue?: integer): string {
|
||||
if (overrideValue === undefined) {
|
||||
overrideValue = this.value;
|
||||
}
|
||||
if (this.value === 0) {
|
||||
getDescription(overrideValue?: number): string {
|
||||
const value = overrideValue ?? this.value;
|
||||
if (value === 0) {
|
||||
return i18next.t("challenges:singleGeneration.desc_default");
|
||||
}
|
||||
return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${this.value}`) });
|
||||
return i18next.t("challenges:singleGeneration.desc", { gen: i18next.t(`challenges:singleGeneration.gen_${value}`) });
|
||||
}
|
||||
|
||||
|
||||
|
138
src/data/move.ts
@ -650,7 +650,7 @@ export default class Move implements Localizable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies each {@linkcode MoveCondition} of this move to the params
|
||||
* Applies each {@linkcode MoveCondition} function of this move to the params, determines if the move can be used prior to calling each attribute's apply()
|
||||
* @param user {@linkcode Pokemon} to apply conditions to
|
||||
* @param target {@linkcode Pokemon} to apply conditions to
|
||||
* @param move {@linkcode Move} to apply conditions to
|
||||
@ -2091,21 +2091,20 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
||||
|
||||
if (target.status) {
|
||||
return false;
|
||||
}
|
||||
//@ts-ignore - how can target.status.effect be checked when we return `false` before when it's defined?
|
||||
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { // TODO: resolve ts-ignore
|
||||
const statusAfflictResult = target.trySetStatus(statusToApply, true, user);
|
||||
if (statusAfflictResult) {
|
||||
} else {
|
||||
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
||||
|
||||
if (canSetStatus) {
|
||||
if (user.status) {
|
||||
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
|
||||
}
|
||||
user.resetStatus();
|
||||
user.updateInfo();
|
||||
target.trySetStatus(statusToApply, true, user);
|
||||
}
|
||||
return statusAfflictResult;
|
||||
}
|
||||
|
||||
return false;
|
||||
return canSetStatus;
|
||||
}
|
||||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
@ -5175,31 +5174,29 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
private user: boolean;
|
||||
private batonPass: boolean;
|
||||
|
||||
constructor(user?: boolean, batonPass?: boolean) {
|
||||
constructor(
|
||||
private selfSwitch: boolean = false,
|
||||
private batonPass: boolean = false
|
||||
) {
|
||||
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
||||
this.user = !!user;
|
||||
this.batonPass = !!batonPass;
|
||||
}
|
||||
|
||||
isBatonPass() {
|
||||
return this.batonPass;
|
||||
}
|
||||
|
||||
// TODO: Why is this a Promise?
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
|
||||
// Check if the move category is not STATUS or if the switch out condition is not met
|
||||
if (!this.getSwitchOutCondition()(user, target, move)) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
// Move the switch out logic inside the conditional block
|
||||
// This ensures that the switch out only happens when the conditions are met
|
||||
const switchOutTarget = this.user ? user : target;
|
||||
if (switchOutTarget instanceof PlayerPokemon) {
|
||||
// Move the switch out logic inside the conditional block
|
||||
// This ensures that the switch out only happens when the conditions are met
|
||||
const switchOutTarget = this.selfSwitch ? user : target;
|
||||
if (switchOutTarget instanceof PlayerPokemon) {
|
||||
switchOutTarget.leaveField(!this.batonPass);
|
||||
|
||||
if (switchOutTarget.hp > 0) {
|
||||
@ -5208,41 +5205,43 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
return;
|
||||
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
||||
// Switch out logic for trainer battles
|
||||
return;
|
||||
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
||||
// Switch out logic for trainer battles
|
||||
switchOutTarget.leaveField(!this.batonPass);
|
||||
|
||||
if (switchOutTarget.hp > 0) {
|
||||
// for opponent switching out
|
||||
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(), (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, this.batonPass, false), MoveEndPhase);
|
||||
if (switchOutTarget.hp > 0) {
|
||||
// for opponent switching out
|
||||
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(),
|
||||
(user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
|
||||
false, this.batonPass, false), MoveEndPhase);
|
||||
}
|
||||
} else {
|
||||
// Switch out logic for everything else (eg: WILD battles)
|
||||
switchOutTarget.leaveField(false);
|
||||
} else {
|
||||
// Switch out logic for everything else (eg: WILD battles)
|
||||
switchOutTarget.leaveField(false);
|
||||
|
||||
if (switchOutTarget.hp) {
|
||||
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
|
||||
if (switchOutTarget.hp) {
|
||||
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
|
||||
|
||||
// in double battles redirect potential moves off fled pokemon
|
||||
if (switchOutTarget.scene.currentBattle.double) {
|
||||
const allyPokemon = switchOutTarget.getAlly();
|
||||
switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!switchOutTarget.getAlly()?.isActive(true)) {
|
||||
user.scene.clearEnemyHeldItemModifiers();
|
||||
if (!switchOutTarget.getAlly()?.isActive(true)) {
|
||||
user.scene.clearEnemyHeldItemModifiers();
|
||||
|
||||
if (switchOutTarget.hp) {
|
||||
user.scene.pushPhase(new BattleEndPhase(user.scene));
|
||||
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (switchOutTarget.hp) {
|
||||
user.scene.pushPhase(new BattleEndPhase(user.scene));
|
||||
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
@ -5257,29 +5256,33 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
|
||||
getSwitchOutCondition(): MoveConditionFunc {
|
||||
return (user, target, move) => {
|
||||
const switchOutTarget = (this.user ? user : target);
|
||||
const switchOutTarget = (this.selfSwitch ? user : target);
|
||||
const player = switchOutTarget instanceof PlayerPokemon;
|
||||
|
||||
if (!this.user && move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
if (!this.selfSwitch) {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blockedByAbility = new Utils.BooleanHolder(false);
|
||||
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility);
|
||||
return !blockedByAbility.value;
|
||||
}
|
||||
|
||||
if (!this.user && move.category === MoveCategory.STATUS && (target.hasAbilityWithAttr(ForceSwitchOutImmunityAbAttr))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!player && !user.scene.currentBattle.battleType) {
|
||||
if (!player && user.scene.currentBattle.battleType === BattleType.WILD) {
|
||||
if (this.batonPass) {
|
||||
return false;
|
||||
}
|
||||
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
|
||||
if (!(user.scene.currentBattle.waveIndex % 10)) {
|
||||
if (user.scene.currentBattle.waveIndex % 10 === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const party = player ? user.scene.getParty() : user.scene.getEnemyParty();
|
||||
return (!player && !user.scene.currentBattle.battleType) || party.filter(p => p.isAllowedInBattle() && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount();
|
||||
return (!player && !user.scene.currentBattle.battleType)
|
||||
|| party.filter(p => p.isAllowedInBattle()
|
||||
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount();
|
||||
};
|
||||
}
|
||||
|
||||
@ -5287,8 +5290,8 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
if (!user.scene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) {
|
||||
return -20;
|
||||
}
|
||||
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
||||
if (this.user && this.batonPass) {
|
||||
let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
||||
if (this.selfSwitch && this.batonPass) {
|
||||
const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0);
|
||||
ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10));
|
||||
}
|
||||
@ -5296,6 +5299,21 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ChillyReceptionAttr extends ForceSwitchOutAttr {
|
||||
|
||||
// using inherited constructor
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
user.scene.arena.trySetWeather(WeatherType.SNOW, true);
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
// chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail
|
||||
return (user, target, move) => user.scene.arena.trySetWeather(WeatherType.SNOW, true) || super.getSwitchOutCondition()(user, target, move);
|
||||
}
|
||||
}
|
||||
export class RemoveTypeAttr extends MoveEffectAttr {
|
||||
|
||||
private removedType: Type;
|
||||
@ -8091,7 +8109,7 @@ export function initMoves() {
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1),
|
||||
new SelfStatusMove(Moves.AUTOTOMIZE, Type.STEEL, -1, 15, -1, 0, 5)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true)
|
||||
.partial(),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.AUTOTOMIZED, true),
|
||||
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
|
||||
.powderMove()
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
|
||||
@ -9073,8 +9091,7 @@ export function initMoves() {
|
||||
new AttackMove(Moves.AURA_WHEEL, Type.ELECTRIC, MoveCategory.PHYSICAL, 110, 100, 10, 100, 0, 8)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
|
||||
.makesContact(false)
|
||||
.attr(AuraWheelTypeAttr)
|
||||
.condition((user, target, move) => [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)), // Missing custom fail message
|
||||
.attr(AuraWheelTypeAttr),
|
||||
new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
||||
@ -9486,10 +9503,9 @@ export function initMoves() {
|
||||
.makesContact(),
|
||||
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||
.unimplemented(),
|
||||
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
||||
.attr(WeatherChangeAttr, WeatherType.SNOW)
|
||||
.attr(ForceSwitchOutAttr, true, false)
|
||||
.target(MoveTarget.BOTH_SIDES),
|
||||
new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
||||
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", {pokemonName: getPokemonNameWithAffix(user)}))
|
||||
.attr(ChillyReceptionAttr, true, false),
|
||||
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
|
||||
.attr(RemoveArenaTrapAttr, true)
|
||||
|
@ -15,6 +15,7 @@ import { EggTier } from "#enums/egg-type";
|
||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:aTrainersTest";
|
||||
@ -27,7 +28,7 @@ const namespace = "mysteryEncounter:aTrainersTest";
|
||||
export const ATrainersTestEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withSceneWaveRangeRequirement(100, 180)
|
||||
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||
.withIntroDialogue([
|
||||
{
|
||||
|
@ -10,7 +10,7 @@ import { PersistentModifierRequirement } from "#app/data/mystery-encounters/myst
|
||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { BerryModifier } from "#app/modifier/modifier";
|
||||
import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
@ -159,12 +159,6 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
])
|
||||
.withHideWildIntroMessage(true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
doGreedentSpriteSteal(scene);
|
||||
doBerrySpritePile(scene);
|
||||
|
||||
return true;
|
||||
})
|
||||
.withIntroDialogue([
|
||||
{
|
||||
text: `${namespace}.intro`,
|
||||
@ -202,10 +196,15 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
|
||||
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
|
||||
const config: EnemyPartyConfig = {
|
||||
levelAdditiveModifier: 1,
|
||||
@ -214,12 +213,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
species: getPokemonSpecies(Species.GREEDENT),
|
||||
isBoss: true,
|
||||
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,
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
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;
|
||||
})
|
||||
.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(
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||
@ -251,7 +265,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
const givePartyPokemonReviverSeeds = () => {
|
||||
const party = scene.getParty();
|
||||
party.forEach(p => {
|
||||
if (revSeed) {
|
||||
const heldItems = p.getHeldItems();
|
||||
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
|
||||
const seedModifier = revSeed.newModifier(p);
|
||||
if (seedModifier) {
|
||||
encounter.setDialogueToken("foodReward", seedModifier.type.name);
|
||||
@ -338,7 +353,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Let it have the food
|
||||
// Greedent joins the team, level equal to 2 below highest party member
|
||||
const level = getHighestLevelPlayerPokemon(scene).level - 2;
|
||||
const level = getHighestLevelPlayerPokemon(scene, false, true).level - 2;
|
||||
const greedent = new EnemyPokemon(scene, getPokemonSpecies(Species.GREEDENT), level, TrainerSlot.NONE, false);
|
||||
greedent.moveset = [new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF)];
|
||||
greedent.passive = true;
|
||||
|
@ -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 { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
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 */
|
||||
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.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
|
||||
@ -26,7 +34,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.LIEPARD.toString(),
|
||||
@ -60,8 +68,12 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
|
||||
const price = scene.getWaveMoneyAmount(10);
|
||||
const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true);
|
||||
|
||||
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("price", price.toString());
|
||||
|
@ -82,7 +82,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||
encounter.spriteConfigs = [
|
||||
{
|
||||
spriteKey: "berry_bush",
|
||||
spriteKey: "berries_abound_bush",
|
||||
fileRoot: "mystery-encounters",
|
||||
x: 25,
|
||||
y: -6,
|
||||
@ -102,7 +102,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
];
|
||||
|
||||
// Get fastest party pokemon for option 2
|
||||
const fastestPokemon = getHighestStatPlayerPokemon(scene, PERMANENT_STATS[Stat.SPD], true);
|
||||
const fastestPokemon = getHighestStatPlayerPokemon(scene, PERMANENT_STATS[Stat.SPD], true, false);
|
||||
encounter.misc.fastestPokemon = fastestPokemon;
|
||||
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
||||
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
||||
@ -127,7 +127,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const numBerries = encounter.misc.numBerries;
|
||||
|
||||
const doBerryRewards = async () => {
|
||||
const doBerryRewards = () => {
|
||||
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
@ -135,7 +135,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
for (let i = 0; i < numBerries; i++) {
|
||||
await tryGiveBerry(scene);
|
||||
tryGiveBerry(scene);
|
||||
}
|
||||
};
|
||||
|
||||
@ -178,7 +178,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
|
||||
if (speedDiff < 1) {
|
||||
// Caught and attacked by boss, gets +1 to all stats at start of fight
|
||||
const doBerryRewards = async () => {
|
||||
const doBerryRewards = () => {
|
||||
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
@ -186,15 +186,20 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
|
||||
// Generate a random berry and give it to the first Pokemon with room for it
|
||||
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];
|
||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||
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);
|
||||
await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
|
||||
@ -204,7 +209,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
// Gains 1 berry for every 10% faster the player's pokemon is than the enemy, up to a max of numBerries, minimum of 2
|
||||
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1)/0.08), numBerries), 2);
|
||||
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
||||
const doFasterBerryRewards = async () => {
|
||||
const doFasterBerryRewards = () => {
|
||||
const berryText = numBerriesGrabbed + " " + i18next.t(`${namespace}.berries`);
|
||||
|
||||
scene.playSound("item_fanfare");
|
||||
@ -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)
|
||||
for (let i = 0; i < numBerriesGrabbed; i++) {
|
||||
await tryGiveBerry(scene, fastestPokemon);
|
||||
tryGiveBerry(scene, fastestPokemon);
|
||||
}
|
||||
};
|
||||
|
||||
@ -242,7 +247,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
)
|
||||
.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 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;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
|
||||
applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -265,7 +270,7 @@ async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokem
|
||||
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||
|
||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
||||
await applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
|
||||
applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -376,9 +376,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(item => {
|
||||
return item instanceof BypassSpeedChanceModifier ||
|
||||
return (item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG);
|
||||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG)) &&
|
||||
item.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
@ -491,7 +492,7 @@ function getTrainerConfigForWave(waveIndex: number) {
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex!;
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
@ -514,14 +515,14 @@ function getTrainerConfigForWave(waveIndex: number) {
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex!;
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
|
||||
p.formIndex = pool3Mon2.formIndex!;
|
||||
p.formIndex = pool3Mon2.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
@ -542,7 +543,7 @@ function getTrainerConfigForWave(waveIndex: number) {
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex!;
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
@ -565,14 +566,14 @@ function getTrainerConfigForWave(waveIndex: number) {
|
||||
}))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex!;
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||
p.formIndex = pool3Mon.formIndex!;
|
||||
p.formIndex = pool3Mon.formIndex;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
}
|
||||
|
@ -29,8 +29,9 @@ import { Moves } from "#enums/moves";
|
||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||
import { MoveCategory } from "#app/data/move";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, GameModes } from "#app/game-mode";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:clowningAround";
|
||||
@ -61,7 +62,7 @@ const RANDOM_ABILITY_POOL = [
|
||||
export const ClowningAroundEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.withDisabledGameModes(GameModes.CHALLENGE)
|
||||
.withDisallowedChallenges(Challenges.SINGLE_TYPE)
|
||||
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withAnimations(EncounterAnim.SMOKESCREEN)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
@ -349,10 +350,18 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
||||
}
|
||||
}
|
||||
newTypes.push(secondType);
|
||||
|
||||
// Apply the type changes (to both base and fusion, if pokemon is fused)
|
||||
if (!pokemon.mysteryEncounterPokemonData) {
|
||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.mysteryEncounterPokemonData.types = newTypes;
|
||||
if (pokemon.isFusion()) {
|
||||
if (!pokemon.fusionMysteryEncounterPokemonData) {
|
||||
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.fusionMysteryEncounterPokemonData.types = newTypes;
|
||||
}
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
@ -415,10 +424,17 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Do ability swap
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
if (!pokemon.mysteryEncounterPokemonData) {
|
||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
if (pokemon.isFusion()) {
|
||||
if (!pokemon.fusionMysteryEncounterPokemonData) {
|
||||
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability;
|
||||
} else {
|
||||
if (!pokemon.mysteryEncounterPokemonData) {
|
||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability;
|
||||
}
|
||||
pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
||||
};
|
||||
|
@ -27,6 +27,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:dancingLessons";
|
||||
@ -268,9 +269,12 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that have a Dancing move can be selected
|
||||
// Only challenge legal/unfainted Pokemon that have a Dancing move can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
|
@ -94,7 +94,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "mad_scientist_m",
|
||||
spriteKey: "dark_deal_scientist",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
@ -115,7 +115,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
||||
},
|
||||
])
|
||||
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||
.withScenePartySizeRequirement(2, 6) // Must have at least 2 pokemon in party
|
||||
.withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party
|
||||
.withCatchAllowed(true)
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
@ -139,7 +139,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
|
||||
// Will never return last battle able mon and instead pick fainted/unable to battle
|
||||
const removedPokemon = getRandomPlayerPokemon(scene, false, true);
|
||||
const removedPokemon = getRandomPlayerPokemon(scene, true, false, true);
|
||||
// Get all the pokemon's held items
|
||||
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||
scene.removePokemonFromPlayerParty(removedPokemon);
|
||||
|
@ -10,7 +10,7 @@ import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement }
|
||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { 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 { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import i18next from "#app/plugins/i18n";
|
||||
@ -33,7 +33,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
|
||||
"PokemonBaseStatTotalModifier"
|
||||
];
|
||||
|
||||
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 1.5;
|
||||
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
|
||||
|
||||
/**
|
||||
* Delibird-y encounter.
|
||||
@ -122,9 +122,9 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
return true;
|
||||
})
|
||||
.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
|
||||
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)) {
|
||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||
@ -133,7 +133,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
scene.playSound("item_fanfare");
|
||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||
} else {
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.AMULET_COIN));
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
@ -159,7 +159,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
@ -179,9 +179,8 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
@ -197,7 +196,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const modifier = encounter.misc.chosenModifier;
|
||||
|
||||
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
|
||||
if (modifier.type.name.includes("Berry")) {
|
||||
if (modifier instanceof BerryModifier) {
|
||||
// Check if the player has max stacks of that Candy Jar already
|
||||
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
|
||||
|
||||
@ -254,7 +253,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
@ -274,9 +273,8 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
|
@ -27,7 +27,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
|
||||
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "b2w2_lady",
|
||||
spriteKey: "department_store_sale_lady",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -20,
|
||||
@ -63,7 +63,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
|
||||
// Choose TMs
|
||||
const modifiers: ModifierTypeFunc[] = [];
|
||||
let i = 0;
|
||||
while (i < 4) {
|
||||
while (i < 5) {
|
||||
// 2/2/1 weight on TM rarity
|
||||
const roll = randSeedInt(5);
|
||||
if (roll < 2) {
|
||||
|
@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:fieldTrip";
|
||||
export const FieldTripEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "preschooler_m",
|
||||
@ -32,7 +32,7 @@ export const FieldTripEncounter: MysteryEncounter =
|
||||
hasShadow: true,
|
||||
},
|
||||
{
|
||||
spriteKey: "teacher",
|
||||
spriteKey: "field_trip_teacher",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
},
|
||||
|
@ -189,7 +189,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
|
||||
}
|
||||
|
||||
// Burn random member
|
||||
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.BURN);
|
||||
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE);
|
||||
if (burnable?.length > 0) {
|
||||
const roll = randSeedInt(burnable.length);
|
||||
const chosenPokemon = burnable[roll];
|
||||
|
@ -68,6 +68,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
||||
// Randomly boost 1 stat 2 stages
|
||||
// Cannot boost Spd, Acc, or Evasion
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2));
|
||||
}
|
||||
}],
|
||||
|
@ -7,7 +7,7 @@ import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Species } from "#enums/species";
|
||||
@ -22,6 +22,7 @@ import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:funAndGames";
|
||||
@ -45,14 +46,14 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
||||
.withSkipToFightInput(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "carnival_game",
|
||||
spriteKey: "fun_and_games_game",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 0,
|
||||
y: 6,
|
||||
},
|
||||
{
|
||||
spriteKey: "carnival_wobbuffet",
|
||||
spriteKey: "fun_and_games_wobbuffet",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -28,
|
||||
@ -60,7 +61,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
||||
yShadow: 6
|
||||
},
|
||||
{
|
||||
spriteKey: "carnival_man",
|
||||
spriteKey: "fun_and_games_man",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 40,
|
||||
@ -110,12 +111,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -11,9 +11,10 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon
|
||||
import { getTypeRgb } from "#app/data/type";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import * as Utils from "#app/utils";
|
||||
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import i18next from "i18next";
|
||||
@ -28,6 +29,11 @@ import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-en
|
||||
/** the i18n namespace for the encounter */
|
||||
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 = {
|
||||
1: [Species.RATTATA, Species.PIDGEY, Species.WEEDLE],
|
||||
2: [Species.SENTRET, Species.HOOTHOOT, Species.LEDYBA],
|
||||
@ -153,7 +159,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
return true;
|
||||
},
|
||||
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 line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
|
||||
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);
|
||||
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
||||
if (!tradePokemon.shiny) {
|
||||
// 512/65536 -> 1/128
|
||||
tradePokemon.trySetShinySeed(512, true);
|
||||
const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE);
|
||||
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)
|
||||
if (pokemon.species.abilityHidden) {
|
||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
||||
if (pokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
|
||||
if (tradePokemon.species.abilityHidden) {
|
||||
if (tradePokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new IntegerHolder(64);
|
||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -317,7 +349,6 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has items to trade
|
||||
const meetsReqs = pokemon.getHeldItems().filter((it) => {
|
||||
@ -451,7 +482,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?:
|
||||
if (validSpecies?.length > 20) {
|
||||
validSpecies = randSeedShuffle(validSpecies);
|
||||
newSpecies = validSpecies.pop();
|
||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
|
||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) {
|
||||
newSpecies = validSpecies.pop();
|
||||
}
|
||||
} else {
|
||||
|
@ -33,7 +33,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "buoy",
|
||||
spriteKey: "lost_at_sea_buoy",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
x: 20,
|
||||
|
@ -19,10 +19,11 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
const namespace = "mysteryEncounter:mysteriousChest";
|
||||
|
||||
const RAND_LENGTH = 100;
|
||||
const COMMON_REWARDS_WEIGHT = 20; // 20%
|
||||
const ULTRA_REWARDS_WEIGHT = 50; // 30%
|
||||
const ROGUE_REWARDS_WEIGHT = 60; // 10%
|
||||
const MASTER_REWARDS_WEIGHT = 65; // 5%
|
||||
const TRAP_PERCENT = 35;
|
||||
const COMMON_REWARDS_PERCENT = 20;
|
||||
const ULTRA_REWARDS_PERCENT = 30;
|
||||
const ROGUE_REWARDS_PERCENT = 10;
|
||||
const MASTER_REWARDS_PERCENT = 5;
|
||||
|
||||
/**
|
||||
* Mysterious Chest encounter.
|
||||
@ -37,7 +38,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "chest_blue",
|
||||
spriteKey: "mysterious_chest_blue",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 8,
|
||||
@ -46,7 +47,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
disableAnimation: true, // Re-enabled after option select
|
||||
},
|
||||
{
|
||||
spriteKey: "chest_red",
|
||||
spriteKey: "mysterious_chest_red",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 8,
|
||||
@ -83,6 +84,11 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
encounter.enemyPartyConfigs = [config];
|
||||
|
||||
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;
|
||||
})
|
||||
@ -109,7 +115,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
roll
|
||||
};
|
||||
|
||||
if (roll >= MASTER_REWARDS_WEIGHT) {
|
||||
if (roll < TRAP_PERCENT) {
|
||||
// Chest is springing trap, change to red chest sprite
|
||||
const blueChestSprites = introVisuals.getSpriteAtIndex(0);
|
||||
const redChestSprites = introVisuals.getSpriteAtIndex(1);
|
||||
@ -124,7 +130,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
// Open the chest
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
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%)
|
||||
setEncounterRewards(scene, {
|
||||
guaranteedModifierTiers: [
|
||||
@ -137,7 +143,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.normal`);
|
||||
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%)
|
||||
setEncounterRewards(scene, {
|
||||
guaranteedModifierTiers: [
|
||||
@ -149,13 +155,13 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.good`);
|
||||
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%)
|
||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE] });
|
||||
// Display result message then proceed to rewards
|
||||
queueEncounterMessage(scene, `${namespace}.option.1.great`);
|
||||
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%)
|
||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER] });
|
||||
// Display result message then proceed to rewards
|
||||
@ -163,11 +169,12 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
} else {
|
||||
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
|
||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(
|
||||
scene,
|
||||
true
|
||||
);
|
||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(scene, true, false);
|
||||
koPlayerPokemon(scene, highestLevelPokemon);
|
||||
|
||||
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
||||
|
||||
// Handle game over edge case
|
||||
const allowedPokemon = scene.getParty().filter(p => p.isAllowedInBattle());
|
||||
if (allowedPokemon.length === 0) {
|
||||
@ -176,8 +183,6 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
scene.unshiftPhase(new GameOverPhase(scene));
|
||||
} else {
|
||||
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
||||
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
|
@ -8,10 +8,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { getEncounterText, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import i18next from "i18next";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:partTimer";
|
||||
@ -27,7 +28,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "warehouse_crate",
|
||||
spriteKey: "part_timer_crate",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: false,
|
||||
y: 6,
|
||||
@ -117,11 +118,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
@ -198,11 +195,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -27,6 +27,8 @@ const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
|
||||
|
||||
const SAFARI_MONEY_MULTIPLIER = 2;
|
||||
|
||||
const NUM_SAFARI_ENCOUNTERS = 3;
|
||||
|
||||
/**
|
||||
* Safari Zone encounter.
|
||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800}
|
||||
@ -55,6 +57,10 @@ export const SafariZoneEncounter: MysteryEncounter =
|
||||
.withTitle(`${namespace}.title`)
|
||||
.withDescription(`${namespace}.description`)
|
||||
.withQuery(`${namespace}.query`)
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
|
||||
return true;
|
||||
})
|
||||
.withOption(MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
|
||||
@ -72,15 +78,15 @@ export const SafariZoneEncounter: MysteryEncounter =
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
encounter.continuousEncounter = true;
|
||||
encounter.misc = {
|
||||
safariPokemonRemaining: 3
|
||||
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS
|
||||
};
|
||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||
// Load bait/mud assets
|
||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
||||
scene.loadSe("PRSFX- Sludge Bomb2", "battle_anims", "PRSFX- Sludge Bomb2.wav");
|
||||
scene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav");
|
||||
scene.loadAtlas("bait", "mystery-encounters");
|
||||
scene.loadAtlas("mud", "mystery-encounters");
|
||||
scene.loadAtlas("safari_zone_bait", "mystery-encounters");
|
||||
scene.loadAtlas("safari_zone_mud", "mystery-encounters");
|
||||
// Clear enemy party
|
||||
scene.currentBattle.enemyParty = [];
|
||||
await transitionMysteryEncounterIntroVisuals(scene);
|
||||
@ -254,7 +260,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
||||
let enemySpecies;
|
||||
let pokemon;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||
const level = scene.currentBattle.getLevelForWave();
|
||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
||||
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
||||
@ -282,7 +288,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
||||
pokemon.calculateStats();
|
||||
|
||||
scene.currentBattle.enemyParty.unshift(pokemon);
|
||||
}, scene.currentBattle.waveIndex * 1000 + encounter.misc.safariPokemonRemaining);
|
||||
}, scene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining);
|
||||
|
||||
scene.gameData.setPokemonSeen(pokemon, true);
|
||||
await pokemon.loadAssets();
|
||||
@ -322,7 +328,7 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "bait", "0001.png");
|
||||
const bait: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 25, "safari_zone_bait", "0001.png");
|
||||
bait.setOrigin(0.5, 0.625);
|
||||
scene.field.add(bait);
|
||||
|
||||
@ -388,7 +394,7 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<bool
|
||||
const originalY: number = pokemon.y;
|
||||
|
||||
const fpOffset = pokemon.getFieldPositionOffset();
|
||||
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 35, "mud", "0001.png");
|
||||
const mud: Phaser.GameObjects.Sprite = scene.addFieldSprite(16 + 75, 80 + 35, "safari_zone_mud", "0001.png");
|
||||
mud.setOrigin(0.5, 0.625);
|
||||
scene.field.add(mud);
|
||||
|
||||
|
@ -9,12 +9,13 @@ import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-enc
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { getNatureName } from "#app/data/nature";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:shadyVitaminDealer";
|
||||
@ -32,7 +33,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
|
||||
.withPrimaryPokemonHealthRatioRequirement([0.5, 1]) // At least 1 Pokemon must have above half HP
|
||||
.withPrimaryPokemonHealthRatioRequirement([0.51, 1]) // At least 1 Pokemon must have above half HP
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.KROOKODILE.toString(),
|
||||
@ -44,7 +45,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
yShadow: -5
|
||||
},
|
||||
{
|
||||
spriteKey: "b2w2_veteran_m",
|
||||
spriteKey: "shady_vitamin_dealer",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: -12,
|
||||
@ -98,8 +99,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
// Only Pokemon that can gain benefits are above half HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
if (!pokemon.isAllowed()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
if (!encounter.pokemonMeetsPrimaryRequirements(scene, pokemon)) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
@ -175,13 +178,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that can gain benefits are unfainted
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon is unfainted it can be selected
|
||||
const meetsReqs = !pokemon.isFainted(true);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -39,12 +39,12 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.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)
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "teleporter",
|
||||
spriteKey: "teleporting_hijinks_teleporter",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
x: 4,
|
||||
@ -73,7 +73,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
|
||||
.withOption(
|
||||
MysteryEncounterOptionBuilder
|
||||
.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({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
@ -171,6 +171,12 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
|
||||
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
|
||||
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
|
||||
// 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 = {
|
||||
pokemonConfigs: [{
|
||||
level: level,
|
||||
@ -180,7 +186,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.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));
|
||||
}
|
||||
}],
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { EnemyPartyConfig, generateModifierType, handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs } from "#app/data/trainer-config";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { randSeedShuffle } from "#app/utils";
|
||||
@ -14,8 +13,6 @@ import { Species } from "#enums/species";
|
||||
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Type } from "#app/data/type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { IEggOptions } from "#app/data/egg";
|
||||
@ -24,15 +21,18 @@ import { EggTier } from "#enums/egg-type";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { achvs } from "#app/system/achv";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { Type } from "#app/data/type";
|
||||
import { getPokeballTintColor } from "#app/data/pokeball";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:expertPokemonBreeder";
|
||||
|
||||
const trainerNameKey = "trainerNames:expert_pokemon_breeder";
|
||||
|
||||
const FIRST_STAGE_EVOLUTION_WAVE = 30;
|
||||
const SECOND_STAGE_EVOLUTION_WAVE = 45;
|
||||
const FINAL_STAGE_EVOLUTION_WAVE = 60;
|
||||
const FIRST_STAGE_EVOLUTION_WAVE = 45;
|
||||
const SECOND_STAGE_EVOLUTION_WAVE = 60;
|
||||
const FINAL_STAGE_EVOLUTION_WAVE = 75;
|
||||
|
||||
const FRIENDSHIP_ADDED = 20;
|
||||
|
||||
@ -216,6 +216,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon1;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
|
||||
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
|
||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
||||
@ -241,14 +242,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
});
|
||||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Give achievement if in Space biome
|
||||
checkAchievement(scene);
|
||||
// Give 20 friendship to the chosen pokemon
|
||||
scene.currentBattle.mysteryEncounter!.misc.pokemon1.addFriendship(FRIENDSHIP_ADDED);
|
||||
await restorePartyAndHeldItems(scene);
|
||||
await doPostEncounterCleanup(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
@ -270,6 +268,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon2;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
|
||||
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
|
||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
||||
@ -295,14 +294,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
});
|
||||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Give achievement if in Space biome
|
||||
checkAchievement(scene);
|
||||
// Give 20 friendship to the chosen pokemon
|
||||
scene.currentBattle.mysteryEncounter!.misc.pokemon2.addFriendship(FRIENDSHIP_ADDED);
|
||||
await restorePartyAndHeldItems(scene);
|
||||
await doPostEncounterCleanup(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
@ -324,6 +320,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||
|
||||
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
|
||||
encounter.misc.chosenPokemon = pokemon3;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
|
||||
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
|
||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
||||
@ -349,19 +346,17 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
});
|
||||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Give achievement if in Space biome
|
||||
checkAchievement(scene);
|
||||
// Give 20 friendship to the chosen pokemon
|
||||
scene.currentBattle.mysteryEncounter!.misc.pokemon3.addFriendship(FRIENDSHIP_ADDED);
|
||||
await restorePartyAndHeldItems(scene);
|
||||
await doPostEncounterCleanup(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.withOutroDialogue([
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}.outro`,
|
||||
},
|
||||
])
|
||||
@ -390,15 +385,7 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(scene, modifierTypes.TERA_SHARD, [Type.STEEL]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.ATK]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1 + Math.floor(waveIndex / 20), // +1 Protein every 20 waves
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1 + Math.floor(waveIndex / 40), // +1 Carbos every 40 waves
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -547,3 +534,83 @@ async function restorePartyAndHeldItems(scene: BattleScene) {
|
||||
});
|
||||
await scene.updateModifiers(true);
|
||||
}
|
||||
|
||||
function onGameOver(scene: BattleScene) {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
speaker: trainerNameKey,
|
||||
text: `${namespace}.outro_failed`,
|
||||
},
|
||||
];
|
||||
|
||||
// Restore original party, player loses all friendship with chosen mon (it remains fainted)
|
||||
restorePartyAndHeldItems(scene);
|
||||
const chosenPokemon = encounter.misc.chosenPokemon;
|
||||
chosenPokemon.friendship = 0;
|
||||
|
||||
// Clear all rewards that would have been earned
|
||||
encounter.doEncounterRewards = undefined;
|
||||
|
||||
// Set flag that encounter was failed
|
||||
encounter.misc.encounterFailed = true;
|
||||
|
||||
// Revert BGM
|
||||
scene.playBgm(scene.arena.bgm);
|
||||
|
||||
// Return enemy Pokemon
|
||||
const pokemon = scene.getEnemyPokemon();
|
||||
if (pokemon) {
|
||||
scene.playSound("se/pb_rel");
|
||||
pokemon.hideInfo();
|
||||
pokemon.tint(getPokeballTintColor(pokemon.pokeball), 1, 250, "Sine.easeIn");
|
||||
scene.tweens.add({
|
||||
targets: pokemon,
|
||||
duration: 250,
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.5,
|
||||
onComplete: () => {
|
||||
scene.field.remove(pokemon, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show the enemy trainer
|
||||
scene.time.delayedCall(250, () => {
|
||||
const sprites = scene.currentBattle.trainer?.getSprites();
|
||||
const tintSprites = scene.currentBattle.trainer?.getTintSprites();
|
||||
if (sprites && tintSprites) {
|
||||
for (let i = 0; i < sprites.length; i++) {
|
||||
sprites[i].setVisible(true);
|
||||
tintSprites[i].setVisible(true);
|
||||
sprites[i].clearTint();
|
||||
tintSprites[i].clearTint();
|
||||
}
|
||||
}
|
||||
scene.tweens.add({
|
||||
targets: scene.currentBattle.trainer,
|
||||
x: "-=16",
|
||||
y: "+=16",
|
||||
alpha: 1,
|
||||
ease: "Sine.easeInOut",
|
||||
duration: 750
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
handleMysteryEncounterBattleFailed(scene, true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function doPostEncounterCleanup(scene: BattleScene) {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
if (!encounter.misc.encounterFailed) {
|
||||
// Give achievement if in Space biome
|
||||
checkAchievement(scene);
|
||||
// Give 20 friendship to the chosen pokemon
|
||||
encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED);
|
||||
await restorePartyAndHeldItems(scene);
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
|
||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||
.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)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
@ -58,12 +58,13 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
const tries = 0;
|
||||
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||
let tries = 0;
|
||||
|
||||
// Reroll any species that don't have HAs
|
||||
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||
tries++;
|
||||
}
|
||||
|
||||
let pokemon: PlayerPokemon;
|
||||
@ -71,7 +72,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
// If no HA mon found or you roll 1%, give shiny Magikarp
|
||||
species = getPokemonSpecies(Species.MAGIKARP);
|
||||
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 {
|
||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
|
||||
@ -113,7 +114,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
MysteryEncounterOptionBuilder
|
||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||
.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({
|
||||
buttonLabel: `${namespace}.option.1.label`,
|
||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||
|
@ -33,7 +33,7 @@ const BST_INCREASE_VALUE = 10;
|
||||
*/
|
||||
export const TheStrongStuffEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
|
||||
.withMaxAllowedEncounters(1)
|
||||
|
@ -13,13 +13,14 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
import i18next from "i18next";
|
||||
import { getStatKey } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** The i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:trainingSession";
|
||||
@ -38,7 +39,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
.withHideWildIntroMessage(true)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: "training_gear",
|
||||
spriteKey: "training_session_gear",
|
||||
fileRoot: "mystery-encounters",
|
||||
hasShadow: true,
|
||||
y: 6,
|
||||
@ -77,12 +78,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
@ -211,12 +207,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
@ -307,12 +298,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
@ -338,21 +324,18 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
const abilityIndex = encounter.misc.abilityIndex;
|
||||
if (!!playerPokemon.getFusionSpeciesForm()) {
|
||||
playerPokemon.fusionAbilityIndex = abilityIndex;
|
||||
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies!.speciesId)) {
|
||||
scene.gameData.starterData[playerPokemon.fusionSpecies!.speciesId]
|
||||
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies.speciesId)) {
|
||||
scene.gameData.starterData[playerPokemon.fusionSpecies.speciesId]
|
||||
.abilityAttr |=
|
||||
abilityIndex !== 1 || playerPokemon.fusionSpecies!.ability2
|
||||
abilityIndex !== 1 || playerPokemon.fusionSpecies.ability2
|
||||
? Math.pow(2, playerPokemon.fusionAbilityIndex)
|
||||
: AbilityAttr.ABILITY_HIDDEN;
|
||||
}
|
||||
} else {
|
||||
playerPokemon.abilityIndex = abilityIndex;
|
||||
if (
|
||||
speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)
|
||||
) {
|
||||
scene.gameData.starterData[
|
||||
playerPokemon.species.speciesId
|
||||
].abilityAttr |=
|
||||
if (speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)) {
|
||||
scene.gameData.starterData[playerPokemon.species.speciesId]
|
||||
.abilityAttr |=
|
||||
abilityIndex !== 1 || playerPokemon.species.ability2
|
||||
? Math.pow(2, playerPokemon.abilityIndex)
|
||||
: AbilityAttr.ABILITY_HIDDEN;
|
||||
|
@ -16,7 +16,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { PokemonMove } from "#app/field/pokemon";
|
||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
@ -24,6 +23,9 @@ const namespace = "mysteryEncounter:trashToTreasure";
|
||||
|
||||
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.
|
||||
* @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- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
|
||||
|
||||
encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString());
|
||||
|
||||
return true;
|
||||
})
|
||||
.withOption(
|
||||
@ -102,8 +106,14 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
||||
transitionMysteryEncounterIntroVisuals(scene);
|
||||
await tryApplyDigRewardItems(scene);
|
||||
|
||||
// Give the player the Black Sludge curse
|
||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE));
|
||||
const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [SHOP_ITEM_COST_MULTIPLIER]);
|
||||
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);
|
||||
})
|
||||
.build()
|
||||
@ -180,7 +190,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
||||
}
|
||||
|
||||
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
|
||||
for (const pokemon of party) {
|
||||
@ -207,7 +217,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -12,7 +12,6 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
||||
import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
@ -50,14 +49,13 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
|
||||
// Calculate boss mon
|
||||
// Level equal to 2 below highest party member
|
||||
const level = getHighestLevelPlayerPokemon(scene).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 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
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
|
||||
const eggMoves = pokemon.getEggMoves();
|
||||
if (eggMoves) {
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove: Moves = eggMoves[eggMoveIndex];
|
||||
encounter.misc = {
|
||||
@ -72,6 +70,11 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
|
||||
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 = {
|
||||
pokemonConfigs: [{
|
||||
level: level,
|
||||
@ -81,7 +84,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
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;
|
||||
|
||||
// Give 1 additional egg move
|
||||
const previousEggMove = 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);
|
||||
}
|
||||
}
|
||||
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||
|
||||
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
@ -235,20 +225,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
const pokemon = encounter.misc.pokemon;
|
||||
|
||||
// Give 1 additional egg move
|
||||
const previousEggMove = 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);
|
||||
}
|
||||
}
|
||||
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||
|
||||
// Roll IVs a second time
|
||||
pokemon.ivs = pokemon.ivs.map(iv => {
|
||||
@ -266,3 +243,18 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||
import { achvs } from "#app/system/achv";
|
||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
@ -20,7 +19,8 @@ import i18next from "#app/plugins/i18n";
|
||||
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||
import { getLevelTotalExp } from "#app/data/exp";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, GameModes } from "#app/game-mode";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
|
||||
/** i18n namespace for encounter */
|
||||
const namespace = "mysteryEncounter:weirdDream";
|
||||
@ -82,6 +82,9 @@ const SUPER_LEGENDARY_BST_THRESHOLD = 600;
|
||||
const NON_LEGENDARY_BST_THRESHOLD = 570;
|
||||
const GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD = 450;
|
||||
|
||||
/** 0-100 */
|
||||
const PERCENT_LEVEL_LOSS_ON_REFUSE = 12.5;
|
||||
|
||||
/**
|
||||
* Value ranges of the resulting species BST transformations after adding values to original species
|
||||
* 2 Pokemon in the party use this range
|
||||
@ -101,7 +104,7 @@ const STANDARD_BST_TRANSFORM_BASE_VALUES: [number, number] = [40, 50];
|
||||
export const WeirdDreamEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
|
||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||
.withDisabledGameModes(GameModes.CHALLENGE)
|
||||
.withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
@ -207,12 +210,12 @@ export const WeirdDreamEncounter: MysteryEncounter =
|
||||
async (scene: BattleScene) => {
|
||||
// Reduce party levels by 20%
|
||||
for (const pokemon of scene.getParty()) {
|
||||
pokemon.level = Math.max(Math.ceil(0.8 * pokemon.level), 1);
|
||||
pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1);
|
||||
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
||||
pokemon.levelExp = 0;
|
||||
|
||||
pokemon.calculateStats();
|
||||
pokemon.updateInfo();
|
||||
await pokemon.updateInfo();
|
||||
}
|
||||
|
||||
leaveEncounterWithoutBattle(scene, true);
|
||||
@ -339,6 +342,12 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
}
|
||||
}
|
||||
|
||||
// If the previous pokemon had pokerus, transfer to new pokemon
|
||||
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)
|
||||
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
|
||||
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
|
||||
@ -349,22 +358,17 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
|
||||
}
|
||||
|
||||
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg 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();
|
||||
// Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move
|
||||
const newPokemonGeneratedMoveset = newPokemon.moveset;
|
||||
|
||||
newPokemon.moveset = previousPokemon.moveset;
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves = speciesEggMoves[speciesRootForm];
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove = eggMoves[eggMoveIndex];
|
||||
if (newPokemon.moveset.length < 4) {
|
||||
newPokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
newPokemon.moveset[randSeedInt(4)] = 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), eggMoveIndex, true);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// If the pokemon does not normally have a second type, it will gain 1
|
||||
@ -381,7 +385,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
|
||||
for (const item of transformation.heldItems) {
|
||||
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
|
||||
@ -392,11 +396,12 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
||||
// Def or 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().generateType(scene.getParty(), [20, stats]);
|
||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU()
|
||||
.generateType(scene.getParty(), [20, stats])
|
||||
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
|
||||
const modifier = modType?.newModifier(newPokemon);
|
||||
if (modifier) {
|
||||
scene.addModifier(modifier);
|
||||
await scene.addModifier(modifier, false, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -404,13 +409,15 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
newPokemon.passive = previousPokemon.passive;
|
||||
|
||||
newPokemon.calculateStats();
|
||||
newPokemon.initBattleInfo();
|
||||
await newPokemon.updateInfo();
|
||||
}
|
||||
|
||||
// One random pokemon will get its passive unlocked
|
||||
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
|
||||
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
|
||||
@ -440,7 +447,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
|
||||
if (validSpecies?.length > 20) {
|
||||
validSpecies = randSeedShuffle(validSpecies);
|
||||
newSpecies = validSpecies.pop();
|
||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
|
||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) {
|
||||
newSpecies = validSpecies.pop();
|
||||
}
|
||||
} else {
|
||||
@ -450,7 +457,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
|
||||
}
|
||||
}
|
||||
|
||||
return newSpecies!;
|
||||
return newSpecies;
|
||||
}
|
||||
|
||||
function doShowDreamBackground(scene: BattleScene) {
|
||||
@ -535,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;
|
||||
}
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ export class MysteryEncounterOptionBuilder implements Partial<IMysteryEncounterO
|
||||
return Object.assign(this, { requirements: this.requirements });
|
||||
}
|
||||
|
||||
withSceneMoneyRequirement(requiredMoney?: number, scalingMultiplier?: number) {
|
||||
withSceneMoneyRequirement(requiredMoney: number, scalingMultiplier?: number) {
|
||||
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
|
||||
}
|
||||
|
||||
|
@ -165,9 +165,9 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -251,7 +251,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
||||
const currentWeather = scene.arena.weather?.weatherType;
|
||||
let token = "";
|
||||
if (!isNullOrUndefined(currentWeather)) {
|
||||
token = WeatherType[currentWeather!].replace("_", " ").toLocaleLowerCase();
|
||||
token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase();
|
||||
}
|
||||
return ["weather", token];
|
||||
}
|
||||
@ -259,24 +259,24 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
||||
|
||||
export class PartySizeRequirement extends EncounterSceneRequirement {
|
||||
partySizeRange: [number, number];
|
||||
excludeFainted: boolean;
|
||||
excludeDisallowedPokemon: boolean;
|
||||
|
||||
/**
|
||||
* Used for specifying a party size requirement
|
||||
* If min and max are equivalent, will check for exact size
|
||||
* @param partySizeRange
|
||||
* @param excludeFainted
|
||||
* @param excludeDisallowedPokemon
|
||||
*/
|
||||
constructor(partySizeRange: [number, number], excludeFainted: boolean) {
|
||||
constructor(partySizeRange: [number, number], excludeDisallowedPokemon: boolean) {
|
||||
super();
|
||||
this.partySizeRange = partySizeRange;
|
||||
this.excludeFainted = excludeFainted;
|
||||
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||
}
|
||||
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
||||
const partySize = this.excludeFainted ? 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 (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) {
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -326,7 +326,7 @@ export class MoneyRequirement extends EncounterSceneRequirement {
|
||||
requiredMoney: number; // Static value
|
||||
scalingMultiplier: number; // Calculates required money based off wave index
|
||||
|
||||
constructor(requiredMoney?: number, scalingMultiplier?: number) {
|
||||
constructor(requiredMoney: number, scalingMultiplier?: number) {
|
||||
super();
|
||||
this.requiredMoney = requiredMoney ?? 0;
|
||||
this.scalingMultiplier = scalingMultiplier ?? 0;
|
||||
@ -418,8 +418,8 @@ export class NatureRequirement extends EncounterPokemonRequirement {
|
||||
}
|
||||
|
||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon!.nature)) {
|
||||
return ["nature", Nature[pokemon!.nature]];
|
||||
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) {
|
||||
return ["nature", Nature[pokemon.nature]];
|
||||
}
|
||||
return ["nature", ""];
|
||||
}
|
||||
@ -620,7 +620,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
||||
return this.requiredStatusEffect.some((statusEffect) => {
|
||||
if (statusEffect === StatusEffect.NONE) {
|
||||
// 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 {
|
||||
return pokemon.status?.effect === statusEffect;
|
||||
}
|
||||
@ -628,12 +628,11 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
||||
});
|
||||
} else {
|
||||
// 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 !this.requiredStatusEffect.some((statusEffect) => {
|
||||
if (statusEffect === StatusEffect.NONE) {
|
||||
// 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 {
|
||||
return pokemon.status?.effect === statusEffect;
|
||||
}
|
||||
@ -645,7 +644,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const reqStatus = this.requiredStatusEffect.filter((a) => {
|
||||
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;
|
||||
});
|
||||
@ -767,12 +766,14 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon: number = 1, invertQuery: boolean = false, requireTransferable: boolean = true) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
@ -787,21 +788,23 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
|
||||
return pokemon.getHeldItems().some((it) => {
|
||||
return it.constructor.name === heldItem;
|
||||
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
||||
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem)
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
}).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter((it) => {
|
||||
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem)
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
@ -814,12 +817,14 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
requiredHeldItemTypes: Type[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(heldItemTypes: Type | Type[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
constructor(heldItemTypes: Type | Type[], minNumberOfPokemon: number = 1, invertQuery: boolean = false, requireTransferable: boolean = true) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
@ -834,21 +839,29 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemTypes.some((heldItemType) => {
|
||||
return pokemon.getHeldItems().some((it) => {
|
||||
return it instanceof AttackTypeBoosterModifier && (it.type as AttackTypeBoosterModifierType).moveType === heldItemType;
|
||||
return it instanceof AttackTypeBoosterModifier
|
||||
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
||||
return !this.requiredHeldItemTypes.some(heldItemType => it instanceof AttackTypeBoosterModifier && (it.type as AttackTypeBoosterModifierType).moveType === heldItemType);
|
||||
return !this.requiredHeldItemTypes.some(heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier
|
||||
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType
|
||||
&& (!this.requireTransferable || it.isTransferable));
|
||||
}).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter((it) => {
|
||||
return this.requiredHeldItemTypes.some(heldItemType => it instanceof AttackTypeBoosterModifier && (it.type as AttackTypeBoosterModifierType).moveType === heldItemType);
|
||||
return this.requiredHeldItemTypes.some(heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier
|
||||
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType)
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
@ -974,8 +987,9 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
|
||||
}
|
||||
|
||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
if (!isNullOrUndefined(pokemon?.getHpRatio())) {
|
||||
return ["healthRatio", Math.floor(pokemon!.getHpRatio() * 100).toString() + "%"];
|
||||
const hpRatio = pokemon?.getHpRatio();
|
||||
if (!isNullOrUndefined(hpRatio)) {
|
||||
return ["healthRatio", Math.floor(hpRatio * 100).toString() + "%"];
|
||||
}
|
||||
return ["healthRatio", ""];
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { GameModes } from "#app/game-mode";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { Challenges } from "#enums/challenges";
|
||||
|
||||
export interface EncounterStartOfBattleEffect {
|
||||
sourcePokemon?: Pokemon;
|
||||
@ -40,7 +41,8 @@ export interface IMysteryEncounter {
|
||||
spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||
encounterTier: MysteryEncounterTier;
|
||||
encounterAnimations?: EncounterAnim[];
|
||||
disabledGameModes?: GameModes[];
|
||||
disallowedGameModes?: GameModes[];
|
||||
disallowedChallenges?: Challenges[];
|
||||
hideBattleIntroMessage: boolean;
|
||||
autoHideIntroVisuals: boolean;
|
||||
enterIntroVisualsFromRight: boolean;
|
||||
@ -93,7 +95,11 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
/**
|
||||
* If specified, defines any game modes where the {@linkcode MysteryEncounter} should *NOT* spawn
|
||||
*/
|
||||
disabledGameModes?: GameModes[];
|
||||
disallowedGameModes?: GameModes[];
|
||||
/**
|
||||
* If specified, defines any challenges (from Challenge game mode) where the {@linkcode MysteryEncounter} should *NOT* spawn
|
||||
*/
|
||||
disallowedChallenges?: Challenges[];
|
||||
/**
|
||||
* If true, hides "A Wild X Appeared" etc. messages
|
||||
* Default true
|
||||
@ -161,6 +167,11 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
doEncounterRewards?: (scene: BattleScene) => boolean;
|
||||
/** Will execute callback during VictoryPhase of a continuousEncounter */
|
||||
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
|
||||
/**
|
||||
* Can perform special logic when a ME battle is lost, before GameOver/battle retry prompt.
|
||||
* Should return `true` if it is treated as "real" Game Over, `false` if not.
|
||||
*/
|
||||
onGameOver?: (scene: BattleScene) => boolean;
|
||||
|
||||
/**
|
||||
* Requirements
|
||||
@ -656,11 +667,21 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
/**
|
||||
* Defines any game modes where the Mystery Encounter should *NOT* spawn
|
||||
* @returns
|
||||
* @param disabledGameModes
|
||||
* @param disallowedGameModes
|
||||
*/
|
||||
withDisabledGameModes(...disabledGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disabledGameModes">> {
|
||||
const gameModes = Array.isArray(disabledGameModes) ? disabledGameModes : [disabledGameModes];
|
||||
return Object.assign(this, { disabledGameModes: gameModes });
|
||||
withDisallowedGameModes(...disallowedGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
|
||||
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
|
||||
return Object.assign(this, { disallowedGameModes: gameModes });
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines any challenges (from Challenge game mode) where the Mystery Encounter should *NOT* spawn
|
||||
* @returns
|
||||
* @param disallowedChallenges
|
||||
*/
|
||||
withDisallowedChallenges(...disallowedChallenges: Challenges[]): this & Required<Pick<IMysteryEncounter, "disallowedChallenges">> {
|
||||
const challenges = Array.isArray(disallowedChallenges) ? disallowedChallenges : [disallowedChallenges];
|
||||
return Object.assign(this, { disallowedChallenges: challenges });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -742,11 +763,11 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
*
|
||||
* @param min min wave (or exact size if only min is given)
|
||||
* @param max optional max size. If not given, defaults to min => exact wave
|
||||
* @param excludeFainted if true, only counts unfainted mons
|
||||
* @param excludeDisallowedPokemon if true, only counts allowed (legal in Challenge/unfainted) mons
|
||||
* @returns
|
||||
*/
|
||||
withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
|
||||
withScenePartySizeRequirement(min: number, max?: number, excludeDisallowedPokemon: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeDisallowedPokemon));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 { 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 { 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
|
||||
@ -362,11 +363,16 @@ export function initMysteryEncounters() {
|
||||
});
|
||||
|
||||
// Add ANY biome encounters to biome map
|
||||
mysteryEncountersByBiome.forEach(biomeEncounters => {
|
||||
let encounterBiomeTableLog = "";
|
||||
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
|
||||
anyBiomeEncounters.forEach(encounter => {
|
||||
if (!biomeEncounters.includes(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);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima
|
||||
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
|
||||
// primaryStyle is applied to all text that does not have its own specified style
|
||||
|
@ -43,7 +43,7 @@ import { Variant } from "#app/data/variant";
|
||||
* @param scene
|
||||
*/
|
||||
export function doTrainerExclamation(scene: BattleScene) {
|
||||
const exclamationSprite = scene.add.sprite(0, 0, "exclaim");
|
||||
const exclamationSprite = scene.add.sprite(0, 0, "encounter_exclaim");
|
||||
exclamationSprite.setName("exclamation");
|
||||
scene.field.add(exclamationSprite);
|
||||
scene.field.moveTo(exclamationSprite, scene.field.getAll().length - 1);
|
||||
@ -135,7 +135,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
scene.currentBattle.trainer.destroy();
|
||||
}
|
||||
|
||||
trainerConfig = partyConfig?.trainerConfig ? partyConfig?.trainerConfig : trainerConfigs[trainerType!];
|
||||
trainerConfig = partyTrainerConfig ? partyTrainerConfig : trainerConfigs[trainerType!];
|
||||
|
||||
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
|
||||
doubleBattle = doubleTrainer;
|
||||
@ -166,7 +166,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
// 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.
|
||||
// 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);
|
||||
battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
|
||||
|
||||
@ -226,7 +226,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
|
||||
// Set form
|
||||
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
|
||||
@ -236,29 +236,29 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
|
||||
// Set form
|
||||
if (!isNullOrUndefined(config.formIndex)) {
|
||||
enemyPokemon.formIndex = config.formIndex!;
|
||||
enemyPokemon.formIndex = config.formIndex;
|
||||
}
|
||||
|
||||
// Set shiny
|
||||
if (!isNullOrUndefined(config.shiny)) {
|
||||
enemyPokemon.shiny = config.shiny!;
|
||||
enemyPokemon.shiny = config.shiny;
|
||||
}
|
||||
|
||||
// Set 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.)
|
||||
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) {
|
||||
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData!;
|
||||
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData;
|
||||
}
|
||||
|
||||
// Set Boss
|
||||
if (config.isBoss) {
|
||||
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true);
|
||||
if (!isNullOrUndefined(config.bossSegmentModifier)) {
|
||||
segments += config.bossSegmentModifier!;
|
||||
segments += config.bossSegmentModifier;
|
||||
}
|
||||
enemyPokemon.setBoss(true, segments);
|
||||
}
|
||||
@ -294,18 +294,18 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
|
||||
// Set ability
|
||||
if (!isNullOrUndefined(config.abilityIndex)) {
|
||||
enemyPokemon.abilityIndex = config.abilityIndex!;
|
||||
enemyPokemon.abilityIndex = config.abilityIndex;
|
||||
}
|
||||
|
||||
// Set gender
|
||||
if (!isNullOrUndefined(config.gender)) {
|
||||
enemyPokemon.gender = config.gender!;
|
||||
enemyPokemon.summonData.gender = config.gender!;
|
||||
enemyPokemon.summonData.gender = config.gender;
|
||||
}
|
||||
|
||||
// Set AI type
|
||||
if (!isNullOrUndefined(config.aiType)) {
|
||||
enemyPokemon.aiType = config.aiType!;
|
||||
enemyPokemon.aiType = config.aiType;
|
||||
}
|
||||
|
||||
// Set moves
|
||||
@ -339,7 +339,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
|
||||
loadEnemyAssets.push(enemyPokemon.loadAssets());
|
||||
|
||||
console.log(enemyPokemon.name, enemyPokemon.species.speciesId, enemyPokemon.stats);
|
||||
console.log(`Pokemon: ${enemyPokemon.name}`, `Species ID: ${enemyPokemon.species.speciesId}`, `Stats: ${enemyPokemon.stats}`, `Ability: ${enemyPokemon.getAbility().name}`, `Passive Ability: ${enemyPokemon.getPassiveAbility().name}`);
|
||||
});
|
||||
|
||||
scene.pushPhase(new MysteryEncounterBattlePhase(scene, partyConfig.disableSwitch));
|
||||
@ -744,6 +744,37 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@linkcode handleMysteryEncounterVictory}, but for cases where the player lost a battle or failed a challenge
|
||||
* @param scene
|
||||
* @param addHealPhase
|
||||
*/
|
||||
export function handleMysteryEncounterBattleFailed(scene: BattleScene, addHealPhase: boolean = false, doNotContinue: boolean = false) {
|
||||
const allowedPkm = scene.getParty().filter((pkm) => pkm.isAllowedInBattle());
|
||||
|
||||
if (allowedPkm.length === 0) {
|
||||
scene.clearPhaseQueue();
|
||||
scene.unshiftPhase(new GameOverPhase(scene));
|
||||
return;
|
||||
}
|
||||
|
||||
// If in repeated encounter variant, do nothing
|
||||
// Variant must eventually be swapped in order to handle "true" end of the encounter
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
if (encounter.continuousEncounter || doNotContinue) {
|
||||
return;
|
||||
} else if (encounter.encounterMode !== MysteryEncounterMode.NO_BATTLE) {
|
||||
scene.pushPhase(new BattleEndPhase(scene, false));
|
||||
}
|
||||
|
||||
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
||||
|
||||
if (!encounter.doContinueEncounter) {
|
||||
// Only lapse eggs once for multi-battle encounters
|
||||
scene.pushPhase(new EggLapsePhase(scene));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param scene
|
||||
|
@ -13,7 +13,7 @@ import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { Gender } from "#app/data/gender";
|
||||
@ -50,28 +50,39 @@ export function getSpriteKeysFromPokemon(pokemon: Pokemon): { spriteKey: string,
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Will never remove the player's last non-fainted Pokemon (if they only have 1)
|
||||
* Otherwise, picks a Pokemon completely at random and removes from the party
|
||||
* @param scene
|
||||
* @param isAllowedInBattle Default false. If true, only picks from unfainted mons. If there is only 1 unfainted mon left and doNotReturnLastAbleMon is also true, will return fainted mon
|
||||
* @param doNotReturnLastAbleMon Default false. If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
||||
* @param isAllowed Default false. If true, only picks from legal mons. If no legal mons are found (or there is 1, with `doNotReturnLastAllowedMon = true), will return a mon that is not allowed.
|
||||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @param doNotReturnLastAllowedMon Default false. If true, will never return the last unfainted pokemon in the party. Useful when this function is being used to determine what Pokemon to remove from the party (Don't want to remove last unfainted)
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: boolean = false, doNotReturnLastAbleMon: boolean = false): PlayerPokemon {
|
||||
export function getRandomPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false, doNotReturnLastAllowedMon: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let chosenIndex: number;
|
||||
let chosenPokemon: PlayerPokemon;
|
||||
const unfaintedMons = party.filter(p => p.isAllowedInBattle());
|
||||
const faintedMons = party.filter(p => !p.isAllowedInBattle());
|
||||
let chosenPokemon: PlayerPokemon | null = null;
|
||||
const fullyLegalMons = party.filter(p => (!isAllowed || p.isAllowed()) && (isFainted || !p.isFainted()));
|
||||
const allowedOnlyMons = party.filter(p => p.isAllowed());
|
||||
|
||||
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
|
||||
chosenIndex = randSeedInt(faintedMons.length);
|
||||
chosenPokemon = faintedMons[chosenIndex];
|
||||
} else if (isAllowedInBattle) {
|
||||
chosenIndex = randSeedInt(unfaintedMons.length);
|
||||
chosenPokemon = unfaintedMons[chosenIndex];
|
||||
} else {
|
||||
if (doNotReturnLastAllowedMon && fullyLegalMons.length === 1) {
|
||||
// If there is only 1 legal/unfainted mon left, select from fainted legal mons
|
||||
const faintedLegalMons = party.filter(p => (!isAllowed || p.isAllowed()) && p.isFainted());
|
||||
if (faintedLegalMons.length > 0) {
|
||||
chosenIndex = randSeedInt(faintedLegalMons.length);
|
||||
chosenPokemon = faintedLegalMons[chosenIndex];
|
||||
}
|
||||
}
|
||||
if (!chosenPokemon && fullyLegalMons.length > 0) {
|
||||
chosenIndex = randSeedInt(fullyLegalMons.length);
|
||||
chosenPokemon = fullyLegalMons[chosenIndex];
|
||||
}
|
||||
if (!chosenPokemon && isAllowed && allowedOnlyMons.length > 0) {
|
||||
chosenIndex = randSeedInt(allowedOnlyMons.length);
|
||||
chosenPokemon = allowedOnlyMons[chosenIndex];
|
||||
}
|
||||
if (!chosenPokemon) {
|
||||
// If no other options worked, returns fully random
|
||||
chosenIndex = randSeedInt(party.length);
|
||||
chosenPokemon = party[chosenIndex];
|
||||
}
|
||||
@ -82,15 +93,19 @@ export function getRandomPlayerPokemon(scene: BattleScene, isAllowedInBattle: bo
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param unfainted Default false. If true, only picks from unfainted mons.
|
||||
* @param isAllowed Default false. If true, only picks from legal mons.
|
||||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||
export function getHighestLevelPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
for (const p of party) {
|
||||
if (unfainted && p.isFainted()) {
|
||||
if (isAllowed && !p.isAllowed()) {
|
||||
continue;
|
||||
}
|
||||
if (!isFainted && p.isFainted()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -104,15 +119,19 @@ export function getHighestLevelPlayerPokemon(scene: BattleScene, unfainted: bool
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param stat Stat to search for
|
||||
* @param unfainted Default false. If true, only picks from unfainted mons.
|
||||
* @param isAllowed Default false. If true, only picks from legal mons.
|
||||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentStat, unfainted: boolean = false): PlayerPokemon {
|
||||
export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentStat, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
for (const p of party) {
|
||||
if (unfainted && p.isFainted()) {
|
||||
if (isAllowed && !p.isAllowed()) {
|
||||
continue;
|
||||
}
|
||||
if (!isFainted && p.isFainted()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -125,15 +144,19 @@ export function getHighestStatPlayerPokemon(scene: BattleScene, stat: PermanentS
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||
* @param isAllowed Default false. If true, only picks from legal mons.
|
||||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||
export function getLowestLevelPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
for (const p of party) {
|
||||
if (unfainted && p.isFainted()) {
|
||||
if (isAllowed && !p.isAllowed()) {
|
||||
continue;
|
||||
}
|
||||
if (!isFainted && p.isFainted()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -146,15 +169,19 @@ export function getLowestLevelPlayerPokemon(scene: BattleScene, unfainted: boole
|
||||
/**
|
||||
* Ties are broken by whatever mon is closer to the front of the party
|
||||
* @param scene
|
||||
* @param unfainted - default false. If true, only picks from unfainted mons.
|
||||
* @param isAllowed Default false. If true, only picks from legal mons.
|
||||
* @param isFainted Default false. If true, includes fainted mons.
|
||||
* @returns
|
||||
*/
|
||||
export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted: boolean = false): PlayerPokemon {
|
||||
export function getHighestStatTotalPlayerPokemon(scene: BattleScene, isAllowed: boolean = false, isFainted: boolean = false): PlayerPokemon {
|
||||
const party = scene.getParty();
|
||||
let pokemon: PlayerPokemon | null = null;
|
||||
|
||||
for (const p of party) {
|
||||
if (unfainted && p.isFainted()) {
|
||||
if (isAllowed && !p.isAllowed()) {
|
||||
continue;
|
||||
}
|
||||
if (!isFainted && p.isFainted()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -170,19 +197,28 @@ export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted:
|
||||
* @param starterTiers
|
||||
* @param excludedSpecies
|
||||
* @param types
|
||||
* @param allowSubLegendary
|
||||
* @param allowLegendary
|
||||
* @param allowMythical
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[], allowSubLegendary: boolean = true, allowLegendary: boolean = true, allowMythical: boolean = true): Species {
|
||||
let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
|
||||
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
|
||||
|
||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
||||
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
|
||||
.filter(s => {
|
||||
const pokemonSpecies = getPokemonSpecies(s[0]);
|
||||
return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0])
|
||||
&& (allowSubLegendary || !pokemonSpecies.subLegendary)
|
||||
&& (allowLegendary || !pokemonSpecies.legendary)
|
||||
&& (allowMythical || !pokemonSpecies.mythical));
|
||||
})
|
||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||
|
||||
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
|
||||
@ -275,7 +311,9 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h
|
||||
* @param value
|
||||
*/
|
||||
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);
|
||||
if (modifier) {
|
||||
await pokemon.scene.addModifier(modifier, false, false, false, true);
|
||||
@ -744,8 +782,7 @@ export function getGoldenBugNetSpecies(): PokemonSpecies {
|
||||
*/
|
||||
export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) {
|
||||
const currentBattle = scene.currentBattle;
|
||||
// Default to use the first generated level from enemyLevels, or generate a new one if it DNE
|
||||
const baseLevel = currentBattle.enemyLevels && currentBattle.enemyLevels?.length > 0 ? currentBattle.enemyLevels[0] : currentBattle.getLevelForWave();
|
||||
const baseLevel = currentBattle.getLevelForWave();
|
||||
|
||||
// Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier
|
||||
return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0);
|
||||
@ -773,3 +810,23 @@ export async function addPokemonDataToDexAndValidateAchievements(scene: BattleSc
|
||||
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||
return scene.gameData.setPokemonCaught(pokemon, true, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Pokemon is allowed under a challenge, and allowed in battle.
|
||||
* If both are true, returns `null`.
|
||||
* If one of them is not true, returns message content that the Pokemon is invalid.
|
||||
* Typically used for cheecking whether a Pokemon can be selected for a {@linkcode MysteryEncounterOption}
|
||||
* @param pokemon
|
||||
* @param scene
|
||||
* @param invalidSelectionKey
|
||||
*/
|
||||
export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, scene: BattleScene, invalidSelectionKey: string): string | null {
|
||||
if (!pokemon.isAllowed()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, invalidSelectionKey) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -597,7 +597,7 @@ export class TrainerConfig {
|
||||
case "flare": {
|
||||
return {
|
||||
[TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY],
|
||||
[TrainerPoolTier.UNCOMMON]: [Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.BERGMITE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO],
|
||||
[TrainerPoolTier.UNCOMMON]: [Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO],
|
||||
[TrainerPoolTier.RARE]: [Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO]
|
||||
};
|
||||
}
|
||||
@ -640,14 +640,14 @@ export class TrainerConfig {
|
||||
return {
|
||||
[TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ],
|
||||
[TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ],
|
||||
[TrainerPoolTier.RARE]: [ Species.BULBASAUR, Species.GLIMMET ]
|
||||
[TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ]
|
||||
};
|
||||
}
|
||||
case "star_4": {
|
||||
return {
|
||||
[TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ],
|
||||
[TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ],
|
||||
[TrainerPoolTier.RARE]: [ Species.POPPLIO, Species.GALAR_PONYTA ]
|
||||
[TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ]
|
||||
};
|
||||
}
|
||||
case "star_5": {
|
||||
@ -1509,7 +1509,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setSpeciesPools({
|
||||
[TrainerPoolTier.COMMON]: [Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH, Species.SPHEAL, Species.REMORAID, Species.QWILFISH, Species.BARBOACH],
|
||||
[TrainerPoolTier.UNCOMMON]: [Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.CLOBBOPUS, Species.HORSEA],
|
||||
[TrainerPoolTier.RARE]: [Species.MANTINE, Species.DHELMISE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.PALDEA_WOOPER, Species.SKRELP],
|
||||
[TrainerPoolTier.RARE]: [Species.MANTYKE, Species.DHELMISE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.PALDEA_WOOPER, Species.SKRELP],
|
||||
[TrainerPoolTier.SUPER_RARE]: [Species.DONDOZO, Species.BASCULEGION]
|
||||
}),
|
||||
[TrainerType.MATT]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aqua_admin", "aqua", [Species.SHARPEDO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
|
||||
@ -1527,8 +1527,8 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
[TrainerType.PLASMA_GRUNT]: new TrainerConfig(++t).setHasGenders("Plasma Grunt Female").setHasDouble("Plasma Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
|
||||
.setSpeciesPools({
|
||||
[TrainerPoolTier.COMMON]: [Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE, Species.SANDILE, Species.TRUBBISH, Species.TYMPOLE],
|
||||
[TrainerPoolTier.UNCOMMON]: [Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.JOLTIK],
|
||||
[TrainerPoolTier.RARE]: [Species.PAWNIARD, Species.RUFFLET, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.CUBCHOO, Species.MIENFOO, Species.DURANT, Species.BOUFFALANT],
|
||||
[TrainerPoolTier.UNCOMMON]: [Species.FRILLISH, Species.VENIPEDE, Species.GOLETT, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.JOLTIK, Species.CUBCHOO, Species.KLINK],
|
||||
[TrainerPoolTier.RARE]: [Species.PAWNIARD, Species.RUFFLET, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.MIENFOO, Species.DURANT, Species.BOUFFALANT],
|
||||
[TrainerPoolTier.SUPER_RARE]: [Species.DRUDDIGON, Species.HISUI_ZORUA, Species.AXEW, Species.DEINO]
|
||||
}),
|
||||
[TrainerType.ZINZOLIN]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("plasma_sage", "plasma", [Species.CRYOGONAL]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_plasma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
|
||||
@ -1537,7 +1537,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
.setSpeciesPools({
|
||||
[TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR],
|
||||
[TrainerPoolTier.UNCOMMON]: [Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP],
|
||||
[TrainerPoolTier.RARE]: [Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.BERGMITE, Species.SLIGGOO],
|
||||
[TrainerPoolTier.RARE]: [Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO],
|
||||
[TrainerPoolTier.SUPER_RARE]: [Species.NOIVERN, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG]
|
||||
}),
|
||||
[TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [Species.LIEPARD]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
|
||||
@ -1545,15 +1545,15 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
[TrainerType.AETHER_GRUNT]: new TrainerConfig(++t).setHasGenders("Aether Grunt Female").setHasDouble("Aether Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
|
||||
.setSpeciesPools({
|
||||
[TrainerPoolTier.COMMON]: [ Species.PIKIPEK, Species.ROCKRUFF, Species.ALOLA_DIGLETT, Species.ALOLA_EXEGGUTOR, Species.YUNGOOS, Species.CORSOLA, Species.ALOLA_GEODUDE, Species.ALOLA_RAICHU, Species.BOUNSWEET, Species.LILLIPUP, Species.KOMALA, Species.MORELULL, Species.COMFEY, Species.TOGEDEMARU],
|
||||
[TrainerPoolTier.UNCOMMON]: [ Species.POLIWAG, Species.STUFFUL, Species.ORANGURU, Species.PASSIMIAN, Species.BRUXISH, Species.MINIOR, Species.WISHIWASHI, Species.CRABRAWLER, Species.CUTIEFLY, Species.ORICORIO, Species.MUDBRAY, Species.PYUKUMUKU, Species.ALOLA_MAROWAK],
|
||||
[TrainerPoolTier.RARE]: [ Species.GALAR_CORSOLA, Species.ALOLA_SANDSHREW, Species.ALOLA_VULPIX, Species.TURTONATOR, Species.DRAMPA],
|
||||
[TrainerPoolTier.UNCOMMON]: [ Species.POLIWAG, Species.STUFFUL, Species.ORANGURU, Species.PASSIMIAN, Species.BRUXISH, Species.MINIOR, Species.WISHIWASHI, Species.ALOLA_SANDSHREW, Species.ALOLA_VULPIX, Species.CRABRAWLER, Species.CUTIEFLY, Species.ORICORIO, Species.MUDBRAY, Species.PYUKUMUKU, Species.ALOLA_MAROWAK],
|
||||
[TrainerPoolTier.RARE]: [ Species.GALAR_CORSOLA, Species.TURTONATOR, Species.MIMIKYU, Species.MAGNEMITE, Species.DRAMPA],
|
||||
[TrainerPoolTier.SUPER_RARE]: [Species.JANGMO_O, Species.PORYGON]
|
||||
}),
|
||||
[TrainerType.FABA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("aether_admin", "aether", [Species.HYPNO]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aether_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
|
||||
[TrainerType.SKULL_GRUNT]: new TrainerConfig(++t).setHasGenders("Skull Grunt Female").setHasDouble("Skull Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_skull_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
|
||||
.setSpeciesPools({
|
||||
[TrainerPoolTier.COMMON]: [ Species.SALANDIT, Species.ALOLA_RATTATA, Species.EKANS, Species.ALOLA_MEOWTH, Species.SCRAGGY, Species.KOFFING, Species.ALOLA_GRIMER, Species.MAREANIE, Species.SPINARAK, Species.TRUBBISH],
|
||||
[TrainerPoolTier.UNCOMMON]: [ Species.FOMANTIS, Species.SABLEYE, Species.SANDILE, Species.HOUNDOUR, Species.ALOLA_MAROWAK, Species.GASTLY, Species.PANCHAM, Species.DROWZEE, Species.ZUBAT, Species.VENIPEDE, Species.VULLABY],
|
||||
[TrainerPoolTier.COMMON]: [ Species.SALANDIT, Species.ALOLA_RATTATA, Species.EKANS, Species.ALOLA_MEOWTH, Species.SCRAGGY, Species.KOFFING, Species.ALOLA_GRIMER, Species.MAREANIE, Species.SPINARAK, Species.TRUBBISH, Species.DROWZEE],
|
||||
[TrainerPoolTier.UNCOMMON]: [ Species.FOMANTIS, Species.SABLEYE, Species.SANDILE, Species.HOUNDOUR, Species.ALOLA_MAROWAK, Species.GASTLY, Species.PANCHAM, Species.ZUBAT, Species.VENIPEDE, Species.VULLABY],
|
||||
[TrainerPoolTier.RARE]: [Species.SANDYGAST, Species.PAWNIARD, Species.MIMIKYU, Species.DHELMISE, Species.WISHIWASHI, Species.NYMBLE],
|
||||
[TrainerPoolTier.SUPER_RARE]: [Species.GRUBBIN, Species.DEWPIDER]
|
||||
}),
|
||||
@ -1916,7 +1916,14 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
p.formIndex = 1; // Mega Kangaskhan
|
||||
p.generateName();
|
||||
}))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([Species.GASTRODON, Species.SEISMITOAD]))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([Species.GASTRODON, Species.SEISMITOAD], TrainerSlot.TRAINER, true, p => {
|
||||
//Storm Drain Gastrodon, Water Absorb Seismitoad
|
||||
if (p.species.speciesId === Species.GASTRODON) {
|
||||
p.abilityIndex = 0;
|
||||
} else if (p.species.speciesId === Species.SEISMITOAD) {
|
||||
p.abilityIndex = 2;
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(5, getRandomPartyMemberFunc([Species.MEWTWO], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
@ -2060,7 +2067,7 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.ULTRA_BALL;
|
||||
p.formIndex = Utils.randSeedInt(5, 1); // Shock, Burn, Chill, or Douse Drive
|
||||
p.formIndex = Utils.randSeedInt(4, 1); // Shock, Burn, Chill, or Douse Drive
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BASCULEGION, Species.JELLICENT ], TrainerSlot.TRAINER, true, p => {
|
||||
p.generateAndPopulateMoveset();
|
||||
@ -2153,9 +2160,23 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
p.pokeball = PokeballType.MASTER_BALL;
|
||||
})),
|
||||
[TrainerType.GUZMA]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", []).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma")
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LOKIX, Species.YANMEGA ]))
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.LOKIX, Species.YANMEGA ], TrainerSlot.TRAINER, true, p => {
|
||||
//Tinted Lens Lokix, Tinted Lens Yanmega
|
||||
if (p.species.speciesId === Species.LOKIX) {
|
||||
p.abilityIndex = 2;
|
||||
} else if (p.species.speciesId === Species.YANMEGA) {
|
||||
p.abilityIndex = 1;
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HERACROSS ]))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ]))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => {
|
||||
//Technician Scizor, Sharpness Kleavor
|
||||
if (p.species.speciesId === Species.SCIZOR) {
|
||||
p.abilityIndex = 1;
|
||||
} else if (p.species.speciesId === Species.KLEAVOR) {
|
||||
p.abilityIndex = 2;
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALVANTULA, Species.VIKAVOLT]))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
|
||||
p.generateAndPopulateMoveset();
|
||||
@ -2175,25 +2196,32 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
p.abilityIndex = 2; //Anticipation
|
||||
p.pokeball = PokeballType.ULTRA_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.HISUI_SAMUROTT, Species.CRAWDAUNT ], TrainerSlot.TRAINER, true, p => {
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => {
|
||||
//Technician Scizor, Sharpness Kleavor
|
||||
if (p.species.speciesId === Species.SCIZOR) {
|
||||
p.abilityIndex = 1;
|
||||
} else if (p.species.speciesId === Species.KLEAVOR) {
|
||||
p.abilityIndex = 2;
|
||||
}
|
||||
}))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HISUI_SAMUROTT, Species.CRAWDAUNT ], TrainerSlot.TRAINER, true, p => {
|
||||
p.abilityIndex = 2; //Sharpness Hisui Samurott, Adaptability Crawdaunt
|
||||
}))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ]))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.BUZZWOLE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.ROGUE_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.XURKITREE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.ROGUE_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.formIndex = 1;
|
||||
p.generateAndPopulateMoveset();
|
||||
p.generateName();
|
||||
p.pokeball = PokeballType.ULTRA_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.BUZZWOLE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.ROGUE_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.XURKITREE ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.ROGUE_BALL;
|
||||
})),
|
||||
[TrainerType.ROSE]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", []).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ]))
|
||||
@ -2209,17 +2237,16 @@ export const trainerConfigs: TrainerConfigs = {
|
||||
p.pokeball = PokeballType.ULTRA_BALL;
|
||||
})),
|
||||
[TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.MELMETAL ], TrainerSlot.TRAINER, true, p => {
|
||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
p.pokeball = PokeballType.ULTRA_BALL;
|
||||
}))
|
||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AEGISLASH, Species.GHOLDENGO ]))
|
||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.DRACOVISH, Species.DRACOZOLT ], TrainerSlot.TRAINER, true, p => {
|
||||
p.generateAndPopulateMoveset();
|
||||
p.abilityIndex = 1; //Strong Jaw Dracovish, Hustle Dracozolt
|
||||
}))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.ARCHALUDON ]))
|
||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.MELMETAL ]))
|
||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GALAR_ARTICUNO, Species.GALAR_ZAPDOS, Species.GALAR_MOLTRES ], TrainerSlot.TRAINER, true, p => {
|
||||
p.setBoss(true, 2);
|
||||
p.generateAndPopulateMoveset();
|
||||
|
@ -79,6 +79,7 @@ export enum BattlerTagType {
|
||||
TAR_SHOT = "TAR_SHOT",
|
||||
BURNED_UP = "BURNED_UP",
|
||||
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
|
||||
AUTOTOMIZED = "AUTOTOMIZED",
|
||||
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
||||
HEAL_BLOCK = "HEAL_BLOCK",
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ export class Arena {
|
||||
public tags: ArenaTag[];
|
||||
public bgm: string;
|
||||
public ignoreAbilities: boolean;
|
||||
public ignoringEffectSource: BattlerIndex | null;
|
||||
|
||||
private lastTimeOfDay: TimeOfDay;
|
||||
|
||||
@ -569,8 +570,9 @@ export class Arena {
|
||||
}
|
||||
}
|
||||
|
||||
setIgnoreAbilities(ignoreAbilities: boolean = true): void {
|
||||
setIgnoreAbilities(ignoreAbilities: boolean, ignoringEffectSource: BattlerIndex | null = null): void {
|
||||
this.ignoreAbilities = ignoreAbilities;
|
||||
this.ignoringEffectSource = ignoreAbilities ? ignoringEffectSource : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -86,7 +86,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
};
|
||||
|
||||
if (!isNullOrUndefined(result.species)) {
|
||||
const keys = getSpriteKeysFromSpecies(result.species!);
|
||||
const keys = getSpriteKeysFromSpecies(result.species);
|
||||
result.spriteKey = keys.spriteKey;
|
||||
result.fileRoot = keys.fileRoot;
|
||||
result.isPokemon = true;
|
||||
|
@ -17,10 +17,10 @@ import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
|
||||
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
|
||||
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
|
||||
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag } from "../data/battler-tags";
|
||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags";
|
||||
import { WeatherType } from "../data/weather";
|
||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability";
|
||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "../data/ability";
|
||||
import PokemonData from "../system/pokemon-data";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Mode } from "../ui/ui";
|
||||
@ -109,6 +109,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
public fusionVariant: Variant;
|
||||
public fusionGender: Gender;
|
||||
public fusionLuck: integer;
|
||||
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null;
|
||||
|
||||
private summonDataPrimer: PokemonSummonData | null;
|
||||
|
||||
@ -206,6 +207,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.fusionVariant = dataSource.fusionVariant || 0;
|
||||
this.fusionGender = dataSource.fusionGender;
|
||||
this.fusionLuck = dataSource.fusionLuck;
|
||||
this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData;
|
||||
this.usedTMs = dataSource.usedTMs ?? [];
|
||||
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData);
|
||||
} else {
|
||||
@ -343,7 +345,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
isAllowed(): boolean {
|
||||
const challengeAllowed = new Utils.BooleanHolder(true);
|
||||
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed);
|
||||
return !this.isFainted() && challengeAllowed.value;
|
||||
return challengeAllowed.value;
|
||||
}
|
||||
|
||||
isActive(onField?: boolean): boolean {
|
||||
@ -1164,11 +1166,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
if (!types.length || !includeTeraType) {
|
||||
if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) {
|
||||
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
||||
this.mysteryEncounterPokemonData.types.forEach(t => types.push(t));
|
||||
} else if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
|
||||
if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
|
||||
this.summonData.types.forEach(t => types.push(t));
|
||||
} else if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) {
|
||||
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
||||
types.push(this.mysteryEncounterPokemonData.types[0]);
|
||||
|
||||
// Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal
|
||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
||||
if (fusionSpeciesForm) {
|
||||
// Check if the fusion Pokemon also had "permanently changed" types
|
||||
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types;
|
||||
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
||||
types.push(fusionMETypes[1]);
|
||||
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
||||
types.push(fusionMETypes[0]);
|
||||
} else if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== types[0]) {
|
||||
types.push(fusionSpeciesForm.type2);
|
||||
} else if (fusionSpeciesForm.type1 !== types[0]) {
|
||||
types.push(fusionSpeciesForm.type1);
|
||||
}
|
||||
}
|
||||
|
||||
if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) {
|
||||
types.push(this.mysteryEncounterPokemonData.types[1]);
|
||||
}
|
||||
} else {
|
||||
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
||||
|
||||
@ -1176,7 +1198,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
||||
if (fusionSpeciesForm) {
|
||||
if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) {
|
||||
// Check if the fusion Pokemon also had "permanently changed" types
|
||||
// Otherwise, use standard fusion type logic
|
||||
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types;
|
||||
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
||||
types.push(fusionMETypes[1]);
|
||||
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
||||
types.push(fusionMETypes[0]);
|
||||
} else if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) {
|
||||
types.push(fusionSpeciesForm.type2);
|
||||
} else if (fusionSpeciesForm.type1 !== speciesForm.type1) {
|
||||
types.push(fusionSpeciesForm.type1);
|
||||
@ -1228,12 +1257,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) {
|
||||
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (this.isFusion()) {
|
||||
if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) {
|
||||
return allAbilities[this.fusionMysteryEncounterPokemonData.ability];
|
||||
} else {
|
||||
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
||||
}
|
||||
}
|
||||
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) {
|
||||
return allAbilities[this.mysteryEncounterPokemonData.ability];
|
||||
}
|
||||
if (this.isFusion()) {
|
||||
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
||||
}
|
||||
let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex);
|
||||
if (abilityId === Abilities.NONE) {
|
||||
abilityId = this.species.ability1;
|
||||
@ -1331,7 +1364,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
|
||||
return false;
|
||||
}
|
||||
if (this.scene?.arena.ignoreAbilities && ability.isIgnorable) {
|
||||
const arena = this.scene?.arena;
|
||||
if (arena.ignoreAbilities && arena.ignoringEffectSource !== this.getBattlerIndex() && ability.isIgnorable) {
|
||||
return false;
|
||||
}
|
||||
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
|
||||
@ -1393,11 +1427,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the weight of the Pokemon with subtractive modifiers (Autotomize) happening first
|
||||
* and then multiplicative modifiers happening after (Heavy Metal and Light Metal)
|
||||
* @returns the kg of the Pokemon (minimum of 0.1)
|
||||
*/
|
||||
getWeight(): number {
|
||||
const weight = new Utils.NumberHolder(this.species.weight);
|
||||
const autotomizedTag = this.getTag(AutotomizedTag);
|
||||
let weightRemoved = 0;
|
||||
if (!Utils.isNullOrUndefined(autotomizedTag)) {
|
||||
weightRemoved = 100 * autotomizedTag!.autotomizeCount;
|
||||
}
|
||||
const minWeight = 0.1;
|
||||
const weight = new Utils.NumberHolder(this.species.weight - weightRemoved);
|
||||
|
||||
// This will trigger the ability overlay so only call this function when necessary
|
||||
applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight);
|
||||
return weight.value;
|
||||
return Math.max(minWeight, weight.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1753,8 +1799,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*
|
||||
* @returns list of egg moves
|
||||
*/
|
||||
getEggMoves() : Moves[] {
|
||||
return speciesEggMoves[this.species.speciesId];
|
||||
getEggMoves() : Moves[] | undefined {
|
||||
return speciesEggMoves[this.getSpeciesForm().getRootSpeciesId(true)];
|
||||
}
|
||||
|
||||
setMove(moveIndex: integer, moveId: Moves): void {
|
||||
@ -1927,6 +1973,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.fusionVariant = 0;
|
||||
this.fusionGender = 0;
|
||||
this.fusionLuck = 0;
|
||||
this.fusionMysteryEncounterPokemonData = null;
|
||||
|
||||
this.generateName();
|
||||
this.calculateStats();
|
||||
@ -3331,7 +3378,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
if (asPhase) {
|
||||
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText!, sourcePokemon!)); // TODO: are these bangs correct?
|
||||
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3365,6 +3412,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
if (effect !== StatusEffect.FAINT) {
|
||||
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
||||
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, this, effect, sourcePokemon);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -4231,6 +4279,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.fusionVariant = pokemon.variant;
|
||||
this.fusionGender = pokemon.gender;
|
||||
this.fusionLuck = pokemon.luck;
|
||||
this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData;
|
||||
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
|
||||
this.pauseEvolutions = true;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { GachaType } from "./enums/gacha-types";
|
||||
import { trainerConfigs } from "./data/trainer-config";
|
||||
import { getBiomeHasProps } from "./field/arena";
|
||||
import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin";
|
||||
import { SceneBase } from "./scene-base";
|
||||
@ -21,7 +20,6 @@ import i18next from "i18next";
|
||||
import { initStatsKeys } from "./ui/game-stats-ui-handler";
|
||||
import { initVouchers } from "./system/voucher";
|
||||
import { Biome } from "#enums/biome";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import {initMysteryEncounters} from "#app/data/mystery-encounters/mystery-encounters";
|
||||
|
||||
export class LoadingScene extends SceneBase {
|
||||
@ -208,14 +206,6 @@ export class LoadingScene extends SceneBase {
|
||||
this.loadAtlas("trainer_f_back", "trainer");
|
||||
this.loadAtlas("trainer_f_back_pb", "trainer");
|
||||
|
||||
Utils.getEnumValues(TrainerType).map(tt => {
|
||||
const config = trainerConfigs[tt];
|
||||
this.loadAtlas(config.getSpriteKey(), "trainer");
|
||||
if (config.doubleOnly || config.hasDouble) {
|
||||
this.loadAtlas(config.getSpriteKey(true), "trainer");
|
||||
}
|
||||
});
|
||||
|
||||
// Load character sprites
|
||||
this.loadAtlas("c_rival_m", "character", "rival_m");
|
||||
this.loadAtlas("c_rival_f", "character", "rival_f");
|
||||
|
@ -53,7 +53,49 @@ import terrain from "./terrain.json";
|
||||
import modifierSelectUiHandler from "./modifier-select-ui-handler.json";
|
||||
import moveTriggers from "./move-trigger.json";
|
||||
import runHistory from "./run-history.json";
|
||||
import mysteryEncounterMessages from "./mystery-encounter-messages.json";
|
||||
import lostAtSea from "./mystery-encounters/lost-at-sea-dialogue.json";
|
||||
import mysteriousChest from "./mystery-encounters/mysterious-chest-dialogue.json";
|
||||
import mysteriousChallengers from "./mystery-encounters/mysterious-challengers-dialogue.json";
|
||||
import darkDeal from "./mystery-encounters/dark-deal-dialogue.json";
|
||||
import departmentStoreSale from "./mystery-encounters/department-store-sale-dialogue.json";
|
||||
import fieldTrip from "./mystery-encounters/field-trip-dialogue.json";
|
||||
import fieryFallout from "./mystery-encounters/fiery-fallout-dialogue.json";
|
||||
import fightOrFlight from "./mystery-encounters/fight-or-flight-dialogue.json";
|
||||
import safariZone from "./mystery-encounters/safari-zone-dialogue.json";
|
||||
import shadyVitaminDealer from "./mystery-encounters/shady-vitamin-dealer-dialogue.json";
|
||||
import slumberingSnorlax from "./mystery-encounters/slumbering-snorlax-dialogue.json";
|
||||
import trainingSession from "./mystery-encounters/training-session-dialogue.json";
|
||||
import theStrongStuff from "./mystery-encounters/the-strong-stuff-dialogue.json";
|
||||
import pokemonSalesman from "./mystery-encounters/the-pokemon-salesman-dialogue.json";
|
||||
import offerYouCantRefuse from "./mystery-encounters/an-offer-you-cant-refuse-dialogue.json";
|
||||
import delibirdy from "./mystery-encounters/delibirdy-dialogue.json";
|
||||
import absoluteAvarice from "./mystery-encounters/absolute-avarice-dialogue.json";
|
||||
import aTrainersTest from "./mystery-encounters/a-trainers-test-dialogue.json";
|
||||
import trashToTreasure from "./mystery-encounters/trash-to-treasure-dialogue.json";
|
||||
import berriesAbound from "./mystery-encounters/berries-abound-dialogue.json";
|
||||
import clowningAround from "./mystery-encounters/clowning-around-dialogue.json";
|
||||
import partTimer from "./mystery-encounters/part-timer-dialogue.json";
|
||||
import dancingLessons from "./mystery-encounters/dancing-lessons-dialogue.json";
|
||||
import weirdDream from "./mystery-encounters/weird-dream-dialogue.json";
|
||||
import theWinstrateChallenge from "./mystery-encounters/the-winstrate-challenge-dialogue.json";
|
||||
import teleportingHijinks from "./mystery-encounters/teleporting-hijinks-dialogue.json";
|
||||
import bugTypeSuperfan from "./mystery-encounters/bug-type-superfan-dialogue.json";
|
||||
import funAndGames from "./mystery-encounters/fun-and-games-dialogue.json";
|
||||
import uncommonBreed from "./mystery-encounters/uncommon-breed-dialogue.json";
|
||||
import globalTradeSystem from "./mystery-encounters/global-trade-system-dialogue.json";
|
||||
import expertPokemonBreeder from "./mystery-encounters/the-expert-pokemon-breeder-dialogue.json";
|
||||
|
||||
/**
|
||||
* Dialogue/Text token injection patterns that can be used:
|
||||
* - `$` will be treated as a new line for Message and Dialogue strings.
|
||||
* - `@d{<number>}` will add a time delay to text animation for Message and Dialogue strings.
|
||||
* - `@s{<sound_effect_key>}` will play a specified sound effect for Message and Dialogue strings.
|
||||
* - `@f{<number>}` will fade the screen to black for the given duration, then fade back in for Message and Dialogue strings.
|
||||
* - `{{<token>}}` (MYSTERY ENCOUNTERS ONLY) will auto-inject the matching dialogue token value that is stored in {@link IMysteryEncounter.dialogueTokens}.
|
||||
* - (see [i18next interpolations](https://www.i18next.com/translation-function/interpolation)) for more details.
|
||||
* - `@[<TextStyle>]{<text>}` (STATIC TEXT ONLY, NOT USEABLE WITH {@link UI.showText()} OR {@link UI.showDialogue()}) will auto-color the given text to a specified {@link TextStyle} (e.g. `TextStyle.SUMMARY_GREEN`).
|
||||
*/
|
||||
export const caEsConfig = {
|
||||
ability,
|
||||
abilityTriggers,
|
||||
@ -110,4 +152,40 @@ export const caEsConfig = {
|
||||
modifierSelectUiHandler,
|
||||
moveTriggers,
|
||||
runHistory,
|
||||
mysteryEncounter: {
|
||||
// DO NOT REMOVE
|
||||
"unit_test_dialogue": "{{test}}{{test}} {{test{{test}}}} {{test1}} {{test\}} {{test\\}} {{test\\\}} {test}}",
|
||||
mysteriousChallengers,
|
||||
mysteriousChest,
|
||||
darkDeal,
|
||||
fightOrFlight,
|
||||
slumberingSnorlax,
|
||||
trainingSession,
|
||||
departmentStoreSale,
|
||||
shadyVitaminDealer,
|
||||
fieldTrip,
|
||||
safariZone,
|
||||
lostAtSea,
|
||||
fieryFallout,
|
||||
theStrongStuff,
|
||||
pokemonSalesman,
|
||||
offerYouCantRefuse,
|
||||
delibirdy,
|
||||
absoluteAvarice,
|
||||
aTrainersTest,
|
||||
trashToTreasure,
|
||||
berriesAbound,
|
||||
clowningAround,
|
||||
partTimer,
|
||||
dancingLessons,
|
||||
weirdDream,
|
||||
theWinstrateChallenge,
|
||||
teleportingHijinks,
|
||||
bugTypeSuperfan,
|
||||
funAndGames,
|
||||
uncommonBreed,
|
||||
globalTradeSystem,
|
||||
expertPokemonBreeder
|
||||
},
|
||||
mysteryEncounterMessages
|
||||
};
|
||||
|
1
src/locales/ca_ES/mystery-encounter-messages.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1 @@
|
||||
{}
|