Merge branch 'beta' into Snow-Worker-Dialogue-
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "berry_bush.png",
|
"image": "berries_abound_bush.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 49,
|
"w": 49,
|
Before Width: | Height: | Size: 719 B After Width: | Height: | Size: 719 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "mad_scientist_m.png",
|
"image": "dark_deal_scientist.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 46,
|
"w": 46,
|
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 920 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "b2w2_lady.png",
|
"image": "department_store_sale_lady.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 399,
|
"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": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "teacher.png",
|
"image": "field_trip_teacher.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 43,
|
"w": 43,
|
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 727 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "carnival_game.png",
|
"image": "fun_and_games_game.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 38,
|
"w": 38,
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 517 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "carnival_man.png",
|
"image": "fun_and_games_man.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 50,
|
"w": 50,
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 833 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "carnival_wobbuffet.png",
|
"image": "fun_and_games_wobbuffet.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 45,
|
"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": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "chest_blue.png",
|
"image": "mysterious_chest_blue.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 54,
|
"w": 54,
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "chest_red.png",
|
"image": "mysterious_chest_red.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 54,
|
"w": 54,
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "warehouse_crate.png",
|
"image": "part_timer_crate.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 71,
|
"w": 71,
|
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 868 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "bait.png",
|
"image": "safari_zone_bait.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 14,
|
"w": 14,
|
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 277 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "mud.png",
|
"image": "safari_zone_mud.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 14,
|
"w": 14,
|
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "b2w2_veteran_m.png",
|
"image": "shady_vitamin_dealer.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 424,
|
"w": 424,
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "teleporter.png",
|
"image": "teleporting_hijinks_teleporter.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 74,
|
"w": 74,
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"textures": [
|
"textures": [
|
||||||
{
|
{
|
||||||
"image": "training_gear.png",
|
"image": "training_session_gear.png",
|
||||||
"format": "RGBA8888",
|
"format": "RGBA8888",
|
||||||
"size": {
|
"size": {
|
||||||
"w": 76,
|
"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 Phaser from "phaser";
|
||||||
import UI from "./ui/ui";
|
import UI from "./ui/ui";
|
||||||
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
|
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon";
|
||||||
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
|
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "./data/pokemon-species";
|
||||||
import { Constructor, isNullOrUndefined } from "#app/utils";
|
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||||
import * as Utils from "./utils";
|
import * as Utils from "./utils";
|
||||||
import { 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 { PokeballType } from "./data/pokeball";
|
||||||
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims";
|
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims";
|
||||||
import { Phase } from "./phase";
|
import { Phase } from "./phase";
|
||||||
@ -13,20 +13,9 @@ import { Arena, ArenaBase } from "./field/arena";
|
|||||||
import { GameData } from "./system/game-data";
|
import { GameData } from "./system/game-data";
|
||||||
import { addTextObject, getTextColor, TextStyle } from "./ui/text";
|
import { addTextObject, getTextColor, TextStyle } from "./ui/text";
|
||||||
import { allMoves } from "./data/move";
|
import { allMoves } from "./data/move";
|
||||||
import {
|
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "./modifier/modifier-type";
|
||||||
ModifierPoolType,
|
|
||||||
getDefaultModifierTypeForTier,
|
|
||||||
getEnemyModifierTypesForWave,
|
|
||||||
getLuckString,
|
|
||||||
getLuckTextTint,
|
|
||||||
getModifierPoolForType,
|
|
||||||
getModifierType,
|
|
||||||
getPartyLuckValue,
|
|
||||||
modifierTypes, PokemonHeldItemModifierType
|
|
||||||
} from "./modifier/modifier-type";
|
|
||||||
import AbilityBar from "./ui/ability-bar";
|
import AbilityBar from "./ui/ability-bar";
|
||||||
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, ChangeMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, BlockItemTheftAbAttr, ChangeMovePriorityAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr } from "./data/ability";
|
||||||
import { allAbilities } from "./data/ability";
|
|
||||||
import Battle, { BattleType, FixedBattleConfig } from "./battle";
|
import Battle, { BattleType, FixedBattleConfig } from "./battle";
|
||||||
import { GameMode, GameModes, getGameMode } from "./game-mode";
|
import { GameMode, GameModes, getGameMode } from "./game-mode";
|
||||||
import FieldSpritePipeline from "./pipelines/field-sprite";
|
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 { addUiThemeOverrides } from "./ui/ui-theme";
|
||||||
import PokemonData from "./system/pokemon-data";
|
import PokemonData from "./system/pokemon-data";
|
||||||
import { Nature } from "./data/nature";
|
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 { FormChangePhase } from "./phases/form-change-phase";
|
||||||
import { getTypeRgb } from "./data/type";
|
import { getTypeRgb } from "./data/type";
|
||||||
import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler";
|
import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler";
|
||||||
@ -1081,6 +1070,11 @@ export default class BattleScene extends SceneBase {
|
|||||||
p.destroy();
|
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
|
//@ts-ignore - allowing `null` for currentBattle causes a lot of trouble
|
||||||
this.currentBattle = null; // TODO: resolve ts-ignore
|
this.currentBattle = null; // TODO: resolve ts-ignore
|
||||||
|
|
||||||
@ -1111,6 +1105,8 @@ export default class BattleScene extends SceneBase {
|
|||||||
this.trainer.setPosition(406, 186);
|
this.trainer.setPosition(406, 186);
|
||||||
this.trainer.setVisible(true);
|
this.trainer.setVisible(true);
|
||||||
|
|
||||||
|
this.mysteryEncounterSaveData = new MysteryEncounterSaveData();
|
||||||
|
|
||||||
this.updateGameInfo();
|
this.updateGameInfo();
|
||||||
|
|
||||||
if (reloadI18n) {
|
if (reloadI18n) {
|
||||||
@ -1205,32 +1201,12 @@ export default class BattleScene extends SceneBase {
|
|||||||
|
|
||||||
// Check for mystery encounter
|
// Check for mystery encounter
|
||||||
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
|
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
|
||||||
const [lowestMysteryEncounterWave, highestMysteryEncounterWave] = this.gameMode.getMysteryEncounterLegalWaves();
|
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER || !isNullOrUndefined(mysteryEncounterType)) {
|
||||||
if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(newWaveIndex) && newWaveIndex < highestMysteryEncounterWave && newWaveIndex > lowestMysteryEncounterWave) {
|
newBattleType = BattleType.MYSTERY_ENCOUNTER;
|
||||||
const roll = Utils.randSeedInt(MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT);
|
// Reset base spawn weight
|
||||||
|
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_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
|
} else if (newBattleType === BattleType.WILD) {
|
||||||
const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance;
|
this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1271,9 +1247,8 @@ export default class BattleScene extends SceneBase {
|
|||||||
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
|
if (newBattleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||||
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
|
// Disable double battle on mystery encounters (it may be re-enabled as part of encounter)
|
||||||
this.currentBattle.double = false;
|
this.currentBattle.double = false;
|
||||||
this.executeWithSeedOffset(() => {
|
// Will generate the actual Mystery Encounter during NextEncounterPhase, to ensure it uses proper biome
|
||||||
this.currentBattle.mysteryEncounter = this.getMysteryEncounter(mysteryEncounterType);
|
this.currentBattle.mysteryEncounterType = mysteryEncounterType;
|
||||||
}, this.currentBattle.waveIndex << 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
|
//this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6));
|
||||||
@ -2661,8 +2636,7 @@ export default class BattleScene extends SceneBase {
|
|||||||
modifier = mt.modifier as PokemonHeldItemModifier;
|
modifier = mt.modifier as PokemonHeldItemModifier;
|
||||||
modifier.pokemonId = enemyPokemon.id;
|
modifier.pokemonId = enemyPokemon.id;
|
||||||
}
|
}
|
||||||
const stackCount = mt.stackCount ?? 1;
|
modifier.stackCount = mt.stackCount ?? 1;
|
||||||
modifier.stackCount = stackCount;
|
|
||||||
modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable;
|
modifier.isTransferable = mt.isTransferable ?? modifier.isTransferable;
|
||||||
this.addEnemyModifier(modifier, true);
|
this.addEnemyModifier(modifier, true);
|
||||||
});
|
});
|
||||||
@ -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
|
* Loads or generates a mystery encounter
|
||||||
* @param encounterType used to load session encounter when restarting game, etc.
|
* @param encounterType used to load session encounter when restarting game, etc.
|
||||||
@ -3109,10 +3128,10 @@ export default class BattleScene extends SceneBase {
|
|||||||
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
|
getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter {
|
||||||
// Loading override or session encounter
|
// Loading override or session encounter
|
||||||
let encounter: MysteryEncounter | null;
|
let encounter: MysteryEncounter | null;
|
||||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE!)) {
|
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) {
|
||||||
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE!];
|
encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE];
|
||||||
} else {
|
} else {
|
||||||
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType!] : null;
|
encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for queued encounters first
|
// Check for queued encounters first
|
||||||
@ -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;
|
let tier: MysteryEncounterTier | null = tierValue > commonThreshold ? MysteryEncounterTier.COMMON : tierValue > greatThreshold ? MysteryEncounterTier.GREAT : tierValue > ultraThreshold ? MysteryEncounterTier.ULTRA : MysteryEncounterTier.ROGUE;
|
||||||
|
|
||||||
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
|
if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE)) {
|
||||||
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE!;
|
tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE;
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableEncounters: MysteryEncounter[] = [];
|
let availableEncounters: MysteryEncounter[] = [];
|
||||||
@ -3173,11 +3192,17 @@ export default class BattleScene extends SceneBase {
|
|||||||
if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier
|
if (encounterCandidate.encounterTier !== tier) { // Encounter is in tier
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const disabledModes = encounterCandidate.disabledGameModes;
|
const disallowedGameModes = encounterCandidate.disallowedGameModes;
|
||||||
if (disabledModes && disabledModes.length > 0
|
if (disallowedGameModes && disallowedGameModes.length > 0
|
||||||
&& disabledModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
|
&& disallowedGameModes.includes(this.gameMode.modeId)) { // Encounter is enabled for game mode
|
||||||
return false;
|
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
|
if (!encounterCandidate.meetsRequirements(this)) { // Meets encounter requirements
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
|||||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||||
import { CustomModifierSettings } from "#app/modifier/modifier-type";
|
import { CustomModifierSettings } from "#app/modifier/modifier-type";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
|
|
||||||
export enum ClassicFixedBossWaves {
|
export enum ClassicFixedBossWaves {
|
||||||
// TODO: other fixed wave battles should be added here
|
// TODO: other fixed wave battles should be added here
|
||||||
@ -88,6 +89,7 @@ export default class Battle {
|
|||||||
public playerFaintsHistory: FaintLogEntry[] = [];
|
public playerFaintsHistory: FaintLogEntry[] = [];
|
||||||
public enemyFaintsHistory: FaintLogEntry[] = [];
|
public enemyFaintsHistory: FaintLogEntry[] = [];
|
||||||
|
|
||||||
|
public mysteryEncounterType?: MysteryEncounterType;
|
||||||
/** If the current battle is a Mystery Encounter, this will always be defined */
|
/** If the current battle is a Mystery Encounter, this will always be defined */
|
||||||
public mysteryEncounter?: MysteryEncounter;
|
public mysteryEncounter?: MysteryEncounter;
|
||||||
|
|
||||||
|
72
src/data/ability.ts
Executable file → Normal file
@ -366,6 +366,10 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getImmuneType(): Type | null {
|
||||||
|
return this.immuneType;
|
||||||
|
}
|
||||||
|
|
||||||
override getCondition(): AbAttrCondition | null {
|
override getCondition(): AbAttrCondition | null {
|
||||||
return this.condition;
|
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 {
|
export class PostVictoryAbAttr extends AbAttr {
|
||||||
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
return false;
|
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 {
|
export class WeightMultiplierAbAttr extends AbAttr {
|
||||||
private multiplier: integer;
|
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> {
|
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);
|
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
|
* Applies a field Stat multiplier attribute
|
||||||
@ -4907,7 +4974,8 @@ export function initAbilities() {
|
|||||||
.attr(EffectSporeAbAttr),
|
.attr(EffectSporeAbAttr),
|
||||||
new Ability(Abilities.SYNCHRONIZE, 3)
|
new Ability(Abilities.SYNCHRONIZE, 3)
|
||||||
.attr(SyncEncounterNatureAbAttr)
|
.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)
|
new Ability(Abilities.CLEAR_BODY, 3)
|
||||||
.attr(ProtectStatAbAttr)
|
.attr(ProtectStatAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
@ -5879,6 +5947,6 @@ export function initAbilities() {
|
|||||||
new Ability(Abilities.POISON_PUPPETEER, 9)
|
new Ability(Abilities.POISON_PUPPETEER, 9)
|
||||||
.attr(UncopiableAbilityAbAttr)
|
.attr(UncopiableAbilityAbAttr)
|
||||||
.attr(UnswappableAbilityAbAttr)
|
.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);
|
scene.field.add(moveAnim.bgSprite);
|
||||||
const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon();
|
const fieldPokemon = scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon();
|
||||||
if (!isNullOrUndefined(priority)) {
|
if (!isNullOrUndefined(priority)) {
|
||||||
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority!);
|
scene.field.moveTo(moveAnim.bgSprite as Phaser.GameObjects.GameObject, priority);
|
||||||
} else if (fieldPokemon?.isOnField()) {
|
} else if (fieldPokemon?.isOnField()) {
|
||||||
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
|
scene.field.moveBelow(moveAnim.bgSprite as Phaser.GameObjects.GameObject, fieldPokemon);
|
||||||
}
|
}
|
||||||
@ -743,16 +743,21 @@ export abstract class BattleAnim {
|
|||||||
public target: Pokemon | null;
|
public target: Pokemon | null;
|
||||||
public sprites: Phaser.GameObjects.Sprite[];
|
public sprites: Phaser.GameObjects.Sprite[];
|
||||||
public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle;
|
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 srcLine: number[];
|
||||||
private dstLine: 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.user = user ?? null;
|
||||||
this.target = target ?? null;
|
this.target = target ?? null;
|
||||||
this.sprites = [];
|
this.sprites = [];
|
||||||
this.playOnEmptyField = playOnEmptyField;
|
this.playRegardlessOfIssues = playRegardlessOfIssues;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract getAnim(): AnimConfig | null;
|
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 user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
|
||||||
const target = !isOppAnim ? this.target! : this.user!;
|
const target = !isOppAnim ? this.target! : this.user!;
|
||||||
|
|
||||||
if (!target?.isOnField() && !this.playOnEmptyField) {
|
if (!target?.isOnField() && !this.playRegardlessOfIssues) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
@ -896,7 +901,7 @@ export abstract class BattleAnim {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!scene.moveAnimations) {
|
if (!scene.moveAnimations && !this.playRegardlessOfIssues) {
|
||||||
return cleanUpAndComplete();
|
return cleanUpAndComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -910,12 +915,12 @@ export abstract class BattleAnim {
|
|||||||
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
|
this.srcLine = [ userFocusX, userFocusY, targetFocusX, targetFocusY ];
|
||||||
this.dstLine = [ userInitialX, userInitialY, targetInitialX, targetInitialY ];
|
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;
|
let f = 0;
|
||||||
|
|
||||||
scene.tweens.addCounter({
|
scene.tweens.addCounter({
|
||||||
duration: Utils.getFrameMs(3),
|
duration: Utils.getFrameMs(3),
|
||||||
repeat: anim!.frames.length, // TODO: is this bang correct?
|
repeat: anim?.frames.length ?? 0,
|
||||||
onRepeat: () => {
|
onRepeat: () => {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
userSprite.setVisible(false);
|
userSprite.setVisible(false);
|
||||||
@ -932,7 +937,7 @@ export abstract class BattleAnim {
|
|||||||
const isUser = frame.target === AnimFrameTarget.USER;
|
const isUser = frame.target === AnimFrameTarget.USER;
|
||||||
if (isUser && target === user) {
|
if (isUser && target === user) {
|
||||||
continue;
|
continue;
|
||||||
} else if (this.playOnEmptyField && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
|
} else if (this.playRegardlessOfIssues && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];
|
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();
|
return cleanUpAndComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1259,7 +1264,7 @@ export class CommonBattleAnim extends BattleAnim {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAnim(): AnimConfig | null {
|
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 {
|
isOppAnim(): boolean {
|
||||||
@ -1279,7 +1284,7 @@ export class MoveAnim extends BattleAnim {
|
|||||||
getAnim(): AnimConfig {
|
getAnim(): AnimConfig {
|
||||||
return moveAnims.get(this.move) instanceof AnimConfig
|
return moveAnims.get(this.move) instanceof AnimConfig
|
||||||
? moveAnims.get(this.move) as 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 {
|
isOppAnim(): boolean {
|
||||||
@ -1311,7 +1316,7 @@ export class MoveChargeAnim extends MoveAnim {
|
|||||||
getAnim(): AnimConfig {
|
getAnim(): AnimConfig {
|
||||||
return chargeAnims.get(this.chargeAnim) instanceof AnimConfig
|
return chargeAnims.get(this.chargeAnim) instanceof AnimConfig
|
||||||
? chargeAnims.get(this.chargeAnim) as 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 {
|
export class SubstituteTag extends BattlerTag {
|
||||||
/** The substitute's remaining HP. If HP is depleted, the Substitute fades. */
|
/** The substitute's remaining HP. If HP is depleted, the Substitute fades. */
|
||||||
public hp: number;
|
public hp: number;
|
||||||
@ -2568,6 +2598,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
|||||||
return new GorillaTacticsTag();
|
return new GorillaTacticsTag();
|
||||||
case BattlerTagType.SUBSTITUTE:
|
case BattlerTagType.SUBSTITUTE:
|
||||||
return new SubstituteTag(sourceMove, sourceId);
|
return new SubstituteTag(sourceMove, sourceId);
|
||||||
|
case BattlerTagType.AUTOTOMIZED:
|
||||||
|
return new AutotomizedTag();
|
||||||
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON:
|
||||||
return new MysteryEncounterPostSummonTag();
|
return new MysteryEncounterPostSummonTag();
|
||||||
case BattlerTagType.HEAL_BLOCK:
|
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.
|
* @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.
|
* @returns {@link string} The localised name for the current value.
|
||||||
*/
|
*/
|
||||||
getValue(overrideValue?: integer): string {
|
getValue(overrideValue?: number): string {
|
||||||
if (overrideValue === undefined) {
|
const value = overrideValue ?? this.value;
|
||||||
overrideValue = this.value;
|
return i18next.t(`challenges:${this.geti18nKey()}.value.${value}`);
|
||||||
}
|
|
||||||
return i18next.t(`challenges:${this.geti18nKey()}.value.${this.value}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,11 +182,9 @@ export abstract class Challenge {
|
|||||||
* @param overrideValue {@link integer} The value to check for. If undefined, gets the current value.
|
* @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.
|
* @returns {@link string} The localised description for the current value.
|
||||||
*/
|
*/
|
||||||
getDescription(overrideValue?: integer): string {
|
getDescription(overrideValue?: number): string {
|
||||||
if (overrideValue === undefined) {
|
const value = overrideValue ?? this.value;
|
||||||
overrideValue = this.value;
|
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${value}`, `challenges:${this.geti18nKey()}.desc`])}`;
|
||||||
}
|
|
||||||
return `${i18next.t([`challenges:${this.geti18nKey()}.desc.${this.value}`, `challenges:${this.geti18nKey()}.desc`])}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -451,30 +447,6 @@ export class SingleGenerationChallenge extends Challenge {
|
|||||||
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean {
|
applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean {
|
||||||
let trainerTypes: TrainerType[] = [];
|
let trainerTypes: TrainerType[] = [];
|
||||||
switch (waveIndex) {
|
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:
|
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 ];
|
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;
|
break;
|
||||||
@ -511,14 +483,12 @@ export class SingleGenerationChallenge extends Challenge {
|
|||||||
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||||
* @returns {string} The localised name for the current value.
|
* @returns {string} The localised name for the current value.
|
||||||
*/
|
*/
|
||||||
getValue(overrideValue?: integer): string {
|
getValue(overrideValue?: number): string {
|
||||||
if (overrideValue === undefined) {
|
const value = overrideValue ?? this.value;
|
||||||
overrideValue = this.value;
|
if (value === 0) {
|
||||||
}
|
|
||||||
if (this.value === 0) {
|
|
||||||
return i18next.t("settings:off");
|
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.
|
* @param {value} overrideValue The value to check for. If undefined, gets the current value.
|
||||||
* @returns {string} The localised description for the current value.
|
* @returns {string} The localised description for the current value.
|
||||||
*/
|
*/
|
||||||
getDescription(overrideValue?: integer): string {
|
getDescription(overrideValue?: number): string {
|
||||||
if (overrideValue === undefined) {
|
const value = overrideValue ?? this.value;
|
||||||
overrideValue = this.value;
|
if (value === 0) {
|
||||||
}
|
|
||||||
if (this.value === 0) {
|
|
||||||
return i18next.t("challenges:singleGeneration.desc_default");
|
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 user {@linkcode Pokemon} to apply conditions to
|
||||||
* @param target {@linkcode Pokemon} to apply conditions to
|
* @param target {@linkcode Pokemon} to apply conditions to
|
||||||
* @param move {@linkcode Move} to apply conditions to
|
* @param move {@linkcode Move} to apply conditions to
|
||||||
@ -2091,21 +2091,20 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
if (target.status) {
|
if (target.status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
//@ts-ignore - how can target.status.effect be checked when we return `false` before when it's defined?
|
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
||||||
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { // TODO: resolve ts-ignore
|
|
||||||
const statusAfflictResult = target.trySetStatus(statusToApply, true, user);
|
if (canSetStatus) {
|
||||||
if (statusAfflictResult) {
|
|
||||||
if (user.status) {
|
if (user.status) {
|
||||||
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
|
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
|
||||||
}
|
}
|
||||||
user.resetStatus();
|
user.resetStatus();
|
||||||
user.updateInfo();
|
user.updateInfo();
|
||||||
|
target.trySetStatus(statusToApply, true, user);
|
||||||
}
|
}
|
||||||
return statusAfflictResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return canSetStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
@ -5175,31 +5174,29 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ForceSwitchOutAttr extends MoveEffectAttr {
|
export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||||
private user: boolean;
|
constructor(
|
||||||
private batonPass: boolean;
|
private selfSwitch: boolean = false,
|
||||||
|
private batonPass: boolean = false
|
||||||
constructor(user?: boolean, batonPass?: boolean) {
|
) {
|
||||||
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
||||||
this.user = !!user;
|
|
||||||
this.batonPass = !!batonPass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isBatonPass() {
|
isBatonPass() {
|
||||||
return this.batonPass;
|
return this.batonPass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Why is this a Promise?
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||||
return new Promise(resolve => {
|
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)) {
|
if (!this.getSwitchOutCondition()(user, target, move)) {
|
||||||
return resolve(false);
|
return resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the switch out logic inside the conditional block
|
// Move the switch out logic inside the conditional block
|
||||||
// This ensures that the switch out only happens when the conditions are met
|
// This ensures that the switch out only happens when the conditions are met
|
||||||
const switchOutTarget = this.user ? user : target;
|
const switchOutTarget = this.selfSwitch ? user : target;
|
||||||
if (switchOutTarget instanceof PlayerPokemon) {
|
if (switchOutTarget instanceof PlayerPokemon) {
|
||||||
switchOutTarget.leaveField(!this.batonPass);
|
switchOutTarget.leaveField(!this.batonPass);
|
||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
@ -5208,41 +5205,43 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
} else {
|
} else {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
||||||
// Switch out logic for trainer battles
|
// Switch out logic for trainer battles
|
||||||
switchOutTarget.leaveField(!this.batonPass);
|
switchOutTarget.leaveField(!this.batonPass);
|
||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
// for opponent switching out
|
// 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);
|
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 {
|
} else {
|
||||||
// Switch out logic for everything else (eg: WILD battles)
|
// Switch out logic for everything else (eg: WILD battles)
|
||||||
switchOutTarget.leaveField(false);
|
switchOutTarget.leaveField(false);
|
||||||
|
|
||||||
if (switchOutTarget.hp) {
|
if (switchOutTarget.hp) {
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
|
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
|
||||||
|
|
||||||
// in double battles redirect potential moves off fled pokemon
|
// in double battles redirect potential moves off fled pokemon
|
||||||
if (switchOutTarget.scene.currentBattle.double) {
|
if (switchOutTarget.scene.currentBattle.double) {
|
||||||
const allyPokemon = switchOutTarget.getAlly();
|
const allyPokemon = switchOutTarget.getAlly();
|
||||||
switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon);
|
switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!switchOutTarget.getAlly()?.isActive(true)) {
|
if (!switchOutTarget.getAlly()?.isActive(true)) {
|
||||||
user.scene.clearEnemyHeldItemModifiers();
|
user.scene.clearEnemyHeldItemModifiers();
|
||||||
|
|
||||||
if (switchOutTarget.hp) {
|
if (switchOutTarget.hp) {
|
||||||
user.scene.pushPhase(new BattleEndPhase(user.scene));
|
user.scene.pushPhase(new BattleEndPhase(user.scene));
|
||||||
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(true);
|
resolve(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
@ -5257,29 +5256,33 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
getSwitchOutCondition(): MoveConditionFunc {
|
getSwitchOutCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => {
|
return (user, target, move) => {
|
||||||
const switchOutTarget = (this.user ? user : target);
|
const switchOutTarget = (this.selfSwitch ? user : target);
|
||||||
const player = switchOutTarget instanceof PlayerPokemon;
|
const player = switchOutTarget instanceof PlayerPokemon;
|
||||||
|
|
||||||
if (!this.user && move.hitsSubstitute(user, target)) {
|
if (!this.selfSwitch) {
|
||||||
return false;
|
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))) {
|
if (!player && user.scene.currentBattle.battleType === BattleType.WILD) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!player && !user.scene.currentBattle.battleType) {
|
|
||||||
if (this.batonPass) {
|
if (this.batonPass) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const party = player ? user.scene.getParty() : user.scene.getEnemyParty();
|
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())) {
|
if (!user.scene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) {
|
||||||
return -20;
|
return -20;
|
||||||
}
|
}
|
||||||
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
||||||
if (this.user && this.batonPass) {
|
if (this.selfSwitch && this.batonPass) {
|
||||||
const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0);
|
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));
|
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 {
|
export class RemoveTypeAttr extends MoveEffectAttr {
|
||||||
|
|
||||||
private removedType: Type;
|
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),
|
.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)
|
new SelfStatusMove(Moves.AUTOTOMIZE, Type.STEEL, -1, 15, -1, 0, 5)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true)
|
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true)
|
||||||
.partial(),
|
.attr(AddBattlerTagAttr, BattlerTagType.AUTOTOMIZED, true),
|
||||||
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
|
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
|
||||||
.powderMove()
|
.powderMove()
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
|
.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)
|
new AttackMove(Moves.AURA_WHEEL, Type.ELECTRIC, MoveCategory.PHYSICAL, 110, 100, 10, 100, 0, 8)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
|
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
|
||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.attr(AuraWheelTypeAttr)
|
.attr(AuraWheelTypeAttr),
|
||||||
.condition((user, target, move) => [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)), // Missing custom fail message
|
|
||||||
new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8)
|
new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
|
||||||
@ -9486,10 +9503,9 @@ export function initMoves() {
|
|||||||
.makesContact(),
|
.makesContact(),
|
||||||
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
|
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
||||||
.attr(WeatherChangeAttr, WeatherType.SNOW)
|
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", {pokemonName: getPokemonNameWithAffix(user)}))
|
||||||
.attr(ForceSwitchOutAttr, true, false)
|
.attr(ChillyReceptionAttr, true, false),
|
||||||
.target(MoveTarget.BOTH_SIDES),
|
|
||||||
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
|
||||||
.attr(RemoveArenaTrapAttr, true)
|
.attr(RemoveArenaTrapAttr, true)
|
||||||
|
@ -15,6 +15,7 @@ import { EggTier } from "#enums/egg-type";
|
|||||||
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
import { PartyHealPhase } from "#app/phases/party-heal-phase";
|
||||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:aTrainersTest";
|
const namespace = "mysteryEncounter:aTrainersTest";
|
||||||
@ -27,7 +28,7 @@ const namespace = "mysteryEncounter:aTrainersTest";
|
|||||||
export const ATrainersTestEncounter: MysteryEncounter =
|
export const ATrainersTestEncounter: MysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST)
|
||||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||||
.withSceneWaveRangeRequirement(100, 180)
|
.withSceneWaveRangeRequirement(100, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||||
.withIntroSpriteConfigs([]) // These are set in onInit()
|
.withIntroSpriteConfigs([]) // These are set in onInit()
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,7 @@ import { PersistentModifierRequirement } from "#app/data/mystery-encounters/myst
|
|||||||
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { BerryModifier } from "#app/modifier/modifier";
|
import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||||
@ -159,12 +159,6 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
])
|
])
|
||||||
.withHideWildIntroMessage(true)
|
.withHideWildIntroMessage(true)
|
||||||
.withAutoHideIntroVisuals(false)
|
.withAutoHideIntroVisuals(false)
|
||||||
.withOnVisualsStart((scene: BattleScene) => {
|
|
||||||
doGreedentSpriteSteal(scene);
|
|
||||||
doBerrySpritePile(scene);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.withIntroDialogue([
|
.withIntroDialogue([
|
||||||
{
|
{
|
||||||
text: `${namespace}.intro`,
|
text: `${namespace}.intro`,
|
||||||
@ -202,10 +196,15 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
|
const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType;
|
||||||
bossModifierConfigs.push({ modifier: modifierType });
|
bossModifierConfigs.push({ modifier: modifierType });
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.removeModifier(berryMod);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Do NOT remove the real berries yet or else it will be persisted in the session data
|
||||||
|
|
||||||
|
// SpDef buff below wave 50, +1 to all stats otherwise
|
||||||
|
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
|
||||||
|
[Stat.SPDEF] :
|
||||||
|
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||||
|
|
||||||
// Calculate boss mon
|
// Calculate boss mon
|
||||||
const config: EnemyPartyConfig = {
|
const config: EnemyPartyConfig = {
|
||||||
levelAdditiveModifier: 1,
|
levelAdditiveModifier: 1,
|
||||||
@ -214,12 +213,12 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
species: getPokemonSpecies(Species.GREEDENT),
|
species: getPokemonSpecies(Species.GREEDENT),
|
||||||
isBoss: true,
|
isBoss: true,
|
||||||
bossSegments: 3,
|
bossSegments: 3,
|
||||||
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF],
|
moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH],
|
||||||
modifierConfigs: bossModifierConfigs,
|
modifierConfigs: bossModifierConfigs,
|
||||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
|
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`);
|
||||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1));
|
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -230,6 +229,21 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
|
.withOnVisualsStart((scene: BattleScene) => {
|
||||||
|
doGreedentSpriteSteal(scene);
|
||||||
|
doBerrySpritePile(scene);
|
||||||
|
|
||||||
|
// Remove the berries from the party
|
||||||
|
// Session has been safely saved at this point, so data won't be lost
|
||||||
|
const berryItems = scene.findModifiers(m => m instanceof BerryModifier) as BerryModifier[];
|
||||||
|
berryItems.forEach(berryMod => {
|
||||||
|
scene.removeModifier(berryMod);
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.updateModifiers(true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.withOption(
|
.withOption(
|
||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
.newOptionWithMode(MysteryEncounterOptionMode.DEFAULT)
|
||||||
@ -251,7 +265,8 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
const givePartyPokemonReviverSeeds = () => {
|
const givePartyPokemonReviverSeeds = () => {
|
||||||
const party = scene.getParty();
|
const party = scene.getParty();
|
||||||
party.forEach(p => {
|
party.forEach(p => {
|
||||||
if (revSeed) {
|
const heldItems = p.getHeldItems();
|
||||||
|
if (revSeed && !heldItems.some(item => item instanceof PokemonInstantReviveModifier)) {
|
||||||
const seedModifier = revSeed.newModifier(p);
|
const seedModifier = revSeed.newModifier(p);
|
||||||
if (seedModifier) {
|
if (seedModifier) {
|
||||||
encounter.setDialogueToken("foodReward", seedModifier.type.name);
|
encounter.setDialogueToken("foodReward", seedModifier.type.name);
|
||||||
@ -338,7 +353,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
|||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
// Let it have the food
|
// Let it have the food
|
||||||
// Greedent joins the team, level equal to 2 below highest party member
|
// 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);
|
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.moveset = [new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF)];
|
||||||
greedent.passive = true;
|
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 { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
import { EXTORTION_ABILITIES, EXTORTION_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
||||||
@ -17,6 +17,14 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
|||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounter:offerYouCantRefuse";
|
const namespace = "mysteryEncounter:offerYouCantRefuse";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money offered starts at base value of Relic Gold, increasing linearly up to 3x Relic Gold based on the starter tier of the Pokemon being purchased
|
||||||
|
* Starter value 1-3 -> Relic Gold
|
||||||
|
* Starter value 10 -> 3 * Relic Gold
|
||||||
|
*/
|
||||||
|
const MONEY_MINIMUM_MULTIPLIER = 10;
|
||||||
|
const MONEY_MAXIMUM_MULTIPLIER = 30;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Offer You Can't Refuse encounter.
|
* An Offer You Can't Refuse encounter.
|
||||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
|
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3808 | GitHub Issue #3808}
|
||||||
@ -26,7 +34,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
|||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE)
|
||||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
.withEncounterTier(MysteryEncounterTier.GREAT)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.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([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: Species.LIEPARD.toString(),
|
spriteKey: Species.LIEPARD.toString(),
|
||||||
@ -60,8 +68,12 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter =
|
|||||||
.withQuery(`${namespace}.query`)
|
.withQuery(`${namespace}.query`)
|
||||||
.withOnInit((scene: BattleScene) => {
|
.withOnInit((scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
const pokemon = getHighestStatTotalPlayerPokemon(scene, false);
|
const pokemon = getHighestStatTotalPlayerPokemon(scene, true, true);
|
||||||
const price = scene.getWaveMoneyAmount(10);
|
|
||||||
|
const baseSpecies = pokemon.getSpeciesForm().getRootSpeciesId(true);
|
||||||
|
const starterValue: number = speciesStarters[baseSpecies] ?? 1;
|
||||||
|
const multiplier = Math.max(MONEY_MAXIMUM_MULTIPLIER / 10 * starterValue, MONEY_MINIMUM_MULTIPLIER);
|
||||||
|
const price = scene.getWaveMoneyAmount(multiplier);
|
||||||
|
|
||||||
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
|
encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender());
|
||||||
encounter.setDialogueToken("price", price.toString());
|
encounter.setDialogueToken("price", price.toString());
|
||||||
|
@ -82,7 +82,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
|||||||
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
const { spriteKey, fileRoot } = getSpriteKeysFromPokemon(bossPokemon);
|
||||||
encounter.spriteConfigs = [
|
encounter.spriteConfigs = [
|
||||||
{
|
{
|
||||||
spriteKey: "berry_bush",
|
spriteKey: "berries_abound_bush",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
x: 25,
|
x: 25,
|
||||||
y: -6,
|
y: -6,
|
||||||
@ -102,7 +102,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Get fastest party pokemon for option 2
|
// 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.fastestPokemon = fastestPokemon;
|
||||||
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
encounter.misc.enemySpeed = bossPokemon.getStat(Stat.SPD);
|
||||||
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
encounter.setDialogueToken("fastestPokemon", fastestPokemon.getNameToRender());
|
||||||
@ -127,7 +127,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
|||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
const numBerries = encounter.misc.numBerries;
|
const numBerries = encounter.misc.numBerries;
|
||||||
|
|
||||||
const doBerryRewards = async () => {
|
const doBerryRewards = () => {
|
||||||
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
||||||
|
|
||||||
scene.playSound("item_fanfare");
|
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
|
// Generate a random berry and give it to the first Pokemon with room for it
|
||||||
for (let i = 0; i < numBerries; i++) {
|
for (let i = 0; i < numBerries; i++) {
|
||||||
await tryGiveBerry(scene);
|
tryGiveBerry(scene);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
if (speedDiff < 1) {
|
if (speedDiff < 1) {
|
||||||
// Caught and attacked by boss, gets +1 to all stats at start of fight
|
// 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`);
|
const berryText = numBerries + " " + i18next.t(`${namespace}.berries`);
|
||||||
|
|
||||||
scene.playSound("item_fanfare");
|
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
|
// Generate a random berry and give it to the first Pokemon with room for it
|
||||||
for (let i = 0; i < numBerries; i++) {
|
for (let i = 0; i < numBerries; i++) {
|
||||||
await tryGiveBerry(scene);
|
tryGiveBerry(scene);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||||
|
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
|
||||||
|
[Stat.DEF, Stat.SPDEF, Stat.SPD] :
|
||||||
|
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||||
|
|
||||||
const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
const config = scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
||||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||||
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);
|
queueEncounterMessage(pokemon.scene, `${namespace}.option.2.boss_enraged`);
|
||||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1));
|
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||||
};
|
};
|
||||||
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
setEncounterRewards(scene, { guaranteedModifierTypeOptions: shopOptions, fillRemaining: false }, undefined, doBerryRewards);
|
||||||
await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
|
await showEncounterText(scene, `${namespace}.option.2.selected_bad`);
|
||||||
@ -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
|
// 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);
|
const numBerriesGrabbed = Math.max(Math.min(Math.round((speedDiff - 1)/0.08), numBerries), 2);
|
||||||
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
encounter.setDialogueToken("numBerries", String(numBerriesGrabbed));
|
||||||
const doFasterBerryRewards = async () => {
|
const doFasterBerryRewards = () => {
|
||||||
const berryText = numBerriesGrabbed + " " + i18next.t(`${namespace}.berries`);
|
const berryText = numBerriesGrabbed + " " + i18next.t(`${namespace}.berries`);
|
||||||
|
|
||||||
scene.playSound("item_fanfare");
|
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)
|
// Generate a random berry and give it to the first Pokemon with room for it (trying to give to fastest first)
|
||||||
for (let i = 0; i < numBerriesGrabbed; i++) {
|
for (let i = 0; i < numBerriesGrabbed; i++) {
|
||||||
await tryGiveBerry(scene, fastestPokemon);
|
tryGiveBerry(scene, fastestPokemon);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -242,7 +247,7 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
|||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
|
function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) {
|
||||||
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
|
const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType;
|
||||||
const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType;
|
||||||
|
|
||||||
@ -254,7 +259,7 @@ async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokem
|
|||||||
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
&& m.pokemonId === prioritizedPokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||||
|
|
||||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
||||||
await applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
|
applyModifierTypeToPlayerPokemon(scene, prioritizedPokemon, berry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,7 +270,7 @@ async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokem
|
|||||||
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
&& m.pokemonId === pokemon.id && (m as BerryModifier).berryType === berryType, true) as BerryModifier;
|
||||||
|
|
||||||
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
if (!heldBerriesOfType || heldBerriesOfType.getStackCount() < heldBerriesOfType.getMaxStackCount(scene)) {
|
||||||
await applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
|
applyModifierTypeToPlayerPokemon(scene, pokemon, berry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,9 +376,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
|||||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||||
// Get Pokemon held items and filter for valid ones
|
// Get Pokemon held items and filter for valid ones
|
||||||
const validItems = pokemon.getHeldItems().filter(item => {
|
const validItems = pokemon.getHeldItems().filter(item => {
|
||||||
return item instanceof BypassSpeedChanceModifier ||
|
return (item instanceof BypassSpeedChanceModifier ||
|
||||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
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) => {
|
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||||
@ -491,7 +492,7 @@ function getTrainerConfigForWave(waveIndex: number) {
|
|||||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
.setPartyMemberFunc(3, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||||
p.formIndex = pool3Mon.formIndex!;
|
p.formIndex = pool3Mon.formIndex;
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.generateName();
|
p.generateName();
|
||||||
}
|
}
|
||||||
@ -514,14 +515,14 @@ function getTrainerConfigForWave(waveIndex: number) {
|
|||||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||||
p.formIndex = pool3Mon.formIndex!;
|
p.formIndex = pool3Mon.formIndex;
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.generateName();
|
p.generateName();
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(4, getRandomPartyMemberFunc([pool3Mon2.species], TrainerSlot.TRAINER, true, p => {
|
||||||
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
|
if (!isNullOrUndefined(pool3Mon2.formIndex)) {
|
||||||
p.formIndex = pool3Mon2.formIndex!;
|
p.formIndex = pool3Mon2.formIndex;
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.generateName();
|
p.generateName();
|
||||||
}
|
}
|
||||||
@ -542,7 +543,7 @@ function getTrainerConfigForWave(waveIndex: number) {
|
|||||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
.setPartyMemberFunc(2, getRandomPartyMemberFunc(POOL_2_POKEMON, TrainerSlot.TRAINER, true))
|
||||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||||
p.formIndex = pool3Mon.formIndex!;
|
p.formIndex = pool3Mon.formIndex;
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.generateName();
|
p.generateName();
|
||||||
}
|
}
|
||||||
@ -565,14 +566,14 @@ function getTrainerConfigForWave(waveIndex: number) {
|
|||||||
}))
|
}))
|
||||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(2, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||||
p.formIndex = pool3Mon.formIndex!;
|
p.formIndex = pool3Mon.formIndex;
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.generateName();
|
p.generateName();
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(3, getRandomPartyMemberFunc([pool3Mon.species], TrainerSlot.TRAINER, true, p => {
|
||||||
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
if (!isNullOrUndefined(pool3Mon.formIndex)) {
|
||||||
p.formIndex = pool3Mon.formIndex!;
|
p.formIndex = pool3Mon.formIndex;
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.generateName();
|
p.generateName();
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,9 @@ import { Moves } from "#enums/moves";
|
|||||||
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
import { EncounterBattleAnim } from "#app/data/battle-anims";
|
||||||
import { MoveCategory } from "#app/data/move";
|
import { MoveCategory } from "#app/data/move";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
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 { EncounterAnim } from "#enums/encounter-anims";
|
||||||
|
import { Challenges } from "#enums/challenges";
|
||||||
|
|
||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:clowningAround";
|
const namespace = "mysteryEncounter:clowningAround";
|
||||||
@ -61,7 +62,7 @@ const RANDOM_ABILITY_POOL = [
|
|||||||
export const ClowningAroundEncounter: MysteryEncounter =
|
export const ClowningAroundEncounter: MysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND)
|
||||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||||
.withDisabledGameModes(GameModes.CHALLENGE)
|
.withDisallowedChallenges(Challenges.SINGLE_TYPE)
|
||||||
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
.withSceneWaveRangeRequirement(80, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
||||||
.withAnimations(EncounterAnim.SMOKESCREEN)
|
.withAnimations(EncounterAnim.SMOKESCREEN)
|
||||||
.withAutoHideIntroVisuals(false)
|
.withAutoHideIntroVisuals(false)
|
||||||
@ -349,10 +350,18 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
newTypes.push(secondType);
|
newTypes.push(secondType);
|
||||||
|
|
||||||
|
// Apply the type changes (to both base and fusion, if pokemon is fused)
|
||||||
if (!pokemon.mysteryEncounterPokemonData) {
|
if (!pokemon.mysteryEncounterPokemonData) {
|
||||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||||
}
|
}
|
||||||
pokemon.mysteryEncounterPokemonData.types = newTypes;
|
pokemon.mysteryEncounterPokemonData.types = newTypes;
|
||||||
|
if (pokemon.isFusion()) {
|
||||||
|
if (!pokemon.fusionMysteryEncounterPokemonData) {
|
||||||
|
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||||
|
}
|
||||||
|
pokemon.fusionMysteryEncounterPokemonData.types = newTypes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
@ -415,10 +424,17 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
|
|||||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||||
// Do ability swap
|
// Do ability swap
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
if (!pokemon.mysteryEncounterPokemonData) {
|
if (pokemon.isFusion()) {
|
||||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
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());
|
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||||
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
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 { Stat } from "#enums/stat";
|
||||||
import { EncounterAnim } from "#enums/encounter-anims";
|
import { EncounterAnim } from "#enums/encounter-anims";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounter:dancingLessons";
|
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) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
// If pokemon meets primary pokemon reqs, it can be selected
|
// 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);
|
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||||
if (!meetsReqs) {
|
if (!meetsReqs) {
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||||
|
@ -94,7 +94,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
|||||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "mad_scientist_m",
|
spriteKey: "dark_deal_scientist",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
},
|
},
|
||||||
@ -115,7 +115,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
.withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1])
|
.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)
|
.withCatchAllowed(true)
|
||||||
.withTitle(`${namespace}.title`)
|
.withTitle(`${namespace}.title`)
|
||||||
.withDescription(`${namespace}.description`)
|
.withDescription(`${namespace}.description`)
|
||||||
@ -139,7 +139,7 @@ export const DarkDealEncounter: MysteryEncounter =
|
|||||||
.withPreOptionPhase(async (scene: BattleScene) => {
|
.withPreOptionPhase(async (scene: BattleScene) => {
|
||||||
// Removes random pokemon (including fainted) from party and adds name to dialogue data tokens
|
// 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
|
// 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
|
// Get all the pokemon's held items
|
||||||
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier));
|
||||||
scene.removePokemonFromPlayerParty(removedPokemon);
|
scene.removePokemonFromPlayerParty(removedPokemon);
|
||||||
|
@ -10,7 +10,7 @@ import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement }
|
|||||||
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { HealingBoosterModifier, HiddenAbilityRateBoosterModifier, LevelIncrementBoosterModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier";
|
import { BerryModifier, HealingBoosterModifier, LevelIncrementBoosterModifier, MoneyMultiplierModifier, PokemonHeldItemModifier, PreserveBerryModifier } from "#app/modifier/modifier";
|
||||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import i18next from "#app/plugins/i18n";
|
import i18next from "#app/plugins/i18n";
|
||||||
@ -33,7 +33,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [
|
|||||||
"PokemonBaseStatTotalModifier"
|
"PokemonBaseStatTotalModifier"
|
||||||
];
|
];
|
||||||
|
|
||||||
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 1.5;
|
const DELIBIRDY_MONEY_PRICE_MULTIPLIER = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delibird-y encounter.
|
* Delibird-y encounter.
|
||||||
@ -122,9 +122,9 @@ export const DelibirdyEncounter: MysteryEncounter =
|
|||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.withOptionPhase(async (scene: BattleScene) => {
|
.withOptionPhase(async (scene: BattleScene) => {
|
||||||
// Give the player an Ability Charm
|
// Give the player an Amulet Coin
|
||||||
// Check if the player has max stacks of that item already
|
// Check if the player has max stacks of that item already
|
||||||
const existing = scene.findModifier(m => m instanceof HiddenAbilityRateBoosterModifier) as HiddenAbilityRateBoosterModifier;
|
const existing = scene.findModifier(m => m instanceof MoneyMultiplierModifier) as MoneyMultiplierModifier;
|
||||||
|
|
||||||
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
|
if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) {
|
||||||
// At max stacks, give the first party pokemon a Shell Bell instead
|
// At max stacks, give the first party pokemon a Shell Bell instead
|
||||||
@ -133,7 +133,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
|||||||
scene.playSound("item_fanfare");
|
scene.playSound("item_fanfare");
|
||||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true);
|
||||||
} else {
|
} else {
|
||||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ABILITY_CHARM));
|
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.AMULET_COIN));
|
||||||
}
|
}
|
||||||
|
|
||||||
leaveEncounterWithoutBattle(scene, true);
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
@ -159,7 +159,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
|||||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||||
// Get Pokemon held items and filter for valid ones
|
// Get Pokemon held items and filter for valid ones
|
||||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
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) => {
|
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) => {
|
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);
|
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||||
if (!meetsReqs) {
|
if (!meetsReqs) {
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||||
@ -197,7 +196,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
|||||||
const modifier = encounter.misc.chosenModifier;
|
const modifier = encounter.misc.chosenModifier;
|
||||||
|
|
||||||
// Give the player a Candy Jar if they gave a Berry, and a Healing Charm for Reviver Seed
|
// 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
|
// Check if the player has max stacks of that Candy Jar already
|
||||||
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
|
const existing = scene.findModifier(m => m instanceof LevelIncrementBoosterModifier) as LevelIncrementBoosterModifier;
|
||||||
|
|
||||||
@ -254,7 +253,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
|||||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||||
// Get Pokemon held items and filter for valid ones
|
// Get Pokemon held items and filter for valid ones
|
||||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
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) => {
|
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) => {
|
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);
|
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||||
if (!meetsReqs) {
|
if (!meetsReqs) {
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||||
|
@ -27,7 +27,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
|
|||||||
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "b2w2_lady",
|
spriteKey: "department_store_sale_lady",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: -20,
|
x: -20,
|
||||||
@ -63,7 +63,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter =
|
|||||||
// Choose TMs
|
// Choose TMs
|
||||||
const modifiers: ModifierTypeFunc[] = [];
|
const modifiers: ModifierTypeFunc[] = [];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (i < 4) {
|
while (i < 5) {
|
||||||
// 2/2/1 weight on TM rarity
|
// 2/2/1 weight on TM rarity
|
||||||
const roll = randSeedInt(5);
|
const roll = randSeedInt(5);
|
||||||
if (roll < 2) {
|
if (roll < 2) {
|
||||||
|
@ -24,7 +24,7 @@ const namespace = "mysteryEncounter:fieldTrip";
|
|||||||
export const FieldTripEncounter: MysteryEncounter =
|
export const FieldTripEncounter: MysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP)
|
||||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[0], 100)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "preschooler_m",
|
spriteKey: "preschooler_m",
|
||||||
@ -32,7 +32,7 @@ export const FieldTripEncounter: MysteryEncounter =
|
|||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: "teacher",
|
spriteKey: "field_trip_teacher",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
},
|
},
|
||||||
|
@ -189,7 +189,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Burn random member
|
// Burn random member
|
||||||
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.BURN);
|
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status.effect) || p.status.effect === StatusEffect.NONE);
|
||||||
if (burnable?.length > 0) {
|
if (burnable?.length > 0) {
|
||||||
const roll = randSeedInt(burnable.length);
|
const roll = randSeedInt(burnable.length);
|
||||||
const chosenPokemon = burnable[roll];
|
const chosenPokemon = burnable[roll];
|
||||||
|
@ -68,6 +68,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
|||||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
||||||
// Randomly boost 1 stat 2 stages
|
// 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));
|
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 Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { Species } from "#enums/species";
|
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 { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
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 */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:funAndGames";
|
const namespace = "mysteryEncounter:funAndGames";
|
||||||
@ -45,14 +46,14 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
|||||||
.withSkipToFightInput(true)
|
.withSkipToFightInput(true)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "carnival_game",
|
spriteKey: "fun_and_games_game",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: false,
|
hasShadow: false,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 6,
|
y: 6,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: "carnival_wobbuffet",
|
spriteKey: "fun_and_games_wobbuffet",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: -28,
|
x: -28,
|
||||||
@ -60,7 +61,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
|||||||
yShadow: 6
|
yShadow: 6
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: "carnival_man",
|
spriteKey: "fun_and_games_man",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: 40,
|
x: 40,
|
||||||
@ -110,12 +111,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Only Pokemon that are not KOed/legal can be selected
|
// Only Pokemon that are not KOed/legal can be selected
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
const meetsReqs = pokemon.isAllowedInBattle();
|
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||||
if (!meetsReqs) {
|
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
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 { getTypeRgb } from "#app/data/type";
|
||||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
|
import * as Utils from "#app/utils";
|
||||||
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||||
import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
||||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||||
import PokemonData from "#app/system/pokemon-data";
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
@ -28,6 +29,11 @@ import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-en
|
|||||||
/** the i18n namespace for the encounter */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:globalTradeSystem";
|
const namespace = "mysteryEncounter:globalTradeSystem";
|
||||||
|
|
||||||
|
/** Base shiny chance of 512/65536 -> 1/128 odds, affected by events and Shiny Charms. Cannot exceed 1/16 odds. */
|
||||||
|
const WONDER_TRADE_SHINY_CHANCE = 512;
|
||||||
|
/** Max shiny chance of 4096/65536 -> 1/16 odds. */
|
||||||
|
const MAX_WONDER_TRADE_SHINY_CHANCE = 4096;
|
||||||
|
|
||||||
const LEGENDARY_TRADE_POOLS = {
|
const LEGENDARY_TRADE_POOLS = {
|
||||||
1: [Species.RATTATA, Species.PIDGEY, Species.WEEDLE],
|
1: [Species.RATTATA, Species.PIDGEY, Species.WEEDLE],
|
||||||
2: [Species.SENTRET, Species.HOOTHOOT, Species.LEDYBA],
|
2: [Species.SENTRET, Species.HOOTHOOT, Species.LEDYBA],
|
||||||
@ -153,7 +159,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
onHover: () => {
|
onHover: () => {
|
||||||
const formName = tradePokemon.species.forms?.[pokemon.formIndex]?.formName;
|
const formName = tradePokemon.species.forms && tradePokemon.species.forms.length > tradePokemon.formIndex ? tradePokemon.species.forms[pokemon.formIndex].formName : null;
|
||||||
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
|
const line1 = i18next.t("pokemonInfoContainer:ability") + " " + tradePokemon.getAbility().name + (tradePokemon.getGender() !== Gender.GENDERLESS ? " | " + i18next.t("pokemonInfoContainer:gender") + " " + getGenderSymbol(tradePokemon.getGender()) : "");
|
||||||
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
|
const line2 = i18next.t("pokemonInfoContainer:nature") + " " + getNatureName(tradePokemon.getNature()) + (formName ? " | " + i18next.t("pokemonInfoContainer:form") + " " + formName : "");
|
||||||
showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false);
|
showEncounterText(scene, `${line1}\n${line2}`, 0, 0, false);
|
||||||
@ -221,21 +227,47 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
|||||||
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
||||||
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
||||||
if (!tradePokemon.shiny) {
|
if (!tradePokemon.shiny) {
|
||||||
// 512/65536 -> 1/128
|
const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE);
|
||||||
tradePokemon.trySetShinySeed(512, true);
|
if (scene.eventManager.isEventActive()) {
|
||||||
|
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
|
||||||
|
}
|
||||||
|
scene.applyModifiers(ShinyRateBoosterModifier, true, shinyThreshold);
|
||||||
|
|
||||||
|
// Base shiny chance of 512/65536 -> 1/128, affected by events and Shiny Charms
|
||||||
|
// Maximum shiny chance of 4096/65536 -> 1/16, cannot improve further after that
|
||||||
|
const shinyChance = Math.min(shinyThreshold.value, MAX_WONDER_TRADE_SHINY_CHANCE);
|
||||||
|
|
||||||
|
tradePokemon.trySetShinySeed(shinyChance, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra HA roll at base 1/64 odds (boosted by events and charms)
|
// Extra HA roll at base 1/64 odds (boosted by events and charms)
|
||||||
if (pokemon.species.abilityHidden) {
|
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
|
||||||
const hiddenIndex = pokemon.species.ability2 ? 2 : 1;
|
if (tradePokemon.species.abilityHidden) {
|
||||||
if (pokemon.abilityIndex < hiddenIndex) {
|
if (tradePokemon.abilityIndex < hiddenIndex) {
|
||||||
const hiddenAbilityChance = new IntegerHolder(64);
|
const hiddenAbilityChance = new IntegerHolder(64);
|
||||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||||
|
|
||||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||||
|
|
||||||
if (hasHiddenAbility) {
|
if (hasHiddenAbility) {
|
||||||
pokemon.abilityIndex = hiddenIndex;
|
tradePokemon.abilityIndex = hiddenIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Pokemon is still not shiny or with HA, give the Pokemon a random Common egg move in its moveset
|
||||||
|
if (!tradePokemon.shiny && (!tradePokemon.species.abilityHidden || tradePokemon.abilityIndex < hiddenIndex)) {
|
||||||
|
const eggMoves = tradePokemon.getEggMoves();
|
||||||
|
if (eggMoves) {
|
||||||
|
// Cannot gen the rare egg move, only 1 of the first 3 common moves
|
||||||
|
const eggMove = eggMoves[randSeedInt(3)];
|
||||||
|
if (!tradePokemon.moveset.some(m => m?.moveId === eggMove)) {
|
||||||
|
if (tradePokemon.moveset.length < 4) {
|
||||||
|
tradePokemon.moveset.push(new PokemonMove(eggMove));
|
||||||
|
} else {
|
||||||
|
const eggMoveIndex = randSeedInt(4);
|
||||||
|
tradePokemon.moveset[eggMoveIndex] = new PokemonMove(eggMove);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
// If pokemon has items to trade
|
// If pokemon has items to trade
|
||||||
const meetsReqs = pokemon.getHeldItems().filter((it) => {
|
const meetsReqs = pokemon.getHeldItems().filter((it) => {
|
||||||
@ -451,7 +482,7 @@ function generateTradeOption(alreadyUsedSpecies: PokemonSpecies[], originalBst?:
|
|||||||
if (validSpecies?.length > 20) {
|
if (validSpecies?.length > 20) {
|
||||||
validSpecies = randSeedShuffle(validSpecies);
|
validSpecies = randSeedShuffle(validSpecies);
|
||||||
newSpecies = validSpecies.pop();
|
newSpecies = validSpecies.pop();
|
||||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
|
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) {
|
||||||
newSpecies = validSpecies.pop();
|
newSpecies = validSpecies.pop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,7 +33,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with
|
|||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "buoy",
|
spriteKey: "lost_at_sea_buoy",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: false,
|
hasShadow: false,
|
||||||
x: 20,
|
x: 20,
|
||||||
|
@ -19,10 +19,11 @@ import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
|||||||
const namespace = "mysteryEncounter:mysteriousChest";
|
const namespace = "mysteryEncounter:mysteriousChest";
|
||||||
|
|
||||||
const RAND_LENGTH = 100;
|
const RAND_LENGTH = 100;
|
||||||
const COMMON_REWARDS_WEIGHT = 20; // 20%
|
const TRAP_PERCENT = 35;
|
||||||
const ULTRA_REWARDS_WEIGHT = 50; // 30%
|
const COMMON_REWARDS_PERCENT = 20;
|
||||||
const ROGUE_REWARDS_WEIGHT = 60; // 10%
|
const ULTRA_REWARDS_PERCENT = 30;
|
||||||
const MASTER_REWARDS_WEIGHT = 65; // 5%
|
const ROGUE_REWARDS_PERCENT = 10;
|
||||||
|
const MASTER_REWARDS_PERCENT = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mysterious Chest encounter.
|
* Mysterious Chest encounter.
|
||||||
@ -37,7 +38,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
.withCatchAllowed(true)
|
.withCatchAllowed(true)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "chest_blue",
|
spriteKey: "mysterious_chest_blue",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
y: 8,
|
y: 8,
|
||||||
@ -46,7 +47,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
disableAnimation: true, // Re-enabled after option select
|
disableAnimation: true, // Re-enabled after option select
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: "chest_red",
|
spriteKey: "mysterious_chest_red",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: false,
|
hasShadow: false,
|
||||||
y: 8,
|
y: 8,
|
||||||
@ -83,6 +84,11 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
encounter.enemyPartyConfigs = [config];
|
encounter.enemyPartyConfigs = [config];
|
||||||
|
|
||||||
encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName());
|
encounter.setDialogueToken("gimmighoulName", getPokemonSpecies(Species.GIMMIGHOUL).getName());
|
||||||
|
encounter.setDialogueToken("trapPercent", TRAP_PERCENT.toString());
|
||||||
|
encounter.setDialogueToken("commonPercent", COMMON_REWARDS_PERCENT.toString());
|
||||||
|
encounter.setDialogueToken("ultraPercent", ULTRA_REWARDS_PERCENT.toString());
|
||||||
|
encounter.setDialogueToken("roguePercent", ROGUE_REWARDS_PERCENT.toString());
|
||||||
|
encounter.setDialogueToken("masterPercent", MASTER_REWARDS_PERCENT.toString());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
@ -109,7 +115,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
roll
|
roll
|
||||||
};
|
};
|
||||||
|
|
||||||
if (roll >= MASTER_REWARDS_WEIGHT) {
|
if (roll < TRAP_PERCENT) {
|
||||||
// Chest is springing trap, change to red chest sprite
|
// Chest is springing trap, change to red chest sprite
|
||||||
const blueChestSprites = introVisuals.getSpriteAtIndex(0);
|
const blueChestSprites = introVisuals.getSpriteAtIndex(0);
|
||||||
const redChestSprites = introVisuals.getSpriteAtIndex(1);
|
const redChestSprites = introVisuals.getSpriteAtIndex(1);
|
||||||
@ -124,7 +130,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
// Open the chest
|
// Open the chest
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
const roll = encounter.misc.roll;
|
const roll = encounter.misc.roll;
|
||||||
if (roll < COMMON_REWARDS_WEIGHT) {
|
if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT) {
|
||||||
// Choose between 2 COMMON / 2 GREAT tier items (20%)
|
// Choose between 2 COMMON / 2 GREAT tier items (20%)
|
||||||
setEncounterRewards(scene, {
|
setEncounterRewards(scene, {
|
||||||
guaranteedModifierTiers: [
|
guaranteedModifierTiers: [
|
||||||
@ -137,7 +143,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(scene, `${namespace}.option.1.normal`);
|
queueEncounterMessage(scene, `${namespace}.option.1.normal`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll < ULTRA_REWARDS_WEIGHT) {
|
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT) {
|
||||||
// Choose between 3 ULTRA tier items (30%)
|
// Choose between 3 ULTRA tier items (30%)
|
||||||
setEncounterRewards(scene, {
|
setEncounterRewards(scene, {
|
||||||
guaranteedModifierTiers: [
|
guaranteedModifierTiers: [
|
||||||
@ -149,13 +155,13 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(scene, `${namespace}.option.1.good`);
|
queueEncounterMessage(scene, `${namespace}.option.1.good`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll < ROGUE_REWARDS_WEIGHT) {
|
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT) {
|
||||||
// Choose between 2 ROGUE tier items (10%)
|
// Choose between 2 ROGUE tier items (10%)
|
||||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE] });
|
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE] });
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
queueEncounterMessage(scene, `${namespace}.option.1.great`);
|
queueEncounterMessage(scene, `${namespace}.option.1.great`);
|
||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else if (roll < MASTER_REWARDS_WEIGHT) {
|
} else if (roll >= RAND_LENGTH - COMMON_REWARDS_PERCENT - ULTRA_REWARDS_PERCENT - ROGUE_REWARDS_PERCENT - MASTER_REWARDS_PERCENT) {
|
||||||
// Choose 1 MASTER tier item (5%)
|
// Choose 1 MASTER tier item (5%)
|
||||||
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER] });
|
setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.MASTER] });
|
||||||
// Display result message then proceed to rewards
|
// Display result message then proceed to rewards
|
||||||
@ -163,11 +169,12 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
leaveEncounterWithoutBattle(scene);
|
leaveEncounterWithoutBattle(scene);
|
||||||
} else {
|
} else {
|
||||||
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
|
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
|
||||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(
|
const highestLevelPokemon = getHighestLevelPlayerPokemon(scene, true, false);
|
||||||
scene,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
koPlayerPokemon(scene, highestLevelPokemon);
|
koPlayerPokemon(scene, highestLevelPokemon);
|
||||||
|
|
||||||
|
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||||
|
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
||||||
|
|
||||||
// Handle game over edge case
|
// Handle game over edge case
|
||||||
const allowedPokemon = scene.getParty().filter(p => p.isAllowedInBattle());
|
const allowedPokemon = scene.getParty().filter(p => p.isAllowedInBattle());
|
||||||
if (allowedPokemon.length === 0) {
|
if (allowedPokemon.length === 0) {
|
||||||
@ -176,8 +183,6 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
|||||||
scene.unshiftPhase(new GameOverPhase(scene));
|
scene.unshiftPhase(new GameOverPhase(scene));
|
||||||
} else {
|
} else {
|
||||||
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
// 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);
|
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||||
setEncounterRewards(scene, { fillRemaining: true });
|
setEncounterRewards(scene, { fillRemaining: true });
|
||||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
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 { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
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 i18next from "i18next";
|
||||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
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 */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:partTimer";
|
const namespace = "mysteryEncounter:partTimer";
|
||||||
@ -27,7 +28,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "warehouse_crate",
|
spriteKey: "part_timer_crate",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: false,
|
hasShadow: false,
|
||||||
y: 6,
|
y: 6,
|
||||||
@ -117,11 +118,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Only Pokemon non-KOd pokemon can be selected
|
// Only Pokemon non-KOd pokemon can be selected
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
if (!pokemon.isAllowedInBattle()) {
|
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||||
@ -198,11 +195,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Only Pokemon non-KOd pokemon can be selected
|
// Only Pokemon non-KOd pokemon can be selected
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
if (!pokemon.isAllowedInBattle()) {
|
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||||
|
@ -27,6 +27,8 @@ const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768];
|
|||||||
|
|
||||||
const SAFARI_MONEY_MULTIPLIER = 2;
|
const SAFARI_MONEY_MULTIPLIER = 2;
|
||||||
|
|
||||||
|
const NUM_SAFARI_ENCOUNTERS = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safari Zone encounter.
|
* Safari Zone encounter.
|
||||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800}
|
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3800 | GitHub Issue #3800}
|
||||||
@ -55,6 +57,10 @@ export const SafariZoneEncounter: MysteryEncounter =
|
|||||||
.withTitle(`${namespace}.title`)
|
.withTitle(`${namespace}.title`)
|
||||||
.withDescription(`${namespace}.description`)
|
.withDescription(`${namespace}.description`)
|
||||||
.withQuery(`${namespace}.query`)
|
.withQuery(`${namespace}.query`)
|
||||||
|
.withOnInit((scene: BattleScene) => {
|
||||||
|
scene.currentBattle.mysteryEncounter?.setDialogueToken("numEncounters", NUM_SAFARI_ENCOUNTERS.toString());
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.withOption(MysteryEncounterOptionBuilder
|
.withOption(MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||||
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
|
.withSceneRequirement(new MoneyRequirement(0, SAFARI_MONEY_MULTIPLIER)) // Cost equal to 1 Max Revive
|
||||||
@ -72,15 +78,15 @@ export const SafariZoneEncounter: MysteryEncounter =
|
|||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
encounter.continuousEncounter = true;
|
encounter.continuousEncounter = true;
|
||||||
encounter.misc = {
|
encounter.misc = {
|
||||||
safariPokemonRemaining: 3
|
safariPokemonRemaining: NUM_SAFARI_ENCOUNTERS
|
||||||
};
|
};
|
||||||
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney);
|
||||||
// Load bait/mud assets
|
// Load bait/mud assets
|
||||||
scene.loadSe("PRSFX- Bug Bite", "battle_anims", "PRSFX- Bug Bite.wav");
|
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- Sludge Bomb2", "battle_anims", "PRSFX- Sludge Bomb2.wav");
|
||||||
scene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav");
|
scene.loadSe("PRSFX- Taunt2", "battle_anims", "PRSFX- Taunt2.wav");
|
||||||
scene.loadAtlas("bait", "mystery-encounters");
|
scene.loadAtlas("safari_zone_bait", "mystery-encounters");
|
||||||
scene.loadAtlas("mud", "mystery-encounters");
|
scene.loadAtlas("safari_zone_mud", "mystery-encounters");
|
||||||
// Clear enemy party
|
// Clear enemy party
|
||||||
scene.currentBattle.enemyParty = [];
|
scene.currentBattle.enemyParty = [];
|
||||||
await transitionMysteryEncounterIntroVisuals(scene);
|
await transitionMysteryEncounterIntroVisuals(scene);
|
||||||
@ -254,7 +260,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
|||||||
let enemySpecies;
|
let enemySpecies;
|
||||||
let pokemon;
|
let pokemon;
|
||||||
scene.executeWithSeedOffset(() => {
|
scene.executeWithSeedOffset(() => {
|
||||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||||
const level = scene.currentBattle.getLevelForWave();
|
const level = scene.currentBattle.getLevelForWave();
|
||||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
||||||
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
||||||
@ -282,7 +288,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
|||||||
pokemon.calculateStats();
|
pokemon.calculateStats();
|
||||||
|
|
||||||
scene.currentBattle.enemyParty.unshift(pokemon);
|
scene.currentBattle.enemyParty.unshift(pokemon);
|
||||||
}, scene.currentBattle.waveIndex * 1000 + encounter.misc.safariPokemonRemaining);
|
}, scene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining);
|
||||||
|
|
||||||
scene.gameData.setPokemonSeen(pokemon, true);
|
scene.gameData.setPokemonSeen(pokemon, true);
|
||||||
await pokemon.loadAssets();
|
await pokemon.loadAssets();
|
||||||
@ -322,7 +328,7 @@ async function throwBait(scene: BattleScene, pokemon: EnemyPokemon): Promise<boo
|
|||||||
const originalY: number = pokemon.y;
|
const originalY: number = pokemon.y;
|
||||||
|
|
||||||
const fpOffset = pokemon.getFieldPositionOffset();
|
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);
|
bait.setOrigin(0.5, 0.625);
|
||||||
scene.field.add(bait);
|
scene.field.add(bait);
|
||||||
|
|
||||||
@ -388,7 +394,7 @@ async function throwMud(scene: BattleScene, pokemon: EnemyPokemon): Promise<bool
|
|||||||
const originalY: number = pokemon.y;
|
const originalY: number = pokemon.y;
|
||||||
|
|
||||||
const fpOffset = pokemon.getFieldPositionOffset();
|
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);
|
mud.setOrigin(0.5, 0.625);
|
||||||
scene.field.add(mud);
|
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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { getNatureName } from "#app/data/nature";
|
import { getNatureName } from "#app/data/nature";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
const namespace = "mysteryEncounter:shadyVitaminDealer";
|
const namespace = "mysteryEncounter:shadyVitaminDealer";
|
||||||
@ -32,7 +33,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
|
.withSceneRequirement(new MoneyRequirement(0, 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([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: Species.KROOKODILE.toString(),
|
spriteKey: Species.KROOKODILE.toString(),
|
||||||
@ -44,7 +45,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||||||
yShadow: -5
|
yShadow: -5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
spriteKey: "b2w2_veteran_m",
|
spriteKey: "shady_vitamin_dealer",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: -12,
|
x: -12,
|
||||||
@ -98,8 +99,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||||||
// Only Pokemon that can gain benefits are above half HP with no status
|
// Only Pokemon that can gain benefits are above half HP with no status
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
// If pokemon meets primary pokemon reqs, it can be selected
|
// If pokemon meets primary pokemon reqs, it can be selected
|
||||||
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
|
if (!pokemon.isAllowed()) {
|
||||||
if (!meetsReqs) {
|
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||||
|
}
|
||||||
|
if (!encounter.pokemonMeetsPrimaryRequirements(scene, pokemon)) {
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,13 +178,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Only Pokemon that can gain benefits are unfainted
|
// Only Pokemon that can gain benefits are unfainted
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
// If pokemon is unfainted it can be selected
|
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||||
const meetsReqs = !pokemon.isFainted(true);
|
|
||||||
if (!meetsReqs) {
|
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||||
|
@ -39,12 +39,12 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
|
|||||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
|
.withSceneRequirement(new WaveModulusRequirement([1, 2, 3], 10)) // Must be in first 3 waves after boss wave
|
||||||
.withSceneRequirement(new MoneyRequirement(undefined, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
|
.withSceneRequirement(new MoneyRequirement(0, MONEY_COST_MULTIPLIER)) // Must be able to pay teleport cost
|
||||||
.withAutoHideIntroVisuals(false)
|
.withAutoHideIntroVisuals(false)
|
||||||
.withCatchAllowed(true)
|
.withCatchAllowed(true)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "teleporter",
|
spriteKey: "teleporting_hijinks_teleporter",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
x: 4,
|
x: 4,
|
||||||
@ -73,7 +73,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter =
|
|||||||
.withOption(
|
.withOption(
|
||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||||
.withSceneMoneyRequirement(undefined, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
|
.withSceneMoneyRequirement(0, MONEY_COST_MULTIPLIER) // Must be able to pay teleport cost
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}.option.1.label`,
|
buttonLabel: `${namespace}.option.1.label`,
|
||||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||||
@ -171,6 +171,12 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
|
|||||||
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
|
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
|
||||||
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
|
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
|
||||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||||
|
|
||||||
|
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||||
|
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
|
||||||
|
[Stat.DEF, Stat.SPDEF, Stat.SPD] :
|
||||||
|
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||||
|
|
||||||
const config: EnemyPartyConfig = {
|
const config: EnemyPartyConfig = {
|
||||||
pokemonConfigs: [{
|
pokemonConfigs: [{
|
||||||
level: level,
|
level: level,
|
||||||
@ -180,7 +186,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
|
|||||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||||
queueEncounterMessage(pokemon.scene, `${namespace}.boss_enraged`);
|
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 { trainerConfigs } from "#app/data/trainer-config";
|
||||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { randSeedShuffle } from "#app/utils";
|
import { randSeedShuffle } from "#app/utils";
|
||||||
@ -14,8 +13,6 @@ import { Species } from "#enums/species";
|
|||||||
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Type } from "#app/data/type";
|
|
||||||
import { Stat } from "#enums/stat";
|
|
||||||
import { PlayerPokemon } from "#app/field/pokemon";
|
import { PlayerPokemon } from "#app/field/pokemon";
|
||||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { IEggOptions } from "#app/data/egg";
|
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 { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { achvs } from "#app/system/achv";
|
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 */
|
/** the i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:expertPokemonBreeder";
|
const namespace = "mysteryEncounter:expertPokemonBreeder";
|
||||||
|
|
||||||
const trainerNameKey = "trainerNames:expert_pokemon_breeder";
|
const trainerNameKey = "trainerNames:expert_pokemon_breeder";
|
||||||
|
|
||||||
const FIRST_STAGE_EVOLUTION_WAVE = 30;
|
const FIRST_STAGE_EVOLUTION_WAVE = 45;
|
||||||
const SECOND_STAGE_EVOLUTION_WAVE = 45;
|
const SECOND_STAGE_EVOLUTION_WAVE = 60;
|
||||||
const FINAL_STAGE_EVOLUTION_WAVE = 60;
|
const FINAL_STAGE_EVOLUTION_WAVE = 75;
|
||||||
|
|
||||||
const FRIENDSHIP_ADDED = 20;
|
const FRIENDSHIP_ADDED = 20;
|
||||||
|
|
||||||
@ -216,6 +216,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||||
|
|
||||||
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
|
const { pokemon1, pokemon1CommonEggs, pokemon1RareEggs } = encounter.misc;
|
||||||
|
encounter.misc.chosenPokemon = pokemon1;
|
||||||
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
|
encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender());
|
||||||
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
|
const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs);
|
||||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
||||||
@ -241,14 +242,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encounter.onGameOver = onGameOver;
|
||||||
initBattleWithEnemyConfig(scene, config);
|
initBattleWithEnemyConfig(scene, config);
|
||||||
})
|
})
|
||||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||||
// Give achievement if in Space biome
|
await doPostEncounterCleanup(scene);
|
||||||
checkAchievement(scene);
|
|
||||||
// Give 20 friendship to the chosen pokemon
|
|
||||||
scene.currentBattle.mysteryEncounter!.misc.pokemon1.addFriendship(FRIENDSHIP_ADDED);
|
|
||||||
await restorePartyAndHeldItems(scene);
|
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@ -270,6 +268,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||||
|
|
||||||
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
|
const { pokemon2, pokemon2CommonEggs, pokemon2RareEggs } = encounter.misc;
|
||||||
|
encounter.misc.chosenPokemon = pokemon2;
|
||||||
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
|
encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender());
|
||||||
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
|
const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs);
|
||||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
||||||
@ -295,14 +294,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encounter.onGameOver = onGameOver;
|
||||||
initBattleWithEnemyConfig(scene, config);
|
initBattleWithEnemyConfig(scene, config);
|
||||||
})
|
})
|
||||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||||
// Give achievement if in Space biome
|
await doPostEncounterCleanup(scene);
|
||||||
checkAchievement(scene);
|
|
||||||
// Give 20 friendship to the chosen pokemon
|
|
||||||
scene.currentBattle.mysteryEncounter!.misc.pokemon2.addFriendship(FRIENDSHIP_ADDED);
|
|
||||||
await restorePartyAndHeldItems(scene);
|
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@ -324,6 +320,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0];
|
||||||
|
|
||||||
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
|
const { pokemon3, pokemon3CommonEggs, pokemon3RareEggs } = encounter.misc;
|
||||||
|
encounter.misc.chosenPokemon = pokemon3;
|
||||||
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
|
encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender());
|
||||||
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
|
const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs);
|
||||||
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
setEncounterRewards(scene, { fillRemaining: true }, eggOptions);
|
||||||
@ -349,19 +346,17 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encounter.onGameOver = onGameOver;
|
||||||
initBattleWithEnemyConfig(scene, config);
|
initBattleWithEnemyConfig(scene, config);
|
||||||
})
|
})
|
||||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||||
// Give achievement if in Space biome
|
await doPostEncounterCleanup(scene);
|
||||||
checkAchievement(scene);
|
|
||||||
// Give 20 friendship to the chosen pokemon
|
|
||||||
scene.currentBattle.mysteryEncounter!.misc.pokemon3.addFriendship(FRIENDSHIP_ADDED);
|
|
||||||
await restorePartyAndHeldItems(scene);
|
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.withOutroDialogue([
|
.withOutroDialogue([
|
||||||
{
|
{
|
||||||
|
speaker: trainerNameKey,
|
||||||
text: `${namespace}.outro`,
|
text: `${namespace}.outro`,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -390,15 +385,7 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
|
|||||||
modifierConfigs: [
|
modifierConfigs: [
|
||||||
{
|
{
|
||||||
modifier: generateModifierType(scene, modifierTypes.TERA_SHARD, [Type.STEEL]) as PokemonHeldItemModifierType,
|
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);
|
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)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN)
|
||||||
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
.withEncounterTier(MysteryEncounterTier.ULTRA)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
.withSceneRequirement(new MoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
|
.withSceneRequirement(new MoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER)) // Some costs may not be as significant, this is the max you'd pay
|
||||||
.withAutoHideIntroVisuals(false)
|
.withAutoHideIntroVisuals(false)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
@ -58,12 +58,13 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
.withOnInit((scene: BattleScene) => {
|
.withOnInit((scene: BattleScene) => {
|
||||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||||
|
|
||||||
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
let species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||||
const tries = 0;
|
let tries = 0;
|
||||||
|
|
||||||
// Reroll any species that don't have HAs
|
// Reroll any species that don't have HAs
|
||||||
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
||||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||||
|
tries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pokemon: PlayerPokemon;
|
let pokemon: PlayerPokemon;
|
||||||
@ -71,7 +72,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
// If no HA mon found or you roll 1%, give shiny Magikarp
|
// If no HA mon found or you roll 1%, give shiny Magikarp
|
||||||
species = getPokemonSpecies(Species.MAGIKARP);
|
species = getPokemonSpecies(Species.MAGIKARP);
|
||||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true);
|
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true, 0);
|
||||||
} else {
|
} else {
|
||||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
|
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
|
||||||
@ -113,7 +114,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
|||||||
MysteryEncounterOptionBuilder
|
MysteryEncounterOptionBuilder
|
||||||
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
.newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_DEFAULT)
|
||||||
.withHasDexProgress(true)
|
.withHasDexProgress(true)
|
||||||
.withSceneMoneyRequirement(undefined, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
|
.withSceneMoneyRequirement(0, MAX_POKEMON_PRICE_MULTIPLIER) // Wave scaling money multiplier of 2
|
||||||
.withDialogue({
|
.withDialogue({
|
||||||
buttonLabel: `${namespace}.option.1.label`,
|
buttonLabel: `${namespace}.option.1.label`,
|
||||||
buttonTooltip: `${namespace}.option.1.tooltip`,
|
buttonTooltip: `${namespace}.option.1.tooltip`,
|
||||||
|
@ -33,7 +33,7 @@ const BST_INCREASE_VALUE = 10;
|
|||||||
*/
|
*/
|
||||||
export const TheStrongStuffEncounter: MysteryEncounter =
|
export const TheStrongStuffEncounter: MysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF)
|
||||||
.withEncounterTier(MysteryEncounterTier.GREAT)
|
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
|
.withScenePartySizeRequirement(3, 6) // Must have at least 3 pokemon in party
|
||||||
.withMaxAllowedEncounters(1)
|
.withMaxAllowedEncounters(1)
|
||||||
|
@ -13,13 +13,14 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { getStatKey } from "#enums/stat";
|
import { getStatKey } from "#enums/stat";
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
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 */
|
/** The i18n namespace for the encounter */
|
||||||
const namespace = "mysteryEncounter:trainingSession";
|
const namespace = "mysteryEncounter:trainingSession";
|
||||||
@ -38,7 +39,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||||||
.withHideWildIntroMessage(true)
|
.withHideWildIntroMessage(true)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
spriteKey: "training_gear",
|
spriteKey: "training_session_gear",
|
||||||
fileRoot: "mystery-encounters",
|
fileRoot: "mystery-encounters",
|
||||||
hasShadow: true,
|
hasShadow: true,
|
||||||
y: 6,
|
y: 6,
|
||||||
@ -77,12 +78,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Only Pokemon that are not KOed/legal can be trained
|
// Only Pokemon that are not KOed/legal can be trained
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
const meetsReqs = pokemon.isAllowedInBattle();
|
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||||
if (!meetsReqs) {
|
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||||
@ -211,12 +207,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Only Pokemon that are not KOed/legal can be trained
|
// Only Pokemon that are not KOed/legal can be trained
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
const meetsReqs = pokemon.isAllowedInBattle();
|
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||||
if (!meetsReqs) {
|
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||||
@ -307,12 +298,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Only Pokemon that are not KOed/legal can be trained
|
// Only Pokemon that are not KOed/legal can be trained
|
||||||
const selectableFilter = (pokemon: Pokemon) => {
|
const selectableFilter = (pokemon: Pokemon) => {
|
||||||
const meetsReqs = pokemon.isAllowedInBattle();
|
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||||
if (!meetsReqs) {
|
|
||||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||||
@ -338,21 +324,18 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
|||||||
const abilityIndex = encounter.misc.abilityIndex;
|
const abilityIndex = encounter.misc.abilityIndex;
|
||||||
if (!!playerPokemon.getFusionSpeciesForm()) {
|
if (!!playerPokemon.getFusionSpeciesForm()) {
|
||||||
playerPokemon.fusionAbilityIndex = abilityIndex;
|
playerPokemon.fusionAbilityIndex = abilityIndex;
|
||||||
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies!.speciesId)) {
|
if (!isNullOrUndefined(playerPokemon.fusionSpecies?.speciesId) && speciesStarters.hasOwnProperty(playerPokemon.fusionSpecies.speciesId)) {
|
||||||
scene.gameData.starterData[playerPokemon.fusionSpecies!.speciesId]
|
scene.gameData.starterData[playerPokemon.fusionSpecies.speciesId]
|
||||||
.abilityAttr |=
|
.abilityAttr |=
|
||||||
abilityIndex !== 1 || playerPokemon.fusionSpecies!.ability2
|
abilityIndex !== 1 || playerPokemon.fusionSpecies.ability2
|
||||||
? Math.pow(2, playerPokemon.fusionAbilityIndex)
|
? Math.pow(2, playerPokemon.fusionAbilityIndex)
|
||||||
: AbilityAttr.ABILITY_HIDDEN;
|
: AbilityAttr.ABILITY_HIDDEN;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
playerPokemon.abilityIndex = abilityIndex;
|
playerPokemon.abilityIndex = abilityIndex;
|
||||||
if (
|
if (speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)) {
|
||||||
speciesStarters.hasOwnProperty(playerPokemon.species.speciesId)
|
scene.gameData.starterData[playerPokemon.species.speciesId]
|
||||||
) {
|
.abilityAttr |=
|
||||||
scene.gameData.starterData[
|
|
||||||
playerPokemon.species.speciesId
|
|
||||||
].abilityAttr |=
|
|
||||||
abilityIndex !== 1 || playerPokemon.species.ability2
|
abilityIndex !== 1 || playerPokemon.species.ability2
|
||||||
? Math.pow(2, playerPokemon.abilityIndex)
|
? Math.pow(2, playerPokemon.abilityIndex)
|
||||||
: AbilityAttr.ABILITY_HIDDEN;
|
: AbilityAttr.ABILITY_HIDDEN;
|
||||||
|
@ -16,7 +16,6 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
|
|||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { PokemonMove } from "#app/field/pokemon";
|
import { PokemonMove } from "#app/field/pokemon";
|
||||||
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
|
|
||||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||||
|
|
||||||
/** the i18n namespace for this encounter */
|
/** the i18n namespace for this encounter */
|
||||||
@ -24,6 +23,9 @@ const namespace = "mysteryEncounter:trashToTreasure";
|
|||||||
|
|
||||||
const SOUND_EFFECT_WAIT_TIME = 700;
|
const SOUND_EFFECT_WAIT_TIME = 700;
|
||||||
|
|
||||||
|
// Items will cost 2.5x as much for remainder of the run
|
||||||
|
const SHOP_ITEM_COST_MULTIPLIER = 2.5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trash to Treasure encounter.
|
* Trash to Treasure encounter.
|
||||||
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809}
|
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3809 | GitHub Issue #3809}
|
||||||
@ -79,6 +81,8 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
|||||||
scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
|
scene.loadSe("PRSFX- Dig2", "battle_anims", "PRSFX- Dig2.wav");
|
||||||
scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
|
scene.loadSe("PRSFX- Venom Drench", "battle_anims", "PRSFX- Venom Drench.wav");
|
||||||
|
|
||||||
|
encounter.setDialogueToken("costMultiplier", SHOP_ITEM_COST_MULTIPLIER.toString());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.withOption(
|
.withOption(
|
||||||
@ -102,8 +106,14 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
|||||||
transitionMysteryEncounterIntroVisuals(scene);
|
transitionMysteryEncounterIntroVisuals(scene);
|
||||||
await tryApplyDigRewardItems(scene);
|
await tryApplyDigRewardItems(scene);
|
||||||
|
|
||||||
// Give the player the Black Sludge curse
|
const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [SHOP_ITEM_COST_MULTIPLIER]);
|
||||||
scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE));
|
const modifier = blackSludge?.newModifier();
|
||||||
|
if (modifier) {
|
||||||
|
await scene.addModifier(modifier, false, false, false, true);
|
||||||
|
scene.playSound("battle_anims/PRSFX- Venom Drench", { volume: 2 });
|
||||||
|
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: modifier.type.name }), null, undefined, true);
|
||||||
|
}
|
||||||
|
|
||||||
leaveEncounterWithoutBattle(scene, true);
|
leaveEncounterWithoutBattle(scene, true);
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
@ -180,7 +190,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scene.playSound("item_fanfare");
|
scene.playSound("item_fanfare");
|
||||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + leftovers.name }), null, undefined, true);
|
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2x " + leftovers.name }), null, undefined, true);
|
||||||
|
|
||||||
// First Shell bell
|
// First Shell bell
|
||||||
for (const pokemon of party) {
|
for (const pokemon of party) {
|
||||||
@ -207,7 +217,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scene.playSound("item_fanfare");
|
scene.playSound("item_fanfare");
|
||||||
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2 " + shellBell.name }), null, undefined, true);
|
await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: "2x " + shellBell.name }), null, undefined, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doGarbageDig(scene: BattleScene) {
|
async function doGarbageDig(scene: BattleScene) {
|
||||||
|
@ -12,7 +12,6 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
|
|||||||
import { TrainerSlot } from "#app/data/trainer-config";
|
import { TrainerSlot } from "#app/data/trainer-config";
|
||||||
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
import { catchPokemon, getHighestLevelPlayerPokemon, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||||
import PokemonData from "#app/system/pokemon-data";
|
import PokemonData from "#app/system/pokemon-data";
|
||||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
|
||||||
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
import { isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
@ -50,14 +49,13 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
// Calculate boss mon
|
// Calculate boss mon
|
||||||
// Level equal to 2 below highest party member
|
// 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 species = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
|
||||||
const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true);
|
const pokemon = new EnemyPokemon(scene, species, level, TrainerSlot.NONE, true);
|
||||||
const speciesRootForm = pokemon.species.getRootSpeciesId();
|
|
||||||
|
|
||||||
// Pokemon will always have one of its egg moves in its moveset
|
// Pokemon will always have one of its egg moves in its moveset
|
||||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
const eggMoves = pokemon.getEggMoves();
|
||||||
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
|
if (eggMoves) {
|
||||||
const eggMoveIndex = randSeedInt(4);
|
const eggMoveIndex = randSeedInt(4);
|
||||||
const randomEggMove: Moves = eggMoves[eggMoveIndex];
|
const randomEggMove: Moves = eggMoves[eggMoveIndex];
|
||||||
encounter.misc = {
|
encounter.misc = {
|
||||||
@ -72,6 +70,11 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
|
|
||||||
encounter.misc.pokemon = pokemon;
|
encounter.misc.pokemon = pokemon;
|
||||||
|
|
||||||
|
// Defense/Spd buffs below wave 50, +1 to all stats otherwise
|
||||||
|
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
|
||||||
|
[Stat.DEF, Stat.SPDEF, Stat.SPD] :
|
||||||
|
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||||
|
|
||||||
const config: EnemyPartyConfig = {
|
const config: EnemyPartyConfig = {
|
||||||
pokemonConfigs: [{
|
pokemonConfigs: [{
|
||||||
level: level,
|
level: level,
|
||||||
@ -81,7 +84,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
||||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1));
|
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
@ -193,20 +196,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
const pokemon = encounter.misc.pokemon;
|
const pokemon = encounter.misc.pokemon;
|
||||||
|
|
||||||
// Give 1 additional egg move
|
// Give 1 additional egg move
|
||||||
const previousEggMove = encounter.misc.eggMove;
|
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||||
const speciesRootForm = pokemon.species.getRootSpeciesId();
|
|
||||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
|
||||||
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
|
|
||||||
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
|
|
||||||
while (randomEggMove === previousEggMove) {
|
|
||||||
randomEggMove = eggMoves[randSeedInt(4)];
|
|
||||||
}
|
|
||||||
if (pokemon.moveset.length < 4) {
|
|
||||||
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
|
||||||
} else {
|
|
||||||
pokemon.moveset[1] = new PokemonMove(randomEggMove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
|
await catchPokemon(scene, pokemon, null, PokeballType.POKEBALL, false);
|
||||||
setEncounterRewards(scene, { fillRemaining: true });
|
setEncounterRewards(scene, { fillRemaining: true });
|
||||||
@ -235,20 +225,7 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
const pokemon = encounter.misc.pokemon;
|
const pokemon = encounter.misc.pokemon;
|
||||||
|
|
||||||
// Give 1 additional egg move
|
// Give 1 additional egg move
|
||||||
const previousEggMove = encounter.misc.eggMove;
|
givePokemonExtraEggMove(pokemon, encounter.misc.eggMove);
|
||||||
const speciesRootForm = pokemon.species.getRootSpeciesId();
|
|
||||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
|
||||||
const eggMoves: Moves[] = speciesEggMoves[speciesRootForm];
|
|
||||||
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
|
|
||||||
while (randomEggMove === previousEggMove) {
|
|
||||||
randomEggMove = eggMoves[randSeedInt(4)];
|
|
||||||
}
|
|
||||||
if (pokemon.moveset.length < 4) {
|
|
||||||
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
|
||||||
} else {
|
|
||||||
pokemon.moveset[1] = new PokemonMove(randomEggMove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Roll IVs a second time
|
// Roll IVs a second time
|
||||||
pokemon.ivs = pokemon.ivs.map(iv => {
|
pokemon.ivs = pokemon.ivs.map(iv => {
|
||||||
@ -266,3 +243,18 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
function givePokemonExtraEggMove(pokemon: EnemyPokemon, previousEggMove: Moves) {
|
||||||
|
const eggMoves = pokemon.getEggMoves();
|
||||||
|
if (eggMoves) {
|
||||||
|
let randomEggMove: Moves = eggMoves[randSeedInt(4)];
|
||||||
|
while (randomEggMove === previousEggMove) {
|
||||||
|
randomEggMove = eggMoves[randSeedInt(4)];
|
||||||
|
}
|
||||||
|
if (pokemon.moveset.length < 4) {
|
||||||
|
pokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||||
|
} else {
|
||||||
|
pokemon.moveset[1] = new PokemonMove(randomEggMove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,6 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "
|
|||||||
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
|
||||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
|
||||||
import { achvs } from "#app/system/achv";
|
import { achvs } from "#app/system/achv";
|
||||||
import { speciesEggMoves } from "#app/data/egg-moves";
|
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||||
@ -20,7 +19,8 @@ import i18next from "#app/plugins/i18n";
|
|||||||
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence";
|
||||||
import { getLevelTotalExp } from "#app/data/exp";
|
import { getLevelTotalExp } from "#app/data/exp";
|
||||||
import { Stat } from "#enums/stat";
|
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 */
|
/** i18n namespace for encounter */
|
||||||
const namespace = "mysteryEncounter:weirdDream";
|
const namespace = "mysteryEncounter:weirdDream";
|
||||||
@ -82,6 +82,9 @@ const SUPER_LEGENDARY_BST_THRESHOLD = 600;
|
|||||||
const NON_LEGENDARY_BST_THRESHOLD = 570;
|
const NON_LEGENDARY_BST_THRESHOLD = 570;
|
||||||
const GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD = 450;
|
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
|
* Value ranges of the resulting species BST transformations after adding values to original species
|
||||||
* 2 Pokemon in the party use this range
|
* 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 =
|
export const WeirdDreamEncounter: MysteryEncounter =
|
||||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
|
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM)
|
||||||
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
.withEncounterTier(MysteryEncounterTier.ROGUE)
|
||||||
.withDisabledGameModes(GameModes.CHALLENGE)
|
.withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION)
|
||||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||||
.withIntroSpriteConfigs([
|
.withIntroSpriteConfigs([
|
||||||
{
|
{
|
||||||
@ -207,12 +210,12 @@ export const WeirdDreamEncounter: MysteryEncounter =
|
|||||||
async (scene: BattleScene) => {
|
async (scene: BattleScene) => {
|
||||||
// Reduce party levels by 20%
|
// Reduce party levels by 20%
|
||||||
for (const pokemon of scene.getParty()) {
|
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.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
||||||
pokemon.levelExp = 0;
|
pokemon.levelExp = 0;
|
||||||
|
|
||||||
pokemon.calculateStats();
|
pokemon.calculateStats();
|
||||||
pokemon.updateInfo();
|
await pokemon.updateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
leaveEncounterWithoutBattle(scene, true);
|
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)
|
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
|
||||||
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
|
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
|
||||||
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
|
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
|
||||||
@ -349,22 +358,17 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
|||||||
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
|
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;
|
newPokemon.moveset = previousPokemon.moveset;
|
||||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
|
||||||
const eggMoves = speciesEggMoves[speciesRootForm];
|
const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm);
|
||||||
const eggMoveIndex = randSeedInt(4);
|
|
||||||
const randomEggMove = eggMoves[eggMoveIndex];
|
// Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset)
|
||||||
if (newPokemon.moveset.length < 4) {
|
addFavoredMoveToNewPokemonMoveset(scene, newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Randomize the second type of the pokemon
|
// Randomize the second type of the pokemon
|
||||||
// If the pokemon does not normally have a second type, it will gain 1
|
// If the pokemon does not normally have a second type, it will gain 1
|
||||||
@ -381,7 +385,7 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
|||||||
|
|
||||||
for (const item of transformation.heldItems) {
|
for (const item of transformation.heldItems) {
|
||||||
item.pokemonId = newPokemon.id;
|
item.pokemonId = newPokemon.id;
|
||||||
scene.addModifier(item, false, false, false, true);
|
await scene.addModifier(item, false, false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef
|
// Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef
|
||||||
@ -392,11 +396,12 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
|||||||
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK);
|
||||||
// Def or SpDef
|
// Def or SpDef
|
||||||
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF);
|
||||||
// const mod = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().newModifier(newPokemon, 20, stats);
|
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU()
|
||||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU().generateType(scene.getParty(), [20, stats]);
|
.generateType(scene.getParty(), [20, stats])
|
||||||
|
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU);
|
||||||
const modifier = modType?.newModifier(newPokemon);
|
const modifier = modType?.newModifier(newPokemon);
|
||||||
if (modifier) {
|
if (modifier) {
|
||||||
scene.addModifier(modifier);
|
await scene.addModifier(modifier, false, false, false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,13 +409,15 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
|||||||
newPokemon.passive = previousPokemon.passive;
|
newPokemon.passive = previousPokemon.passive;
|
||||||
|
|
||||||
newPokemon.calculateStats();
|
newPokemon.calculateStats();
|
||||||
newPokemon.initBattleInfo();
|
await newPokemon.updateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// One random pokemon will get its passive unlocked
|
// One random pokemon will get its passive unlocked
|
||||||
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
|
const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive);
|
||||||
if (passiveDisabledPokemon?.length > 0) {
|
if (passiveDisabledPokemon?.length > 0) {
|
||||||
passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)].passive = true;
|
const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)];
|
||||||
|
enablePassiveMon.passive = true;
|
||||||
|
await enablePassiveMon.updateInfo(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If at least one new starter was unlocked, play 1 fanfare
|
// If at least one new starter was unlocked, play 1 fanfare
|
||||||
@ -440,7 +447,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
|
|||||||
if (validSpecies?.length > 20) {
|
if (validSpecies?.length > 20) {
|
||||||
validSpecies = randSeedShuffle(validSpecies);
|
validSpecies = randSeedShuffle(validSpecies);
|
||||||
newSpecies = validSpecies.pop();
|
newSpecies = validSpecies.pop();
|
||||||
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies!)) {
|
while (isNullOrUndefined(newSpecies) || alreadyUsedSpecies.includes(newSpecies)) {
|
||||||
newSpecies = validSpecies.pop();
|
newSpecies = validSpecies.pop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -450,7 +457,7 @@ function getTransformedSpecies(originalBst: number, bstSearchRange: [number, num
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newSpecies!;
|
return newSpecies;
|
||||||
}
|
}
|
||||||
|
|
||||||
function doShowDreamBackground(scene: BattleScene) {
|
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 });
|
return Object.assign(this, { requirements: this.requirements });
|
||||||
}
|
}
|
||||||
|
|
||||||
withSceneMoneyRequirement(requiredMoney?: number, scalingMultiplier?: number) {
|
withSceneMoneyRequirement(requiredMoney: number, scalingMultiplier?: number) {
|
||||||
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
|
return this.withSceneRequirement(new MoneyRequirement(requiredMoney, scalingMultiplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,9 +165,9 @@ export class WaveRangeRequirement extends EncounterSceneRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(scene: BattleScene): boolean {
|
override meetsRequirement(scene: BattleScene): boolean {
|
||||||
if (!isNullOrUndefined(this.waveRange) && this.waveRange?.[0] <= this.waveRange?.[1]) {
|
if (!isNullOrUndefined(this.waveRange) && this.waveRange[0] <= this.waveRange[1]) {
|
||||||
const waveIndex = scene.currentBattle.waveIndex;
|
const waveIndex = scene.currentBattle.waveIndex;
|
||||||
if (waveIndex >= 0 && (this.waveRange?.[0] >= 0 && this.waveRange?.[0] > waveIndex) || (this.waveRange?.[1] >= 0 && this.waveRange?.[1] < waveIndex)) {
|
if (waveIndex >= 0 && (this.waveRange[0] >= 0 && this.waveRange[0] > waveIndex) || (this.waveRange[1] >= 0 && this.waveRange[1] < waveIndex)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,7 +251,7 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
|||||||
const currentWeather = scene.arena.weather?.weatherType;
|
const currentWeather = scene.arena.weather?.weatherType;
|
||||||
let token = "";
|
let token = "";
|
||||||
if (!isNullOrUndefined(currentWeather)) {
|
if (!isNullOrUndefined(currentWeather)) {
|
||||||
token = WeatherType[currentWeather!].replace("_", " ").toLocaleLowerCase();
|
token = WeatherType[currentWeather].replace("_", " ").toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
return ["weather", token];
|
return ["weather", token];
|
||||||
}
|
}
|
||||||
@ -259,24 +259,24 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
|||||||
|
|
||||||
export class PartySizeRequirement extends EncounterSceneRequirement {
|
export class PartySizeRequirement extends EncounterSceneRequirement {
|
||||||
partySizeRange: [number, number];
|
partySizeRange: [number, number];
|
||||||
excludeFainted: boolean;
|
excludeDisallowedPokemon: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for specifying a party size requirement
|
* Used for specifying a party size requirement
|
||||||
* If min and max are equivalent, will check for exact size
|
* If min and max are equivalent, will check for exact size
|
||||||
* @param partySizeRange
|
* @param partySizeRange
|
||||||
* @param excludeFainted
|
* @param excludeDisallowedPokemon
|
||||||
*/
|
*/
|
||||||
constructor(partySizeRange: [number, number], excludeFainted: boolean) {
|
constructor(partySizeRange: [number, number], excludeDisallowedPokemon: boolean) {
|
||||||
super();
|
super();
|
||||||
this.partySizeRange = partySizeRange;
|
this.partySizeRange = partySizeRange;
|
||||||
this.excludeFainted = excludeFainted;
|
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(scene: BattleScene): boolean {
|
override meetsRequirement(scene: BattleScene): boolean {
|
||||||
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange[0] <= this.partySizeRange[1]) {
|
||||||
const partySize = this.excludeFainted ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
const partySize = this.excludeDisallowedPokemon ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
||||||
if (partySize >= 0 && (this.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
|
if (partySize >= 0 && (this.partySizeRange[0] >= 0 && this.partySizeRange[0] > partySize) || (this.partySizeRange[1] >= 0 && this.partySizeRange[1] < partySize)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -326,7 +326,7 @@ export class MoneyRequirement extends EncounterSceneRequirement {
|
|||||||
requiredMoney: number; // Static value
|
requiredMoney: number; // Static value
|
||||||
scalingMultiplier: number; // Calculates required money based off wave index
|
scalingMultiplier: number; // Calculates required money based off wave index
|
||||||
|
|
||||||
constructor(requiredMoney?: number, scalingMultiplier?: number) {
|
constructor(requiredMoney: number, scalingMultiplier?: number) {
|
||||||
super();
|
super();
|
||||||
this.requiredMoney = requiredMoney ?? 0;
|
this.requiredMoney = requiredMoney ?? 0;
|
||||||
this.scalingMultiplier = scalingMultiplier ?? 0;
|
this.scalingMultiplier = scalingMultiplier ?? 0;
|
||||||
@ -418,8 +418,8 @@ export class NatureRequirement extends EncounterPokemonRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||||
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon!.nature)) {
|
if (!isNullOrUndefined(pokemon?.nature) && this.requiredNature.includes(pokemon.nature)) {
|
||||||
return ["nature", Nature[pokemon!.nature]];
|
return ["nature", Nature[pokemon.nature]];
|
||||||
}
|
}
|
||||||
return ["nature", ""];
|
return ["nature", ""];
|
||||||
}
|
}
|
||||||
@ -620,7 +620,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
|||||||
return this.requiredStatusEffect.some((statusEffect) => {
|
return this.requiredStatusEffect.some((statusEffect) => {
|
||||||
if (statusEffect === StatusEffect.NONE) {
|
if (statusEffect === StatusEffect.NONE) {
|
||||||
// StatusEffect.NONE also checks for null or undefined status
|
// StatusEffect.NONE also checks for null or undefined status
|
||||||
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect;
|
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect;
|
||||||
} else {
|
} else {
|
||||||
return pokemon.status?.effect === statusEffect;
|
return pokemon.status?.effect === statusEffect;
|
||||||
}
|
}
|
||||||
@ -628,12 +628,11 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects
|
// for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects
|
||||||
// return partyPokemon.filter((pokemon) => this.requiredStatusEffect.filter((statusEffect) => pokemon.status?.effect === statusEffect).length === 0);
|
|
||||||
return partyPokemon.filter((pokemon) => {
|
return partyPokemon.filter((pokemon) => {
|
||||||
return !this.requiredStatusEffect.some((statusEffect) => {
|
return !this.requiredStatusEffect.some((statusEffect) => {
|
||||||
if (statusEffect === StatusEffect.NONE) {
|
if (statusEffect === StatusEffect.NONE) {
|
||||||
// StatusEffect.NONE also checks for null or undefined status
|
// StatusEffect.NONE also checks for null or undefined status
|
||||||
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status!.effect) || pokemon.status?.effect === statusEffect;
|
return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === statusEffect;
|
||||||
} else {
|
} else {
|
||||||
return pokemon.status?.effect === statusEffect;
|
return pokemon.status?.effect === statusEffect;
|
||||||
}
|
}
|
||||||
@ -645,7 +644,7 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement {
|
|||||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||||
const reqStatus = this.requiredStatusEffect.filter((a) => {
|
const reqStatus = this.requiredStatusEffect.filter((a) => {
|
||||||
if (a === StatusEffect.NONE) {
|
if (a === StatusEffect.NONE) {
|
||||||
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon!.status!.effect) || pokemon!.status!.effect === a;
|
return isNullOrUndefined(pokemon?.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status.effect === a;
|
||||||
}
|
}
|
||||||
return pokemon!.status?.effect === a;
|
return pokemon!.status?.effect === a;
|
||||||
});
|
});
|
||||||
@ -767,12 +766,14 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
|||||||
requiredHeldItemModifiers: string[];
|
requiredHeldItemModifiers: string[];
|
||||||
minNumberOfPokemon: number;
|
minNumberOfPokemon: number;
|
||||||
invertQuery: boolean;
|
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();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||||
|
this.requireTransferable = requireTransferable;
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(scene: BattleScene): boolean {
|
override meetsRequirement(scene: BattleScene): boolean {
|
||||||
@ -787,21 +788,23 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
|||||||
if (!this.invertQuery) {
|
if (!this.invertQuery) {
|
||||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
|
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
|
||||||
return pokemon.getHeldItems().some((it) => {
|
return pokemon.getHeldItems().some((it) => {
|
||||||
return it.constructor.name === heldItem;
|
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
// 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
|
// E.g. functions as a blacklist
|
||||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
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);
|
}).length > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||||
const requiredItems = pokemon?.getHeldItems().filter((it) => {
|
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) {
|
if (requiredItems && requiredItems.length > 0) {
|
||||||
return ["heldItem", requiredItems[0].type.name];
|
return ["heldItem", requiredItems[0].type.name];
|
||||||
@ -814,12 +817,14 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
|||||||
requiredHeldItemTypes: Type[];
|
requiredHeldItemTypes: Type[];
|
||||||
minNumberOfPokemon: number;
|
minNumberOfPokemon: number;
|
||||||
invertQuery: boolean;
|
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();
|
super();
|
||||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||||
this.invertQuery = invertQuery;
|
this.invertQuery = invertQuery;
|
||||||
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
|
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
|
||||||
|
this.requireTransferable = requireTransferable;
|
||||||
}
|
}
|
||||||
|
|
||||||
override meetsRequirement(scene: BattleScene): boolean {
|
override meetsRequirement(scene: BattleScene): boolean {
|
||||||
@ -834,21 +839,29 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
|||||||
if (!this.invertQuery) {
|
if (!this.invertQuery) {
|
||||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemTypes.some((heldItemType) => {
|
return partyPokemon.filter((pokemon) => this.requiredHeldItemTypes.some((heldItemType) => {
|
||||||
return pokemon.getHeldItems().some((it) => {
|
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 {
|
} else {
|
||||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
// 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
|
// E.g. functions as a blacklist
|
||||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
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);
|
}).length > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||||
const requiredItems = pokemon?.getHeldItems().filter((it) => {
|
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) {
|
if (requiredItems && requiredItems.length > 0) {
|
||||||
return ["heldItem", requiredItems[0].type.name];
|
return ["heldItem", requiredItems[0].type.name];
|
||||||
@ -974,8 +987,9 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||||
if (!isNullOrUndefined(pokemon?.getHpRatio())) {
|
const hpRatio = pokemon?.getHpRatio();
|
||||||
return ["healthRatio", Math.floor(pokemon!.getHpRatio() * 100).toString() + "%"];
|
if (!isNullOrUndefined(hpRatio)) {
|
||||||
|
return ["healthRatio", Math.floor(hpRatio * 100).toString() + "%"];
|
||||||
}
|
}
|
||||||
return ["healthRatio", ""];
|
return ["healthRatio", ""];
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
|||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
import { GameModes } from "#app/game-mode";
|
import { GameModes } from "#app/game-mode";
|
||||||
import { EncounterAnim } from "#enums/encounter-anims";
|
import { EncounterAnim } from "#enums/encounter-anims";
|
||||||
|
import { Challenges } from "#enums/challenges";
|
||||||
|
|
||||||
export interface EncounterStartOfBattleEffect {
|
export interface EncounterStartOfBattleEffect {
|
||||||
sourcePokemon?: Pokemon;
|
sourcePokemon?: Pokemon;
|
||||||
@ -40,7 +41,8 @@ export interface IMysteryEncounter {
|
|||||||
spriteConfigs: MysteryEncounterSpriteConfig[];
|
spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||||
encounterTier: MysteryEncounterTier;
|
encounterTier: MysteryEncounterTier;
|
||||||
encounterAnimations?: EncounterAnim[];
|
encounterAnimations?: EncounterAnim[];
|
||||||
disabledGameModes?: GameModes[];
|
disallowedGameModes?: GameModes[];
|
||||||
|
disallowedChallenges?: Challenges[];
|
||||||
hideBattleIntroMessage: boolean;
|
hideBattleIntroMessage: boolean;
|
||||||
autoHideIntroVisuals: boolean;
|
autoHideIntroVisuals: boolean;
|
||||||
enterIntroVisualsFromRight: 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
|
* 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
|
* If true, hides "A Wild X Appeared" etc. messages
|
||||||
* Default true
|
* Default true
|
||||||
@ -161,6 +167,11 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
|||||||
doEncounterRewards?: (scene: BattleScene) => boolean;
|
doEncounterRewards?: (scene: BattleScene) => boolean;
|
||||||
/** Will execute callback during VictoryPhase of a continuousEncounter */
|
/** Will execute callback during VictoryPhase of a continuousEncounter */
|
||||||
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
|
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
|
* Requirements
|
||||||
@ -656,11 +667,21 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
|||||||
/**
|
/**
|
||||||
* Defines any game modes where the Mystery Encounter should *NOT* spawn
|
* Defines any game modes where the Mystery Encounter should *NOT* spawn
|
||||||
* @returns
|
* @returns
|
||||||
* @param disabledGameModes
|
* @param disallowedGameModes
|
||||||
*/
|
*/
|
||||||
withDisabledGameModes(...disabledGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disabledGameModes">> {
|
withDisallowedGameModes(...disallowedGameModes: GameModes[]): this & Required<Pick<IMysteryEncounter, "disallowedGameModes">> {
|
||||||
const gameModes = Array.isArray(disabledGameModes) ? disabledGameModes : [disabledGameModes];
|
const gameModes = Array.isArray(disallowedGameModes) ? disallowedGameModes : [disallowedGameModes];
|
||||||
return Object.assign(this, { disabledGameModes: gameModes });
|
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 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 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
|
* @returns
|
||||||
*/
|
*/
|
||||||
withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
withScenePartySizeRequirement(min: number, max?: number, excludeDisallowedPokemon: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
|
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 { UncommonBreedEncounter } from "#app/data/mystery-encounters/encounters/uncommon-breed-encounter";
|
||||||
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
|
import { GlobalTradeSystemEncounter } from "#app/data/mystery-encounters/encounters/global-trade-system-encounter";
|
||||||
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
|
import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter";
|
||||||
|
import { getBiomeName } from "#app/data/biomes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
|
* Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * <number of missed spawns>) / MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT
|
||||||
@ -362,11 +363,16 @@ export function initMysteryEncounters() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Add ANY biome encounters to biome map
|
// Add ANY biome encounters to biome map
|
||||||
mysteryEncountersByBiome.forEach(biomeEncounters => {
|
let encounterBiomeTableLog = "";
|
||||||
|
mysteryEncountersByBiome.forEach((biomeEncounters, biome) => {
|
||||||
anyBiomeEncounters.forEach(encounter => {
|
anyBiomeEncounters.forEach(encounter => {
|
||||||
if (!biomeEncounters.includes(encounter)) {
|
if (!biomeEncounters.includes(encounter)) {
|
||||||
biomeEncounters.push(encounter);
|
biomeEncounters.push(encounter);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
encounterBiomeTableLog += `${getBiomeName(biome).toUpperCase()}: [${biomeEncounters.map(type => MysteryEncounterType[type].toString().toLowerCase()).sort().join(", ")}]\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.debug("All Mystery Encounters by Biome:\n" + encounterBiomeTableLog);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export function getEncounterText(scene: BattleScene, keyOrString?: string, prima
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString!);
|
let textString: string | null = getTextWithDialogueTokens(scene, keyOrString);
|
||||||
|
|
||||||
// Can only color the text if a Primary Style is defined
|
// Can only color the text if a Primary Style is defined
|
||||||
// primaryStyle is applied to all text that does not have its own specified style
|
// primaryStyle is applied to all text that does not have its own specified style
|
||||||
|
@ -43,7 +43,7 @@ import { Variant } from "#app/data/variant";
|
|||||||
* @param scene
|
* @param scene
|
||||||
*/
|
*/
|
||||||
export function doTrainerExclamation(scene: BattleScene) {
|
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");
|
exclamationSprite.setName("exclamation");
|
||||||
scene.field.add(exclamationSprite);
|
scene.field.add(exclamationSprite);
|
||||||
scene.field.moveTo(exclamationSprite, scene.field.getAll().length - 1);
|
scene.field.moveTo(exclamationSprite, scene.field.getAll().length - 1);
|
||||||
@ -135,7 +135,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
scene.currentBattle.trainer.destroy();
|
scene.currentBattle.trainer.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
trainerConfig = partyConfig?.trainerConfig ? partyConfig?.trainerConfig : trainerConfigs[trainerType!];
|
trainerConfig = partyTrainerConfig ? partyTrainerConfig : trainerConfigs[trainerType!];
|
||||||
|
|
||||||
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
|
const doubleTrainer = trainerConfig.doubleOnly || (trainerConfig.hasDouble && !!partyConfig.doubleBattle);
|
||||||
doubleBattle = doubleTrainer;
|
doubleBattle = doubleTrainer;
|
||||||
@ -166,7 +166,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
// This can be amplified or counteracted by setting levelAdditiveModifier in config
|
// This can be amplified or counteracted by setting levelAdditiveModifier in config
|
||||||
// levelAdditiveModifier value of 0.5 will halve the modifier scaling, 2 will double it, etc.
|
// levelAdditiveModifier value of 0.5 will halve the modifier scaling, 2 will double it, etc.
|
||||||
// Leaving null/undefined will disable level scaling
|
// Leaving null/undefined will disable level scaling
|
||||||
const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier! : 0;
|
const mult: number = !isNullOrUndefined(partyConfig.levelAdditiveModifier) ? partyConfig.levelAdditiveModifier : 0;
|
||||||
const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0);
|
const additive = Math.max(Math.round((scene.currentBattle.waveIndex / 10) * mult), 0);
|
||||||
battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
|
battle.enemyLevels = battle.enemyLevels.map(level => level + additive);
|
||||||
|
|
||||||
@ -226,7 +226,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
|
|
||||||
// Set form
|
// Set form
|
||||||
if (!isNullOrUndefined(config.nickname)) {
|
if (!isNullOrUndefined(config.nickname)) {
|
||||||
enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname!)));
|
enemyPokemon.nickname = btoa(unescape(encodeURIComponent(config.nickname)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate new id, reset status and HP in case using data source
|
// Generate new id, reset status and HP in case using data source
|
||||||
@ -236,29 +236,29 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
|
|
||||||
// Set form
|
// Set form
|
||||||
if (!isNullOrUndefined(config.formIndex)) {
|
if (!isNullOrUndefined(config.formIndex)) {
|
||||||
enemyPokemon.formIndex = config.formIndex!;
|
enemyPokemon.formIndex = config.formIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set shiny
|
// Set shiny
|
||||||
if (!isNullOrUndefined(config.shiny)) {
|
if (!isNullOrUndefined(config.shiny)) {
|
||||||
enemyPokemon.shiny = config.shiny!;
|
enemyPokemon.shiny = config.shiny;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Variant
|
// Set Variant
|
||||||
if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) {
|
if (enemyPokemon.shiny && !isNullOrUndefined(config.variant)) {
|
||||||
enemyPokemon.variant = config.variant!;
|
enemyPokemon.variant = config.variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
|
// Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.)
|
||||||
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) {
|
if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) {
|
||||||
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData!;
|
enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Boss
|
// Set Boss
|
||||||
if (config.isBoss) {
|
if (config.isBoss) {
|
||||||
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true);
|
let segments = !isNullOrUndefined(config.bossSegments) ? config.bossSegments! : scene.getEncounterBossSegments(scene.currentBattle.waveIndex, level, enemySpecies, true);
|
||||||
if (!isNullOrUndefined(config.bossSegmentModifier)) {
|
if (!isNullOrUndefined(config.bossSegmentModifier)) {
|
||||||
segments += config.bossSegmentModifier!;
|
segments += config.bossSegmentModifier;
|
||||||
}
|
}
|
||||||
enemyPokemon.setBoss(true, segments);
|
enemyPokemon.setBoss(true, segments);
|
||||||
}
|
}
|
||||||
@ -294,18 +294,18 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
|
|
||||||
// Set ability
|
// Set ability
|
||||||
if (!isNullOrUndefined(config.abilityIndex)) {
|
if (!isNullOrUndefined(config.abilityIndex)) {
|
||||||
enemyPokemon.abilityIndex = config.abilityIndex!;
|
enemyPokemon.abilityIndex = config.abilityIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set gender
|
// Set gender
|
||||||
if (!isNullOrUndefined(config.gender)) {
|
if (!isNullOrUndefined(config.gender)) {
|
||||||
enemyPokemon.gender = config.gender!;
|
enemyPokemon.gender = config.gender!;
|
||||||
enemyPokemon.summonData.gender = config.gender!;
|
enemyPokemon.summonData.gender = config.gender;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set AI type
|
// Set AI type
|
||||||
if (!isNullOrUndefined(config.aiType)) {
|
if (!isNullOrUndefined(config.aiType)) {
|
||||||
enemyPokemon.aiType = config.aiType!;
|
enemyPokemon.aiType = config.aiType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set moves
|
// Set moves
|
||||||
@ -339,7 +339,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
|||||||
|
|
||||||
loadEnemyAssets.push(enemyPokemon.loadAssets());
|
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));
|
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
|
* @param scene
|
||||||
|
@ -13,7 +13,7 @@ import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
|||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { Type } from "#app/data/type";
|
import { Type } from "#app/data/type";
|
||||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
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 { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||||
import { Gender } from "#app/data/gender";
|
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)
|
* 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
|
* Otherwise, picks a Pokemon completely at random and removes from the party
|
||||||
* @param scene
|
* @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 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 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 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
|
* @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();
|
const party = scene.getParty();
|
||||||
let chosenIndex: number;
|
let chosenIndex: number;
|
||||||
let chosenPokemon: PlayerPokemon;
|
let chosenPokemon: PlayerPokemon | null = null;
|
||||||
const unfaintedMons = party.filter(p => p.isAllowedInBattle());
|
const fullyLegalMons = party.filter(p => (!isAllowed || p.isAllowed()) && (isFainted || !p.isFainted()));
|
||||||
const faintedMons = party.filter(p => !p.isAllowedInBattle());
|
const allowedOnlyMons = party.filter(p => p.isAllowed());
|
||||||
|
|
||||||
if (doNotReturnLastAbleMon && unfaintedMons.length === 1) {
|
if (doNotReturnLastAllowedMon && fullyLegalMons.length === 1) {
|
||||||
chosenIndex = randSeedInt(faintedMons.length);
|
// If there is only 1 legal/unfainted mon left, select from fainted legal mons
|
||||||
chosenPokemon = faintedMons[chosenIndex];
|
const faintedLegalMons = party.filter(p => (!isAllowed || p.isAllowed()) && p.isFainted());
|
||||||
} else if (isAllowedInBattle) {
|
if (faintedLegalMons.length > 0) {
|
||||||
chosenIndex = randSeedInt(unfaintedMons.length);
|
chosenIndex = randSeedInt(faintedLegalMons.length);
|
||||||
chosenPokemon = unfaintedMons[chosenIndex];
|
chosenPokemon = faintedLegalMons[chosenIndex];
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
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);
|
chosenIndex = randSeedInt(party.length);
|
||||||
chosenPokemon = party[chosenIndex];
|
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
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
* @param scene
|
* @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
|
* @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();
|
const party = scene.getParty();
|
||||||
let pokemon: PlayerPokemon | null = null;
|
let pokemon: PlayerPokemon | null = null;
|
||||||
|
|
||||||
for (const p of party) {
|
for (const p of party) {
|
||||||
if (unfainted && p.isFainted()) {
|
if (isAllowed && !p.isAllowed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isFainted && p.isFainted()) {
|
||||||
continue;
|
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
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
* @param scene
|
* @param scene
|
||||||
* @param stat Stat to search for
|
* @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
|
* @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();
|
const party = scene.getParty();
|
||||||
let pokemon: PlayerPokemon | null = null;
|
let pokemon: PlayerPokemon | null = null;
|
||||||
|
|
||||||
for (const p of party) {
|
for (const p of party) {
|
||||||
if (unfainted && p.isFainted()) {
|
if (isAllowed && !p.isAllowed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isFainted && p.isFainted()) {
|
||||||
continue;
|
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
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
* @param scene
|
* @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
|
* @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();
|
const party = scene.getParty();
|
||||||
let pokemon: PlayerPokemon | null = null;
|
let pokemon: PlayerPokemon | null = null;
|
||||||
|
|
||||||
for (const p of party) {
|
for (const p of party) {
|
||||||
if (unfainted && p.isFainted()) {
|
if (isAllowed && !p.isAllowed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isFainted && p.isFainted()) {
|
||||||
continue;
|
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
|
* Ties are broken by whatever mon is closer to the front of the party
|
||||||
* @param scene
|
* @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
|
* @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();
|
const party = scene.getParty();
|
||||||
let pokemon: PlayerPokemon | null = null;
|
let pokemon: PlayerPokemon | null = null;
|
||||||
|
|
||||||
for (const p of party) {
|
for (const p of party) {
|
||||||
if (unfainted && p.isFainted()) {
|
if (isAllowed && !p.isAllowed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isFainted && p.isFainted()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,19 +197,28 @@ export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted:
|
|||||||
* @param starterTiers
|
* @param starterTiers
|
||||||
* @param excludedSpecies
|
* @param excludedSpecies
|
||||||
* @param types
|
* @param types
|
||||||
|
* @param allowSubLegendary
|
||||||
|
* @param allowLegendary
|
||||||
|
* @param allowMythical
|
||||||
* @returns
|
* @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 min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
|
||||||
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
|
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
|
||||||
|
|
||||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
.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]]);
|
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||||
|
|
||||||
if (types && types.length > 0) {
|
if (types && types.length > 0) {
|
||||||
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2!)));
|
filteredSpecies = filteredSpecies.filter(s => types.includes(s[0].type1) || (!isNullOrUndefined(s[0].type2) && types.includes(s[0].type2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
// If no filtered mons exist at specified starter tiers, will expand starter search range until there are
|
||||||
@ -275,7 +311,9 @@ export function applyHealToPokemon(scene: BattleScene, pokemon: PlayerPokemon, h
|
|||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
export async function modifyPlayerPokemonBST(pokemon: PlayerPokemon, value: number) {
|
||||||
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE().generateType(pokemon.scene.getParty(), [value]);
|
const modType = modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE()
|
||||||
|
.generateType(pokemon.scene.getParty(), [value])
|
||||||
|
?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_SHUCKLE_JUICE);
|
||||||
const modifier = modType?.newModifier(pokemon);
|
const modifier = modType?.newModifier(pokemon);
|
||||||
if (modifier) {
|
if (modifier) {
|
||||||
await pokemon.scene.addModifier(modifier, false, false, false, true);
|
await pokemon.scene.addModifier(modifier, false, false, false, true);
|
||||||
@ -744,8 +782,7 @@ export function getGoldenBugNetSpecies(): PokemonSpecies {
|
|||||||
*/
|
*/
|
||||||
export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) {
|
export function getEncounterPokemonLevelForWave(scene: BattleScene, levelAdditiveModifier: number = 0) {
|
||||||
const currentBattle = scene.currentBattle;
|
const currentBattle = scene.currentBattle;
|
||||||
// Default to use the first generated level from enemyLevels, or generate a new one if it DNE
|
const baseLevel = currentBattle.getLevelForWave();
|
||||||
const baseLevel = currentBattle.enemyLevels && currentBattle.enemyLevels?.length > 0 ? currentBattle.enemyLevels[0] : currentBattle.getLevelForWave();
|
|
||||||
|
|
||||||
// Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier
|
// Add a level scaling modifier that is (+1 level per 10 waves) * levelAdditiveModifier
|
||||||
return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0);
|
return baseLevel + Math.max(Math.round((currentBattle.waveIndex / 10) * levelAdditiveModifier), 0);
|
||||||
@ -773,3 +810,23 @@ export async function addPokemonDataToDexAndValidateAchievements(scene: BattleSc
|
|||||||
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||||
return scene.gameData.setPokemonCaught(pokemon, true, false, false);
|
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": {
|
case "flare": {
|
||||||
return {
|
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.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]
|
[TrainerPoolTier.RARE]: [Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -640,14 +640,14 @@ export class TrainerConfig {
|
|||||||
return {
|
return {
|
||||||
[TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ],
|
[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.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": {
|
case "star_4": {
|
||||||
return {
|
return {
|
||||||
[TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ],
|
[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.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": {
|
case "star_5": {
|
||||||
@ -1509,7 +1509,7 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
.setSpeciesPools({
|
.setSpeciesPools({
|
||||||
[TrainerPoolTier.COMMON]: [Species.CARVANHA, Species.WAILMER, Species.ZIGZAGOON, Species.LOTAD, Species.CORPHISH, Species.SPHEAL, Species.REMORAID, Species.QWILFISH, Species.BARBOACH],
|
[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.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]
|
[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)),
|
[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))
|
[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({
|
.setSpeciesPools({
|
||||||
[TrainerPoolTier.COMMON]: [Species.PATRAT, Species.LILLIPUP, Species.PURRLOIN, Species.SCRAFTY, Species.WOOBAT, Species.VANILLITE, Species.SANDILE, Species.TRUBBISH, Species.TYMPOLE],
|
[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.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.KLINK, Species.CUBCHOO, Species.MIENFOO, Species.DURANT, Species.BOUFFALANT],
|
[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]
|
[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)),
|
[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({
|
.setSpeciesPools({
|
||||||
[TrainerPoolTier.COMMON]: [Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR],
|
[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.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]
|
[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)),
|
[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))
|
[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({
|
.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.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.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.ALOLA_SANDSHREW, Species.ALOLA_VULPIX, Species.TURTONATOR, Species.DRAMPA],
|
[TrainerPoolTier.RARE]: [ Species.GALAR_CORSOLA, Species.TURTONATOR, Species.MIMIKYU, Species.MAGNEMITE, Species.DRAMPA],
|
||||||
[TrainerPoolTier.SUPER_RARE]: [Species.JANGMO_O, Species.PORYGON]
|
[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.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))
|
[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({
|
.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.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.DROWZEE, Species.ZUBAT, Species.VENIPEDE, Species.VULLABY],
|
[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.RARE]: [Species.SANDYGAST, Species.PAWNIARD, Species.MIMIKYU, Species.DHELMISE, Species.WISHIWASHI, Species.NYMBLE],
|
||||||
[TrainerPoolTier.SUPER_RARE]: [Species.GRUBBIN, Species.DEWPIDER]
|
[TrainerPoolTier.SUPER_RARE]: [Species.GRUBBIN, Species.DEWPIDER]
|
||||||
}),
|
}),
|
||||||
@ -1916,7 +1916,14 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
p.formIndex = 1; // Mega Kangaskhan
|
p.formIndex = 1; // Mega Kangaskhan
|
||||||
p.generateName();
|
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 => {
|
.setPartyMemberFunc(5, getRandomPartyMemberFunc([Species.MEWTWO], TrainerSlot.TRAINER, true, p => {
|
||||||
p.setBoss(true, 2);
|
p.setBoss(true, 2);
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
@ -2060,7 +2067,7 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
p.setBoss(true, 2);
|
p.setBoss(true, 2);
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.pokeball = PokeballType.ULTRA_BALL;
|
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 => {
|
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.BASCULEGION, Species.JELLICENT ], TrainerSlot.TRAINER, true, p => {
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
@ -2153,9 +2160,23 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
p.pokeball = PokeballType.MASTER_BALL;
|
p.pokeball = PokeballType.MASTER_BALL;
|
||||||
})),
|
})),
|
||||||
[TrainerType.GUZMA]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", []).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma")
|
[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(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(3, getRandomPartyMemberFunc([ Species.GALVANTULA, Species.VIKAVOLT]))
|
||||||
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
@ -2175,25 +2196,32 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
p.abilityIndex = 2; //Anticipation
|
p.abilityIndex = 2; //Anticipation
|
||||||
p.pokeball = PokeballType.ULTRA_BALL;
|
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
|
p.abilityIndex = 2; //Sharpness Hisui Samurott, Adaptability Crawdaunt
|
||||||
}))
|
}))
|
||||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ]))
|
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.BUZZWOLE ], TrainerSlot.TRAINER, true, p => {
|
||||||
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.PINSIR ], TrainerSlot.TRAINER, true, p => {
|
|
||||||
p.generateAndPopulateMoveset();
|
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.formIndex = 1;
|
||||||
|
p.generateAndPopulateMoveset();
|
||||||
p.generateName();
|
p.generateName();
|
||||||
p.pokeball = PokeballType.ULTRA_BALL;
|
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")
|
[TrainerType.ROSE]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", []).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
|
||||||
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ]))
|
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ]))
|
||||||
@ -2209,17 +2237,16 @@ export const trainerConfigs: TrainerConfigs = {
|
|||||||
p.pokeball = PokeballType.ULTRA_BALL;
|
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")
|
[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.setBoss(true, 2);
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.pokeball = PokeballType.ULTRA_BALL;
|
|
||||||
}))
|
}))
|
||||||
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AEGISLASH, Species.GHOLDENGO ]))
|
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.AEGISLASH, Species.GHOLDENGO ]))
|
||||||
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.DRACOVISH, Species.DRACOZOLT ], TrainerSlot.TRAINER, true, p => {
|
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.DRACOVISH, Species.DRACOZOLT ], TrainerSlot.TRAINER, true, p => {
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
p.abilityIndex = 1; //Strong Jaw Dracovish, Hustle Dracozolt
|
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 => {
|
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.GALAR_ARTICUNO, Species.GALAR_ZAPDOS, Species.GALAR_MOLTRES ], TrainerSlot.TRAINER, true, p => {
|
||||||
p.setBoss(true, 2);
|
p.setBoss(true, 2);
|
||||||
p.generateAndPopulateMoveset();
|
p.generateAndPopulateMoveset();
|
||||||
|
@ -79,6 +79,7 @@ export enum BattlerTagType {
|
|||||||
TAR_SHOT = "TAR_SHOT",
|
TAR_SHOT = "TAR_SHOT",
|
||||||
BURNED_UP = "BURNED_UP",
|
BURNED_UP = "BURNED_UP",
|
||||||
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
|
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
|
||||||
|
AUTOTOMIZED = "AUTOTOMIZED",
|
||||||
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
||||||
HEAL_BLOCK = "HEAL_BLOCK",
|
HEAL_BLOCK = "HEAL_BLOCK",
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ export class Arena {
|
|||||||
public tags: ArenaTag[];
|
public tags: ArenaTag[];
|
||||||
public bgm: string;
|
public bgm: string;
|
||||||
public ignoreAbilities: boolean;
|
public ignoreAbilities: boolean;
|
||||||
|
public ignoringEffectSource: BattlerIndex | null;
|
||||||
|
|
||||||
private lastTimeOfDay: TimeOfDay;
|
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.ignoreAbilities = ignoreAbilities;
|
||||||
|
this.ignoringEffectSource = ignoreAbilities ? ignoringEffectSource : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +86,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!isNullOrUndefined(result.species)) {
|
if (!isNullOrUndefined(result.species)) {
|
||||||
const keys = getSpriteKeysFromSpecies(result.species!);
|
const keys = getSpriteKeysFromSpecies(result.species);
|
||||||
result.spriteKey = keys.spriteKey;
|
result.spriteKey = keys.spriteKey;
|
||||||
result.fileRoot = keys.fileRoot;
|
result.fileRoot = keys.fileRoot;
|
||||||
result.isPokemon = true;
|
result.isPokemon = true;
|
||||||
|
@ -17,10 +17,10 @@ import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
|
|||||||
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
|
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
|
||||||
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
|
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
|
||||||
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
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 { WeatherType } from "../data/weather";
|
||||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
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 PokemonData from "../system/pokemon-data";
|
||||||
import { BattlerIndex } from "../battle";
|
import { BattlerIndex } from "../battle";
|
||||||
import { Mode } from "../ui/ui";
|
import { Mode } from "../ui/ui";
|
||||||
@ -109,6 +109,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
public fusionVariant: Variant;
|
public fusionVariant: Variant;
|
||||||
public fusionGender: Gender;
|
public fusionGender: Gender;
|
||||||
public fusionLuck: integer;
|
public fusionLuck: integer;
|
||||||
|
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null;
|
||||||
|
|
||||||
private summonDataPrimer: PokemonSummonData | null;
|
private summonDataPrimer: PokemonSummonData | null;
|
||||||
|
|
||||||
@ -206,6 +207,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.fusionVariant = dataSource.fusionVariant || 0;
|
this.fusionVariant = dataSource.fusionVariant || 0;
|
||||||
this.fusionGender = dataSource.fusionGender;
|
this.fusionGender = dataSource.fusionGender;
|
||||||
this.fusionLuck = dataSource.fusionLuck;
|
this.fusionLuck = dataSource.fusionLuck;
|
||||||
|
this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData;
|
||||||
this.usedTMs = dataSource.usedTMs ?? [];
|
this.usedTMs = dataSource.usedTMs ?? [];
|
||||||
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData);
|
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData);
|
||||||
} else {
|
} else {
|
||||||
@ -343,7 +345,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
isAllowed(): boolean {
|
isAllowed(): boolean {
|
||||||
const challengeAllowed = new Utils.BooleanHolder(true);
|
const challengeAllowed = new Utils.BooleanHolder(true);
|
||||||
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed);
|
applyChallenges(this.scene.gameMode, ChallengeType.POKEMON_IN_BATTLE, this, challengeAllowed);
|
||||||
return !this.isFainted() && challengeAllowed.value;
|
return challengeAllowed.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive(onField?: boolean): boolean {
|
isActive(onField?: boolean): boolean {
|
||||||
@ -1164,11 +1166,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!types.length || !includeTeraType) {
|
if (!types.length || !includeTeraType) {
|
||||||
if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) {
|
if (!ignoreOverride && this.summonData?.types && this.summonData.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) {
|
|
||||||
this.summonData.types.forEach(t => types.push(t));
|
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 {
|
} else {
|
||||||
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
||||||
|
|
||||||
@ -1176,7 +1198,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
||||||
if (fusionSpeciesForm) {
|
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);
|
types.push(fusionSpeciesForm.type2);
|
||||||
} else if (fusionSpeciesForm.type1 !== speciesForm.type1) {
|
} else if (fusionSpeciesForm.type1 !== speciesForm.type1) {
|
||||||
types.push(fusionSpeciesForm.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()) {
|
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) {
|
||||||
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
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) {
|
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) {
|
||||||
return allAbilities[this.mysteryEncounterPokemonData.ability];
|
return allAbilities[this.mysteryEncounterPokemonData.ability];
|
||||||
}
|
}
|
||||||
if (this.isFusion()) {
|
|
||||||
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
|
||||||
}
|
|
||||||
let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex);
|
let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex);
|
||||||
if (abilityId === Abilities.NONE) {
|
if (abilityId === Abilities.NONE) {
|
||||||
abilityId = this.species.ability1;
|
abilityId = this.species.ability1;
|
||||||
@ -1331,7 +1364,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
|
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
|
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
|
||||||
@ -1393,11 +1427,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return false;
|
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 {
|
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
|
// This will trigger the ability overlay so only call this function when necessary
|
||||||
applyAbAttrs(WeightMultiplierAbAttr, this, null, false, weight);
|
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
|
* @returns list of egg moves
|
||||||
*/
|
*/
|
||||||
getEggMoves() : Moves[] {
|
getEggMoves() : Moves[] | undefined {
|
||||||
return speciesEggMoves[this.species.speciesId];
|
return speciesEggMoves[this.getSpeciesForm().getRootSpeciesId(true)];
|
||||||
}
|
}
|
||||||
|
|
||||||
setMove(moveIndex: integer, moveId: Moves): void {
|
setMove(moveIndex: integer, moveId: Moves): void {
|
||||||
@ -1927,6 +1973,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
this.fusionVariant = 0;
|
this.fusionVariant = 0;
|
||||||
this.fusionGender = 0;
|
this.fusionGender = 0;
|
||||||
this.fusionLuck = 0;
|
this.fusionLuck = 0;
|
||||||
|
this.fusionMysteryEncounterPokemonData = null;
|
||||||
|
|
||||||
this.generateName();
|
this.generateName();
|
||||||
this.calculateStats();
|
this.calculateStats();
|
||||||
@ -3331,7 +3378,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (asPhase) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3365,6 +3412,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
if (effect !== StatusEffect.FAINT) {
|
if (effect !== StatusEffect.FAINT) {
|
||||||
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
||||||
|
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, this, effect, sourcePokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -4231,6 +4279,7 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
this.fusionVariant = pokemon.variant;
|
this.fusionVariant = pokemon.variant;
|
||||||
this.fusionGender = pokemon.gender;
|
this.fusionGender = pokemon.gender;
|
||||||
this.fusionLuck = pokemon.luck;
|
this.fusionLuck = pokemon.luck;
|
||||||
|
this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData;
|
||||||
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
|
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
|
||||||
this.pauseEvolutions = true;
|
this.pauseEvolutions = true;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { GachaType } from "./enums/gacha-types";
|
import { GachaType } from "./enums/gacha-types";
|
||||||
import { trainerConfigs } from "./data/trainer-config";
|
|
||||||
import { getBiomeHasProps } from "./field/arena";
|
import { getBiomeHasProps } from "./field/arena";
|
||||||
import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin";
|
import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin";
|
||||||
import { SceneBase } from "./scene-base";
|
import { SceneBase } from "./scene-base";
|
||||||
@ -21,7 +20,6 @@ import i18next from "i18next";
|
|||||||
import { initStatsKeys } from "./ui/game-stats-ui-handler";
|
import { initStatsKeys } from "./ui/game-stats-ui-handler";
|
||||||
import { initVouchers } from "./system/voucher";
|
import { initVouchers } from "./system/voucher";
|
||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
|
||||||
import {initMysteryEncounters} from "#app/data/mystery-encounters/mystery-encounters";
|
import {initMysteryEncounters} from "#app/data/mystery-encounters/mystery-encounters";
|
||||||
|
|
||||||
export class LoadingScene extends SceneBase {
|
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", "trainer");
|
||||||
this.loadAtlas("trainer_f_back_pb", "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
|
// Load character sprites
|
||||||
this.loadAtlas("c_rival_m", "character", "rival_m");
|
this.loadAtlas("c_rival_m", "character", "rival_m");
|
||||||
this.loadAtlas("c_rival_f", "character", "rival_f");
|
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 modifierSelectUiHandler from "./modifier-select-ui-handler.json";
|
||||||
import moveTriggers from "./move-trigger.json";
|
import moveTriggers from "./move-trigger.json";
|
||||||
import runHistory from "./run-history.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 = {
|
export const caEsConfig = {
|
||||||
ability,
|
ability,
|
||||||
abilityTriggers,
|
abilityTriggers,
|
||||||
@ -110,4 +152,40 @@ export const caEsConfig = {
|
|||||||
modifierSelectUiHandler,
|
modifierSelectUiHandler,
|
||||||
moveTriggers,
|
moveTriggers,
|
||||||
runHistory,
|
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 @@
|
|||||||
|
{}
|