diff --git a/public/images/mystery-encounters/mud.json b/public/images/mystery-encounters/mud.json index 804eed36052..505a6fadd27 100644 --- a/public/images/mystery-encounters/mud.json +++ b/public/images/mystery-encounters/mud.json @@ -4,51 +4,72 @@ "image": "mud.png", "format": "RGBA8888", "size": { - "w": 18, - "h": 55 + "w": 14, + "h": 68 }, "scale": 1, "frames": [ { - "filename": "0002.png", + "filename": "0001.png", "rotated": false, "trimmed": true, "sourceSize": { - "w": 16, - "h": 16 + "w": 12, + "h": 20 }, "spriteSourceSize": { "x": 0, - "y": 3, - "w": 16, + "y": 0, + "w": 12, "h": 13 }, "frame": { "x": 1, "y": 1, - "w": 16, + "w": 12, "h": 13 } }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 12, + "h": 20 + }, + "spriteSourceSize": { + "x": 0, + "y": 0, + "w": 12, + "h": 14 + }, + "frame": { + "x": 1, + "y": 16, + "w": 12, + "h": 14 + } + }, { "filename": "0003.png", "rotated": false, "trimmed": true, "sourceSize": { - "w": 16, - "h": 16 + "w": 12, + "h": 20 }, "spriteSourceSize": { "x": 0, - "y": 4, - "w": 16, - "h": 12 + "y": 1, + "w": 12, + "h": 16 }, "frame": { "x": 1, - "y": 16, - "w": 16, - "h": 12 + "y": 32, + "w": 12, + "h": 16 } }, { @@ -56,41 +77,20 @@ "rotated": false, "trimmed": true, "sourceSize": { - "w": 16, - "h": 16 + "w": 12, + "h": 20 }, "spriteSourceSize": { "x": 0, - "y": 7, - "w": 16, - "h": 9 - }, - "frame": { - "x": 1, - "y": 30, - "w": 16, - "h": 9 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 16, - "h": 16 - }, - "spriteSourceSize": { - "x": 1, "y": 3, - "w": 14, - "h": 13 + "w": 12, + "h": 17 }, "frame": { "x": 1, - "y": 41, - "w": 14, - "h": 13 + "y": 50, + "w": 12, + "h": 17 } } ] @@ -99,6 +99,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:a9f7ae83758a2dffaacdaba2ee9dc2e2:0ebff9db47ce74a0ec049f5d74d589fa:c64f6b8befc3d5e9f836246d2b9536be$" + "smartupdate": "$TexturePacker:SmartUpdate:4f18a8effb8f01eb70f9f25b8294c1bf:ad663a73c51f780bbf45d00a52519553:c64f6b8befc3d5e9f836246d2b9536be$" } } diff --git a/public/images/mystery-encounters/mud.png b/public/images/mystery-encounters/mud.png index 89f174bef74..2ba7cb00047 100644 Binary files a/public/images/mystery-encounters/mud.png and b/public/images/mystery-encounters/mud.png differ diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts new file mode 100644 index 00000000000..8db4db8169d --- /dev/null +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -0,0 +1,173 @@ +import { EncounterOptionMode, MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { modifierTypes, } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import BattleScene from "../../../battle-scene"; +import IMysteryEncounter, { MysteryEncounterBuilder, MysteryEncounterTier, } from "../mystery-encounter"; +import { TypeRequirement } from "../mystery-encounter-requirements"; +import { Species } from "#enums/species"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { Gender } from "#app/data/gender"; +import { Type } from "#app/data/type"; +import { BattlerIndex } from "#app/battle"; +import { PokemonMove } from "#app/field/pokemon"; +import { Moves } from "#enums/moves"; +import { initMoveAnim } from "#app/data/battle-anims"; +import { WeatherType } from "#app/data/weather"; + +/** the i18n namespace for the encounter */ +const namespace = "mysteryEncounter:fiery_fallout"; + +export const FieryFalloutEncounter: IMysteryEncounter = + MysteryEncounterBuilder.withEncounterType( + MysteryEncounterType.FIERY_FALLOUT + ) + .withEncounterTier(MysteryEncounterTier.COMMON) + .withSceneWaveRangeRequirement(40, 180) // waves 10 to 180 + .withCatchAllowed(true) + .withIntroSpriteConfigs([]) // Set in onInit() + .withIntroDialogue([ + { + text: `${namespace}_intro_message`, + }, + ]) + .withOnInit((scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + + // Calculate boss mon + const volcaronaSpecies = getPokemonSpecies(Species.VOLCARONA); + const config: EnemyPartyConfig = { + levelAdditiveMultiplier: 0.25, + pokemonConfigs: [ + { + species: volcaronaSpecies, + isBoss: false, + gender: Gender.MALE + }, + { + species: volcaronaSpecies, + isBoss: false, + gender: Gender.FEMALE + } + ], + doubleBattle: true, + disableSwitch: true + }; + encounter.enemyPartyConfigs = [config]; + + const spriteKey = volcaronaSpecies.getSpriteId(false, null, false, null); + encounter.spriteConfigs = [ + { + spriteKey: spriteKey, + fileRoot: "pokemon", + tint: 0.9, + repeat: true + } + ]; + + // Sets weather for 5 turns + scene.arena.trySetWeather(WeatherType.SUNNY, true); + + return true; + }) + .withTitle(`${namespace}_title`) + .withDescription(`${namespace}_description`) + .withQuery(`${namespace}_query`) + .withSimpleOption( + { + buttonLabel: `${namespace}_option_1_label`, + buttonTooltip: `${namespace}_option_1_tooltip`, + selected: [ + { + text: `${namespace}_option_1_selected`, + }, + ], + }, + async (scene: BattleScene) => { + // Pick battle + const encounter = scene.currentBattle.mysteryEncounter; + // TODO: play heat wave animation for weather effect + // await initMoveAnim(scene, Moves.HEAT_WAVE); + // await loadMoveAnimAssets(scene, [ Moves.HEAT_WAVE ], true); + // const heatWave = new MoveAnim(Moves.HEAT_WAVE, scene.getPlayerPokemon(), 0); + // heatWave.play(scene); + + await initMoveAnim(scene, Moves.QUIVER_DANCE); + await initMoveAnim(scene, Moves.FIRE_SPIN); + await initMoveAnim(scene, Moves.HEAT_WAVE); + const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]); + setEncounterRewards(scene, { guaranteedModifierTypeOptions: [charcoal], fillRemaining: true }); + encounter.startOfBattleEffects.push( + { + sourceBattlerIndex: BattlerIndex.ENEMY, + targets: [BattlerIndex.PLAYER, BattlerIndex.PLAYER_2], + move: new PokemonMove(Moves.HEAT_WAVE), + ignorePp: true + }, + { + sourceBattlerIndex: BattlerIndex.ENEMY, + targets: [BattlerIndex.ENEMY], + move: new PokemonMove(Moves.QUIVER_DANCE), + ignorePp: true + }, + { + sourceBattlerIndex: BattlerIndex.ENEMY_2, + targets: [BattlerIndex.ENEMY_2], + move: new PokemonMove(Moves.QUIVER_DANCE), + ignorePp: true + }, + { + sourceBattlerIndex: BattlerIndex.ENEMY, + targets: [BattlerIndex.PLAYER], + move: new PokemonMove(Moves.FIRE_SPIN), + ignorePp: true + }, + { + sourceBattlerIndex: BattlerIndex.ENEMY_2, + targets: [BattlerIndex.PLAYER_2], + move: new PokemonMove(Moves.FIRE_SPIN), + ignorePp: true + }); + await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter.enemyPartyConfigs[0]); + } + ) + .withSimpleOption( + { + buttonLabel: `${namespace}_option_2_label`, + buttonTooltip: `${namespace}_option_2_tooltip`, + selected: [ + { + text: `${namespace}_option_2_selected`, + }, + ], + }, + async (scene: BattleScene) => { + // Damage party and burn 1 random member + // No rewards + leaveEncounterWithoutBattle(scene); + } + ) + .withOption( + new MysteryEncounterOptionBuilder() + .withOptionMode(EncounterOptionMode.DISABLED_OR_SPECIAL) + .withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, 2)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withDialogue({ + buttonLabel: `${namespace}_option_3_label`, + buttonTooltip: `${namespace}_option_3_tooltip`, + selected: [ + { + text: `${namespace}_option_3_selected`, + }, + ], + }) + .withOptionPhase(async (scene: BattleScene) => { + // Fire types help calm the Volcarona + // const encounter = scene.currentBattle.mysteryEncounter; + const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]); + setEncounterRewards(scene, { guaranteedModifierTypeOptions: [charcoal], fillRemaining: true }); + setEncounterExp(scene, scene.getParty().map(p => p.id), 500); + leaveEncounterWithoutBattle(scene); + }) + .build() + ) + .build(); diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 2b7a013dd22..05cf76fae5f 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -304,14 +304,14 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise { scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); scene.time.delayedCall(512, () => { + scene.playSound("pb_throw"); + // Trainer throw frames scene.trainer.setFrame("2"); - scene.time.delayedCall(256, () => { + scene.time.delayedCall(184, () => { scene.trainer.setFrame("3"); scene.time.delayedCall(768, () => { scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); @@ -366,18 +366,18 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise { scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); scene.time.delayedCall(512, () => { + scene.playSound("pb_throw"); + // Trainer throw frames scene.trainer.setFrame("2"); - scene.time.delayedCall(256, () => { + scene.time.delayedCall(184, () => { scene.trainer.setFrame("3"); scene.time.delayedCall(768, () => { scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back`); @@ -395,32 +395,39 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise { + scene.time.delayedCall(200, () => { mud.setFrame("0003.png"); - scene.time.delayedCall(512, () => { + scene.time.delayedCall(400, () => { mud.setFrame("0004.png"); }); }); - scene.time.delayedCall(1536, () => { - mud.destroy(); - scene.tweens.add({ - targets: pokemon, - duration: 300, - ease: "Cubic.easeOut", - yoyo: true, - y: originalY - 20, - loop: 1, - onStart: () => { - scene.playSound("PRSFX- Taunt2"); - }, - onLoop: () => { - scene.playSound("PRSFX- Taunt2"); - }, - onComplete: () => { - resolve(true); - } - }); + // Fade mud then angry animation + scene.tweens.add({ + targets: mud, + alpha: 0, + ease: "Cubic.easeIn", + duration: 1000, + onComplete: () => { + mud.destroy(); + scene.tweens.add({ + targets: pokemon, + duration: 300, + ease: "Cubic.easeOut", + yoyo: true, + y: originalY - 20, + loop: 1, + onStart: () => { + scene.playSound("PRSFX- Taunt2"); + }, + onLoop: () => { + scene.playSound("PRSFX- Taunt2"); + }, + onComplete: () => { + resolve(true); + } + }); + } }); } }); diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index c0c8b1e5aaf..608783b7d87 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -1,5 +1,5 @@ import { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; +import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { isNullOrUndefined } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "../../battle-scene"; @@ -18,6 +18,7 @@ import { StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements"; +import { BattlerIndex } from "#app/battle"; export enum MysteryEncounterVariant { DEFAULT, @@ -36,6 +37,15 @@ export enum MysteryEncounterTier { MASTER // Not currently used } +export class StartOfBattleEffect { + sourcePokemon?: Pokemon; + sourceBattlerIndex?: BattlerIndex; + targets: BattlerIndex[]; + move: PokemonMove; + followUp?: boolean; + ignorePp?: boolean; +} + export default interface IMysteryEncounter { /** * Required params @@ -90,6 +100,15 @@ export default interface IMysteryEncounter { * You probably shouldn't do anything with this unless you have a very specific need */ introVisuals?: MysteryEncounterIntroVisuals; + /** + * Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave + * You should never need to modify this + */ + seedOffset?: any; + /** + * Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases + */ + startOfBattleEffectsComplete?: boolean; /** * Flags @@ -116,16 +135,15 @@ export default interface IMysteryEncounter { * Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases */ selectedOption?: MysteryEncounterOption; + /** + * Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases + */ + startOfBattleEffects?: StartOfBattleEffect[]; /** * Can be set higher or lower based on the type of battle or exp gained for an option/encounter * Defaults to 1 */ expMultiplier?: number; - /** - * Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave - * You should never need to modify this - */ - seedOffset?: any; /** * Generic property to set any custom data required for the encounter * Extremely useful for carrying state/data between onPreOptionPhase/onOptionPhase/onPostOptionPhase @@ -151,8 +169,10 @@ export default class IMysteryEncounter implements IMysteryEncounter { this.requirements = this.requirements ? this.requirements : []; this.hideBattleIntroMessage = !isNullOrUndefined(this.hideBattleIntroMessage) ? this.hideBattleIntroMessage : false; this.hideIntroVisuals = !isNullOrUndefined(this.hideIntroVisuals) ? this.hideIntroVisuals : true; + this.startOfBattleEffects = this.startOfBattleEffects ?? []; // Reset any dirty flags or encounter data + this.startOfBattleEffectsComplete = false; this.lockEncounterRewardTiers = true; this.dialogueTokens = {}; this.enemyPartyConfigs = []; diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index a53f12a0bfa..c3016d8aaa2 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -11,6 +11,7 @@ import { DepartmentStoreSaleEncounter } from "./encounters/department-store-sale import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter"; import { FieldTripEncounter } from "./encounters/field-trip-encounter"; import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter"; +import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; @@ -184,7 +185,9 @@ export const mysteryEncountersByBiome = new Map([ [Biome.ICE_CAVE, []], [Biome.MEADOW, []], [Biome.POWER_PLANT, []], - [Biome.VOLCANO, []], + [Biome.VOLCANO, [ + MysteryEncounterType.FIERY_FALLOUT + ]], [Biome.GRAVEYARD, []], [Biome.DOJO, []], [Biome.FACTORY, []], @@ -215,6 +218,7 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter; allMysteryEncounters[MysteryEncounterType.FIELD_TRIP] = FieldTripEncounter; allMysteryEncounters[MysteryEncounterType.SAFARI_ZONE] = SafariZoneEncounter; + allMysteryEncounters[MysteryEncounterType.FIERY_FALLOUT] = FieryFalloutEncounter; // Add extreme encounters to biome map extremeBiomeEncounters.forEach(encounter => { diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index c9c3e980456..c38caee472b 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; -import { BattleType } from "#app/battle"; +import { BattlerIndex, BattleType } from "#app/battle"; import BattleScene from "../../../battle-scene"; import PokemonSpecies from "../../pokemon-species"; import { MysteryEncounterVariant } from "../mystery-encounter"; @@ -9,7 +9,7 @@ import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon"; import Trainer, { TrainerVariant } from "../../../field/trainer"; import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier"; import { CustomModifierSettings, getModifierPoolForType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; -import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases"; +import { BattleEndPhase, EggLapsePhase, ExpPhase, ModifierRewardPhase, MovePhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases"; import { MysteryEncounterBattlePhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phase"; import * as Utils from "../../../utils"; import { isNullOrUndefined } from "#app/utils"; @@ -25,6 +25,7 @@ import { WIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/myst import * as Overrides from "#app/overrides"; import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; +import { Gender } from "#app/data/gender"; export class EnemyPokemonConfig { species: PokemonSpecies; @@ -33,6 +34,7 @@ export class EnemyPokemonConfig { bossSegmentModifier?: number; // Additive to the determined segment number formIndex?: number; level?: number; + gender?: Gender; modifierTypes?: PokemonHeldItemModifierType[]; dataSource?: PokemonData; tags?: BattlerTagType[]; @@ -48,6 +50,7 @@ export class EnemyPartyConfig { trainerConfig?: TrainerConfig; // More customizable option for configuring trainer battle pokemonConfigs?: EnemyPokemonConfig[]; female?: boolean; // True for female trainer, false for male + disableSwitch?: boolean; // True will prevent player from switching } /** @@ -161,6 +164,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemyPokemon.formIndex = config.formIndex; } + // Set gender + if (!isNullOrUndefined(config.gender)) { + enemyPokemon.gender = config.gender; + } + // Set Boss if (config.isBoss) { let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true); @@ -201,7 +209,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: console.log(enemyPokemon.name, enemyPokemon.species.speciesId, enemyPokemon.stats); }); - scene.pushPhase(new MysteryEncounterBattlePhase(scene)); + scene.pushPhase(new MysteryEncounterBattlePhase(scene, partyConfig.disableSwitch)); await Promise.all(loadEnemyAssets); battle.enemyParty.forEach((enemyPokemon_2, e_1) => { @@ -562,6 +570,36 @@ export function hideMysteryEncounterIntroVisuals(scene: BattleScene): Promise { + let source; + if (effect.sourcePokemon) { + source = effect.sourcePokemon; + } else if (!isNullOrUndefined(effect.sourceBattlerIndex)) { + if (effect.sourceBattlerIndex === BattlerIndex.ATTACKER) { + source = scene.getEnemyField()[0]; + } else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY) { + source = scene.getEnemyField()[0]; + } else if (effect.sourceBattlerIndex === BattlerIndex.ENEMY_2) { + source = scene.getEnemyField()[1]; + } else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER) { + source = scene.getPlayerField()[0]; + } else if (effect.sourceBattlerIndex === BattlerIndex.PLAYER_2) { + source = scene.getPlayerField()[1]; + } + } else { + source = scene.getEnemyField()[0]; + } + scene.pushPhase(new MovePhase(scene, source, effect.targets, effect.move, effect.followUp, effect.followUp)); + }); + + encounter.startOfBattleEffectsComplete = true; + } +} + /** * TODO: remove once encounter spawn rate is finalized * Just a helper function to calculate aggregate stats for MEs in a Classic run diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index fdbb957161f..15bf282d4df 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -167,7 +167,6 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, pokeball.setOrigin(0.5, 0.625); scene.field.add(pokeball); - scene.playSound("pb_throw"); scene.time.delayedCall(300, () => { scene.field.moveBelow(pokeball as Phaser.GameObjects.GameObject, pokemon); }); @@ -175,6 +174,8 @@ export function trainerThrowPokeball(scene: BattleScene, pokemon: EnemyPokemon, return new Promise(resolve => { scene.trainer.setTexture(`trainer_${scene.gameData.gender === PlayerGender.FEMALE ? "f" : "m"}_back_pb`); scene.time.delayedCall(512, () => { + scene.playSound("pb_throw"); + // Trainer throw frames scene.trainer.setFrame("2"); scene.time.delayedCall(256, () => { diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index 2819b0eb6fb..de15b50f2b8 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -8,5 +8,6 @@ export enum MysteryEncounterType { DEPARTMENT_STORE_SALE, SHADY_VITAMIN_DEALER, FIELD_TRIP, - SAFARI_ZONE + SAFARI_ZONE, + FIERY_FALLOUT } diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index 67b2a6c8579..68d6877f53d 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -172,6 +172,26 @@ export const mysteryEncounter: SimpleTranslationEntries = { "safari_zone_pokemon_beside_itself_angry": "{{pokemonName}} is beside itself with anger!", "safari_zone_remaining_count": "{{remainingCount}} Pokémon remaining!", + "fiery_fallout_intro_message": "You encounter a blistering storm of smoke and ash!", + "fiery_fallout_title": "Fiery Fallout", + "fiery_fallout_description": "The whirling storm of ash and embers has cut visibility to nearly zero. It seems like there might be some... source that is causing these conditions. But what could be behind a phenomenon of this magnitude?", + "fiery_fallout_query": "What will you do?", + "fiery_fallout_option_1_label": "Find the source", + "fiery_fallout_option_1_tooltip": "(?) Discover the source\n(-) Hard Battle", + "fiery_fallout_option_2_label": "Hunker down", + "fiery_fallout_option_2_tooltip": "(-) Suffer the effects of the weather", + "fiery_fallout_option_3_label": "Your Fire types help", + "fiery_fallout_option_3_tooltip": "(+) End the conditions\n(+) Gain a Charcoal", + "fiery_fallout_option_3_disabled_tooltip": "You need at least 2 Fire Type Pokémon to choose this", + "fiery_fallout_option_1_selected": `You push through the storm, and find two Volcarona in the middle of a mating dance! + $They don't take kindly to the interruption and attack!`, + "fiery_fallout_option_2_selected": `The weather effects cause significant harm as you struggle to find shelter! + $Your party takes 30% Max HP damage! + $Your {burnTarget} also becomes burned!`, + // "fiery_fallout_boss_enraged": "The opposing {{enemyPokemon}} has become enraged!", + "fiery_fallout_option_3_selected": `Your {{primaryPokemonName}} and {{secondaryPokemonName}} guide you to where two Volcarona are in the middle of a mating dance! + $Thankfully, your Pokémon are able to calm them, and they depart without issue.`, + // Mystery Encounters -- Ultra Tier "training_session_intro_message": "You've come across some\ntraining tools and supplies.", diff --git a/src/overrides.ts b/src/overrides.ts index e19a5bf20dd..f7fa3c5fc86 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -117,9 +117,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0; */ // 1 to 256, set to null to ignore -export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null; +export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256; export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; -export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null; +export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.FIERY_FALLOUT; /** * MODIFIER / ITEM OVERRIDES diff --git a/src/phases.ts b/src/phases.ts index f98c914e320..5bc1a72bdf4 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1,15 +1,16 @@ import BattleScene, { bypassLogin } from "./battle-scene"; -import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; +import { DamageResult, default as Pokemon, EnemyPokemon, FieldPosition, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "./field/pokemon"; import * as Utils from "./utils"; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr } from "./data/move"; +import { isNullOrUndefined } from "./utils"; +import { allMoves, applyFilteredMoveAttrs, applyMoveAttrs, AttackMove, BypassRedirectAttr, BypassSleepAttr, ChargeAttr, CopyMoveAttr, FixedDamageAttr, ForceSwitchOutAttr, getMoveTargets, HealStatusEffectAttr, HitsTagAttr, IgnoreOpponentStatChangesAttr, IncrementMovePriorityAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveEffectTrigger, MoveFlags, MoveTarget, MoveTargetSet, MultiHitAttr, NoEffectAttr, OneHitKOAccuracyAttr, OverrideMoveEffectAttr, PostVictoryStatChangeAttr, PreMoveMessageAttr, SelfStatusMove, VariableAccuracyAttr, VariableTargetAttr } from "./data/move"; import { Mode } from "./ui/ui"; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier } from "./modifier/modifier"; +import { BerryModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, IvScannerModifier, LapsingPersistentModifier, LapsingPokemonHeldItemModifier, MapModifier, Modifier, MoneyInterestModifier, MoneyMultiplierModifier, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier } from "./modifier/modifier"; import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; -import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; -import { StatusEffect, getStatusEffectActivationText, getStatusEffectCatchRateMultiplier, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./data/status-effect"; +import { CommonAnim, CommonBattleAnim, initMoveAnim, loadMoveAnimAssets, MoveAnim } from "./data/battle-anims"; +import { getStatusEffectActivationText, getStatusEffectCatchRateMultiplier, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText, StatusEffect } from "./data/status-effect"; import { SummaryUiMode } from "./ui/summary-ui-handler"; import EvolutionSceneHandler from "./ui/evolution-scene-handler"; import { EvolutionPhase } from "./evolution-phase"; @@ -17,21 +18,21 @@ import { Phase } from "./phase"; import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat"; import { biomeLinks, getBiomeName } from "./data/biomes"; import { ModifierTier } from "./modifier/modifier-tier"; -import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds, CustomModifierSettings } from "./modifier/modifier-type"; +import { CustomModifierSettings, FusePokemonModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, modifierTypes, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, regenerateModifierPoolThresholds, RememberMoveModifierType, TmModifierType } from "./modifier/modifier-type"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, ProtectedTag, SemiInvulnerableTag, TrappedTag, MysteryEncounterPostSummonTag } from "./data/battler-tags"; +import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, MysteryEncounterPostSummonTag, ProtectedTag, SemiInvulnerableTag, TrappedTag } from "./data/battler-tags"; import { getPokemonMessage, getPokemonNameWithAffix } from "./messages"; import { Starter } from "./ui/starter-select-ui-handler"; import { Gender } from "./data/gender"; -import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; +import { getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage, Weather, WeatherType } from "./data/weather"; import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; -import { BattleType, BattlerIndex, TurnCommand } from "./battle"; -import { ChallengeAchv, HealAchv, LevelAchv, achvs } from "./system/achv"; -import { TrainerSlot, trainerConfigs } from "./data/trainer-config"; +import { BattlerIndex, BattleType, TurnCommand } from "./battle"; +import { achvs, ChallengeAchv, HealAchv, LevelAchv } from "./system/achv"; +import { trainerConfigs, TrainerSlot } from "./data/trainer-config"; import { EggHatchPhase } from "./egg-hatch-phase"; import { Egg } from "./data/egg"; import { vouchers } from "./system/voucher"; @@ -41,7 +42,7 @@ import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue"; import { SettingKeys } from "./system/settings/settings"; -import { Tutorial, handleTutorial } from "./tutorial"; +import { handleTutorial, Tutorial } from "./tutorial"; import { TerrainType } from "./data/terrain"; import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; @@ -50,7 +51,7 @@ import { GameMode, GameModes, getGameMode } from "./game-mode"; import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; import i18next from "./plugins/i18n"; import * as Overrides from "./overrides"; -import { TextStyle, addTextObject } from "./ui/text"; +import { addTextObject, TextStyle } from "./ui/text"; import { Type } from "./data/type"; import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./events/battle-scene"; import { Abilities } from "#enums/abilities"; @@ -66,9 +67,8 @@ import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import { MysteryEncounterVariant } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phase"; -import { handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { handleEncounterStartOfBattleEffects, handleMysteryEncounterVictory } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; -import { isNullOrUndefined } from "./utils"; import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; const { t } = i18next; @@ -891,6 +891,11 @@ export class EncounterPhase extends BattlePhase { if (battle.battleType === BattleType.TRAINER) { loadEnemyAssets.push(battle.trainer.loadAssets().then(() => battle.trainer.initSprite())); } else if (battle.battleType === BattleType.MYSTERY_ENCOUNTER) { + // this.scene.getEnemyParty().forEach(p => { + // this.scene.field.remove(p); + // p.destroy(); + // }); + // this.scene.currentBattle.enemyParty = []; if (!battle.mysteryEncounter) { const newEncounter = this.scene.getMysteryEncounter(mysteryEncounter); battle.mysteryEncounter = newEncounter; @@ -2034,6 +2039,9 @@ export class TurnInitPhase extends FieldPhase { //this.scene.pushPhase(new MoveAnimTestPhase(this.scene)); this.scene.eventTarget.dispatchEvent(new TurnInitEvent()); + // Start of battle effects for Mystery Encounters + handleEncounterStartOfBattleEffects(this.scene); + this.scene.getField().forEach((pokemon, i) => { if (pokemon?.isActive()) { if (pokemon.isPlayer()) { diff --git a/src/phases/mystery-encounter-phase.ts b/src/phases/mystery-encounter-phase.ts index 13856471e4d..fda0e008484 100644 --- a/src/phases/mystery-encounter-phase.ts +++ b/src/phases/mystery-encounter-phase.ts @@ -173,8 +173,11 @@ export class MysteryEncounterOptionSelectedPhase extends Phase { * - Queue the SummonPhases, PostSummonPhases, etc., required to initialize the phase queue for a battle */ export class MysteryEncounterBattlePhase extends Phase { - constructor(scene: BattleScene) { + disableSwitch: boolean; + + constructor(scene: BattleScene, disableSwitch = false) { super(scene); + this.disableSwitch = disableSwitch; } start() { @@ -219,7 +222,7 @@ export class MysteryEncounterBattlePhase extends Phase { } if (!scene.currentBattle.mysteryEncounter.hideBattleIntroMessage) { - scene.ui.showText(this.getBattleMessage(scene), null, () => this.endBattleSetup(scene), 1500); + scene.ui.showText(this.getBattleMessage(scene), null, () => this.endBattleSetup(scene), 500); } else { this.endBattleSetup(scene); } @@ -302,7 +305,7 @@ export class MysteryEncounterBattlePhase extends Phase { scene.pushPhase(new ToggleDoublePositionPhase(scene, false)); } - if (encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE && (scene.currentBattle.waveIndex > 1 || !scene.gameMode.isDaily)) { + if (encounterVariant !== MysteryEncounterVariant.TRAINER_BATTLE && !this.disableSwitch) { const minPartySize = scene.currentBattle.double ? 2 : 1; if (availablePartyMembers.length > minPartySize) { scene.pushPhase(new CheckSwitchPhase(scene, 0, scene.currentBattle.double));