mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-29 10:49:29 +02:00
ME balance changes and bug fixes
This commit is contained in:
parent
64aee2ffd7
commit
3e751a477c
@ -1,10 +1,10 @@
|
||||
import Phaser from "phaser";
|
||||
import UI from "./ui/ui";
|
||||
import Pokemon, { PlayerPokemon, EnemyPokemon } from "./field/pokemon";
|
||||
import PokemonSpecies, { PokemonSpeciesFilter, allSpecies, getPokemonSpecies } from "./data/pokemon-species";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "./data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined } from "#app/utils";
|
||||
import * as Utils from "./utils";
|
||||
import { Modifier, ModifierBar, ConsumablePokemonModifier, ConsumableModifier, PokemonHpRestoreModifier, TurnHeldItemTransferModifier, HealingBoosterModifier, PersistentModifier, PokemonHeldItemModifier, ModifierPredicate, DoubleBattleChanceBoosterModifier, FusePokemonModifier, PokemonFormChangeItemModifier, TerastallizeModifier, overrideModifiers, overrideHeldItems, PokemonIncrementingStatModifier, ExpShareModifier, ExpBalanceModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "./modifier/modifier";
|
||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
import { PokeballType } from "./data/pokeball";
|
||||
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims";
|
||||
import { Phase } from "./phase";
|
||||
@ -13,20 +13,9 @@ import { Arena, ArenaBase } from "./field/arena";
|
||||
import { GameData } from "./system/game-data";
|
||||
import { addTextObject, getTextColor, TextStyle } from "./ui/text";
|
||||
import { allMoves } from "./data/move";
|
||||
import {
|
||||
ModifierPoolType,
|
||||
getDefaultModifierTypeForTier,
|
||||
getEnemyModifierTypesForWave,
|
||||
getLuckString,
|
||||
getLuckTextTint,
|
||||
getModifierPoolForType,
|
||||
getModifierType,
|
||||
getPartyLuckValue,
|
||||
modifierTypes, PokemonHeldItemModifierType
|
||||
} from "./modifier/modifier-type";
|
||||
import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, ModifierPoolType, modifierTypes, PokemonHeldItemModifierType } from "./modifier/modifier-type";
|
||||
import AbilityBar from "./ui/ability-bar";
|
||||
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, ChangeMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
||||
import { allAbilities } from "./data/ability";
|
||||
import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, BlockItemTheftAbAttr, ChangeMovePriorityAbAttr, DoubleBattleChanceAbAttr, PostBattleInitAbAttr } from "./data/ability";
|
||||
import Battle, { BattleType, FixedBattleConfig } from "./battle";
|
||||
import { GameMode, GameModes, getGameMode } from "./game-mode";
|
||||
import FieldSpritePipeline from "./pipelines/field-sprite";
|
||||
@ -46,7 +35,7 @@ import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
|
||||
import { addUiThemeOverrides } from "./ui/ui-theme";
|
||||
import PokemonData from "./system/pokemon-data";
|
||||
import { Nature } from "./data/nature";
|
||||
import { SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges, FormChangeItem, SpeciesFormChange } from "./data/pokemon-forms";
|
||||
import { FormChangeItem, pokemonFormChanges, SpeciesFormChange, SpeciesFormChangeManualTrigger, SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger } from "./data/pokemon-forms";
|
||||
import { FormChangePhase } from "./phases/form-change-phase";
|
||||
import { getTypeRgb } from "./data/type";
|
||||
import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler";
|
||||
@ -1081,6 +1070,11 @@ export default class BattleScene extends SceneBase {
|
||||
p.destroy();
|
||||
}
|
||||
|
||||
// If this is a ME, clear any residual visual sprites before reloading
|
||||
if (this.currentBattle?.mysteryEncounter?.introVisuals) {
|
||||
this.field.remove(this.currentBattle.mysteryEncounter?.introVisuals, true);
|
||||
}
|
||||
|
||||
//@ts-ignore - allowing `null` for currentBattle causes a lot of trouble
|
||||
this.currentBattle = null; // TODO: resolve ts-ignore
|
||||
|
||||
@ -1111,6 +1105,8 @@ export default class BattleScene extends SceneBase {
|
||||
this.trainer.setPosition(406, 186);
|
||||
this.trainer.setVisible(true);
|
||||
|
||||
this.mysteryEncounterSaveData = new MysteryEncounterSaveData();
|
||||
|
||||
this.updateGameInfo();
|
||||
|
||||
if (reloadI18n) {
|
||||
|
@ -743,16 +743,21 @@ export abstract class BattleAnim {
|
||||
public target: Pokemon | null;
|
||||
public sprites: Phaser.GameObjects.Sprite[];
|
||||
public bgSprite: Phaser.GameObjects.TileSprite | Phaser.GameObjects.Rectangle;
|
||||
public playOnEmptyField: boolean;
|
||||
/**
|
||||
* Will attempt to play as much of an animation as possible, even if not all targets are on the field.
|
||||
* Will also play the animation, even if the user has selected "Move Animations" OFF in Settings.
|
||||
* Exclusively used by MEs atm, for visual animations at the start of an encounter.
|
||||
*/
|
||||
public playRegardlessOfIssues: boolean;
|
||||
|
||||
private srcLine: number[];
|
||||
private dstLine: number[];
|
||||
|
||||
constructor(user?: Pokemon, target?: Pokemon, playOnEmptyField: boolean = false) {
|
||||
constructor(user?: Pokemon, target?: Pokemon, playRegardlessOfIssues: boolean = false) {
|
||||
this.user = user ?? null;
|
||||
this.target = target ?? null;
|
||||
this.sprites = [];
|
||||
this.playOnEmptyField = playOnEmptyField;
|
||||
this.playRegardlessOfIssues = playRegardlessOfIssues;
|
||||
}
|
||||
|
||||
abstract getAnim(): AnimConfig | null;
|
||||
@ -829,7 +834,7 @@ export abstract class BattleAnim {
|
||||
const user = !isOppAnim ? this.user! : this.target!; // TODO: are those bangs correct?
|
||||
const target = !isOppAnim ? this.target! : this.user!;
|
||||
|
||||
if (!target?.isOnField() && !this.playOnEmptyField) {
|
||||
if (!target?.isOnField() && !this.playRegardlessOfIssues) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
@ -896,7 +901,7 @@ export abstract class BattleAnim {
|
||||
}
|
||||
};
|
||||
|
||||
if (!scene.moveAnimations) {
|
||||
if (!scene.moveAnimations && !this.playRegardlessOfIssues) {
|
||||
return cleanUpAndComplete();
|
||||
}
|
||||
|
||||
@ -932,7 +937,7 @@ export abstract class BattleAnim {
|
||||
const isUser = frame.target === AnimFrameTarget.USER;
|
||||
if (isUser && target === user) {
|
||||
continue;
|
||||
} else if (this.playOnEmptyField && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
|
||||
} else if (this.playRegardlessOfIssues && frame.target === AnimFrameTarget.TARGET && !target.isOnField()) {
|
||||
continue;
|
||||
}
|
||||
const sprites = spriteCache[isUser ? AnimFrameTarget.USER : AnimFrameTarget.TARGET];
|
||||
@ -1145,7 +1150,7 @@ export abstract class BattleAnim {
|
||||
}
|
||||
};
|
||||
|
||||
if (!scene.moveAnimations) {
|
||||
if (!scene.moveAnimations && !this.playRegardlessOfIssues) {
|
||||
return cleanUpAndComplete();
|
||||
}
|
||||
|
||||
|
@ -376,9 +376,10 @@ export const BugTypeSuperfanEncounter: MysteryEncounter =
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter(item => {
|
||||
return item instanceof BypassSpeedChanceModifier ||
|
||||
return (item instanceof BypassSpeedChanceModifier ||
|
||||
item instanceof ContactHeldItemTransferChanceModifier ||
|
||||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG);
|
||||
(item instanceof AttackTypeBoosterModifier && (item.type as AttackTypeBoosterModifierType).moveType === Type.BUG)) &&
|
||||
item.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
|
@ -349,10 +349,18 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
||||
}
|
||||
}
|
||||
newTypes.push(secondType);
|
||||
|
||||
// Apply the type changes (to both base and fusion, if pokemon is fused)
|
||||
if (!pokemon.mysteryEncounterPokemonData) {
|
||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.mysteryEncounterPokemonData.types = newTypes;
|
||||
if (pokemon.isFusion()) {
|
||||
if (!pokemon.fusionMysteryEncounterPokemonData) {
|
||||
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.fusionMysteryEncounterPokemonData.types = newTypes;
|
||||
}
|
||||
}
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
@ -415,10 +423,17 @@ function onYesAbilitySwap(scene: BattleScene, resolve) {
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Do ability swap
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
if (!pokemon.mysteryEncounterPokemonData) {
|
||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
if (pokemon.isFusion()) {
|
||||
if (!pokemon.fusionMysteryEncounterPokemonData) {
|
||||
pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability;
|
||||
} else {
|
||||
if (!pokemon.mysteryEncounterPokemonData) {
|
||||
pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData();
|
||||
}
|
||||
pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability;
|
||||
}
|
||||
pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability;
|
||||
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
|
||||
scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
|
||||
};
|
||||
|
@ -27,6 +27,7 @@ import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { EncounterAnim } from "#enums/encounter-anims";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:dancingLessons";
|
||||
@ -271,6 +272,9 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||
// Only Pokemon that have a Dancing move can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
if (!pokemon.isAllowed()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
|
@ -159,7 +159,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
return OPTION_2_ALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
@ -179,9 +179,8 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
@ -254,7 +253,7 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
const onPokemonSelected = (pokemon: PlayerPokemon) => {
|
||||
// Get Pokemon held items and filter for valid ones
|
||||
const validItems = pokemon.getHeldItems().filter((it) => {
|
||||
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem);
|
||||
return !OPTION_3_DISALLOWED_MODIFIERS.some(heldItem => it.constructor.name === heldItem) && it.isTransferable;
|
||||
});
|
||||
|
||||
return validItems.map((modifier: PokemonHeldItemModifier) => {
|
||||
@ -274,9 +273,8 @@ export const DelibirdyEncounter: MysteryEncounter =
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
// If pokemon has valid item, it can be selected
|
||||
const meetsReqs = encounter.options[2].pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
|
@ -189,7 +189,7 @@ export const FieryFalloutEncounter: MysteryEncounter =
|
||||
}
|
||||
|
||||
// Burn random member
|
||||
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.BURN);
|
||||
const burnable = nonFireTypes.filter(p => isNullOrUndefined(p.status) || isNullOrUndefined(p.status!.effect) || p.status?.effect === StatusEffect.NONE);
|
||||
if (burnable?.length > 0) {
|
||||
const roll = randSeedInt(burnable.length);
|
||||
const chosenPokemon = burnable[roll];
|
||||
|
@ -68,6 +68,7 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.option.1.stat_boost`);
|
||||
// Randomly boost 1 stat 2 stages
|
||||
// Cannot boost Spd, Acc, or Evasion
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [randSeedInt(4, 1)], 2));
|
||||
}
|
||||
}],
|
||||
|
@ -7,7 +7,7 @@ import { TrainerSlot } from "#app/data/trainer-config";
|
||||
import Pokemon, { FieldPosition, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getPokemonSpecies } from "#app/data/pokemon-species";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Species } from "#enums/species";
|
||||
@ -22,6 +22,7 @@ import { PostSummonPhase } from "#app/phases/post-summon-phase";
|
||||
import { modifierTypes } from "#app/modifier/modifier-type";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:funAndGames";
|
||||
@ -110,12 +111,7 @@ export const FunAndGamesEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -317,7 +317,6 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
});
|
||||
};
|
||||
|
||||
// Only Pokemon that can gain benefits are above 1/3rd HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon has items to trade
|
||||
const meetsReqs = pokemon.getHeldItems().filter((it) => {
|
||||
|
@ -163,11 +163,12 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
leaveEncounterWithoutBattle(scene);
|
||||
} else {
|
||||
// Your highest level unfainted Pokemon gets OHKO. Start battle against a Gimmighoul (35%)
|
||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(
|
||||
scene,
|
||||
true
|
||||
);
|
||||
const highestLevelPokemon = getHighestLevelPlayerPokemon(scene, true);
|
||||
koPlayerPokemon(scene, highestLevelPokemon);
|
||||
|
||||
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
||||
|
||||
// Handle game over edge case
|
||||
const allowedPokemon = scene.getParty().filter(p => p.isAllowedInBattle());
|
||||
if (allowedPokemon.length === 0) {
|
||||
@ -176,8 +177,6 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
scene.unshiftPhase(new GameOverPhase(scene));
|
||||
} else {
|
||||
// Show which Pokemon was KOed, then start battle against Gimmighoul
|
||||
encounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender());
|
||||
await showEncounterText(scene, `${namespace}.option.1.bad`);
|
||||
transitionMysteryEncounterIntroVisuals(scene, true, true, 500);
|
||||
setEncounterRewards(scene, { fillRemaining: true });
|
||||
await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]);
|
||||
|
@ -8,10 +8,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
|
||||
import { getEncounterText, showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import i18next from "i18next";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:partTimer";
|
||||
@ -117,11 +118,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
@ -198,11 +195,7 @@ export const PartTimerEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon non-KOd pokemon can be selected
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -254,7 +254,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
||||
let enemySpecies;
|
||||
let pokemon;
|
||||
scene.executeWithSeedOffset(() => {
|
||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
enemySpecies = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||
const level = scene.currentBattle.getLevelForWave();
|
||||
enemySpecies = getPokemonSpecies(enemySpecies.getWildSpeciesForLevel(level, true, false, scene.gameMode));
|
||||
pokemon = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, false);
|
||||
@ -282,7 +282,7 @@ async function summonSafariPokemon(scene: BattleScene) {
|
||||
pokemon.calculateStats();
|
||||
|
||||
scene.currentBattle.enemyParty.unshift(pokemon);
|
||||
}, scene.currentBattle.waveIndex * 1000 + encounter.misc.safariPokemonRemaining);
|
||||
}, scene.currentBattle.waveIndex * 1000 * encounter.misc.safariPokemonRemaining);
|
||||
|
||||
scene.gameData.setPokemonSeen(pokemon, true);
|
||||
await pokemon.loadAssets();
|
||||
|
@ -9,12 +9,13 @@ import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-enc
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
|
||||
import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon, isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { getNatureName } from "#app/data/nature";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import i18next from "i18next";
|
||||
|
||||
/** the i18n namespace for this encounter */
|
||||
const namespace = "mysteryEncounter:shadyVitaminDealer";
|
||||
@ -32,7 +33,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withSceneRequirement(new MoneyRequirement(0, VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER)) // Must have the money for at least the cheap deal
|
||||
.withPrimaryPokemonHealthRatioRequirement([0.5, 1]) // At least 1 Pokemon must have above half HP
|
||||
.withPrimaryPokemonHealthRatioRequirement([0.51, 1]) // At least 1 Pokemon must have above half HP
|
||||
.withIntroSpriteConfigs([
|
||||
{
|
||||
spriteKey: Species.KROOKODILE.toString(),
|
||||
@ -98,8 +99,10 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
// Only Pokemon that can gain benefits are above half HP with no status
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon meets primary pokemon reqs, it can be selected
|
||||
const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon);
|
||||
if (!meetsReqs) {
|
||||
if (!pokemon.isAllowed()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
if (!encounter.pokemonMeetsPrimaryRequirements(scene, pokemon)) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
@ -175,13 +178,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that can gain benefits are unfainted
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
// If pokemon is unfainted it can be selected
|
||||
const meetsReqs = !pokemon.isFainted(true);
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -171,6 +171,12 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
|
||||
const bossSpecies = scene.arena.randomSpecies(scene.currentBattle.waveIndex, level, 0, getPartyLuckValue(scene.getParty()), true);
|
||||
const bossPokemon = new EnemyPokemon(scene, bossSpecies, level, TrainerSlot.NONE, true);
|
||||
encounter.setDialogueToken("enemyPokemon", getPokemonNameWithAffix(bossPokemon));
|
||||
|
||||
// Defense/Spd buffs below wave 50, Atk/Def/Spd buffs otherwise
|
||||
const statChangesForBattle: (Stat.ATK | Stat.DEF | Stat.SPATK | Stat.SPDEF | Stat.SPD | Stat.ACC | Stat.EVA)[] = scene.currentBattle.waveIndex < 50 ?
|
||||
[Stat.DEF, Stat.SPDEF, Stat.SPD] :
|
||||
[Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD];
|
||||
|
||||
const config: EnemyPartyConfig = {
|
||||
pokemonConfigs: [{
|
||||
level: level,
|
||||
@ -180,7 +186,7 @@ async function doBiomeTransitionDialogueAndBattleInit(scene: BattleScene) {
|
||||
tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON],
|
||||
mysteryEncounterBattleEffects: (pokemon: Pokemon) => {
|
||||
queueEncounterMessage(pokemon.scene, `${namespace}.boss_enraged`);
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD], 1));
|
||||
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, statChangesForBattle, 1));
|
||||
}
|
||||
}],
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { EnemyPartyConfig, handleMysteryEncounterBattleFailed, initBattleWithEnemyConfig, setEncounterRewards, } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
|
||||
import { trainerConfigs } from "#app/data/trainer-config";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { randSeedShuffle } from "#app/utils";
|
||||
@ -14,8 +13,6 @@ import { Species } from "#enums/species";
|
||||
import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Type } from "#app/data/type";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { IEggOptions } from "#app/data/egg";
|
||||
@ -30,9 +27,9 @@ const namespace = "mysteryEncounter:expertPokemonBreeder";
|
||||
|
||||
const trainerNameKey = "trainerNames:expert_pokemon_breeder";
|
||||
|
||||
const FIRST_STAGE_EVOLUTION_WAVE = 30;
|
||||
const SECOND_STAGE_EVOLUTION_WAVE = 45;
|
||||
const FINAL_STAGE_EVOLUTION_WAVE = 60;
|
||||
const FIRST_STAGE_EVOLUTION_WAVE = 45;
|
||||
const SECOND_STAGE_EVOLUTION_WAVE = 60;
|
||||
const FINAL_STAGE_EVOLUTION_WAVE = 75;
|
||||
|
||||
const FRIENDSHIP_ADDED = 20;
|
||||
|
||||
@ -193,6 +190,12 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
pokemon3RareEggs
|
||||
};
|
||||
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
text: `${namespace}.outro`,
|
||||
},
|
||||
];
|
||||
|
||||
return true;
|
||||
})
|
||||
.withTitle(`${namespace}.title`)
|
||||
@ -241,14 +244,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
});
|
||||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Give achievement if in Space biome
|
||||
checkAchievement(scene);
|
||||
// Give 20 friendship to the chosen pokemon
|
||||
scene.currentBattle.mysteryEncounter!.misc.pokemon1.addFriendship(FRIENDSHIP_ADDED);
|
||||
await restorePartyAndHeldItems(scene);
|
||||
await doPostEncounterCleanup(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
@ -295,14 +295,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
});
|
||||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Give achievement if in Space biome
|
||||
checkAchievement(scene);
|
||||
// Give 20 friendship to the chosen pokemon
|
||||
scene.currentBattle.mysteryEncounter!.misc.pokemon2.addFriendship(FRIENDSHIP_ADDED);
|
||||
await restorePartyAndHeldItems(scene);
|
||||
await doPostEncounterCleanup(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
@ -349,14 +346,11 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter =
|
||||
});
|
||||
}
|
||||
|
||||
encounter.onGameOver = onGameOver;
|
||||
initBattleWithEnemyConfig(scene, config);
|
||||
})
|
||||
.withPostOptionPhase(async (scene: BattleScene) => {
|
||||
// Give achievement if in Space biome
|
||||
checkAchievement(scene);
|
||||
// Give 20 friendship to the chosen pokemon
|
||||
scene.currentBattle.mysteryEncounter!.misc.pokemon3.addFriendship(FRIENDSHIP_ADDED);
|
||||
await restorePartyAndHeldItems(scene);
|
||||
await doPostEncounterCleanup(scene);
|
||||
})
|
||||
.build()
|
||||
)
|
||||
@ -387,19 +381,6 @@ function getPartyConfig(scene: BattleScene): EnemyPartyConfig {
|
||||
nature: Nature.ADAMANT,
|
||||
moveSet: [Moves.METEOR_MASH, Moves.FIRE_PUNCH, Moves.ICE_PUNCH, Moves.THUNDER_PUNCH],
|
||||
ivs: [31, 31, 31, 31, 31, 31],
|
||||
modifierConfigs: [
|
||||
{
|
||||
modifier: generateModifierType(scene, modifierTypes.TERA_SHARD, [Type.STEEL]) as PokemonHeldItemModifierType,
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.ATK]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1 + Math.floor(waveIndex / 20), // +1 Protein every 20 waves
|
||||
},
|
||||
{
|
||||
modifier: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType,
|
||||
stackCount: 1 + Math.floor(waveIndex / 40), // +1 Carbos every 40 waves
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
@ -547,3 +528,39 @@ async function restorePartyAndHeldItems(scene: BattleScene) {
|
||||
});
|
||||
await scene.updateModifiers(true);
|
||||
}
|
||||
|
||||
function onGameOver(scene: BattleScene) {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
encounter.dialogue.outro = [
|
||||
{
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -58,12 +58,12 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
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;
|
||||
|
||||
// Reroll any species that don't have HAs
|
||||
while ((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) && tries < 5) {
|
||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5]));
|
||||
species = getPokemonSpecies(getRandomSpeciesByStarterTier([0, 5], undefined, undefined, false, false, false));
|
||||
}
|
||||
|
||||
let pokemon: PlayerPokemon;
|
||||
|
@ -13,13 +13,14 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import HeldModifierConfig from "#app/interfaces/held-modifier-config";
|
||||
import i18next from "i18next";
|
||||
import { getStatKey } from "#enums/stat";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
|
||||
/** The i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounter:trainingSession";
|
||||
@ -77,12 +78,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
@ -211,12 +207,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
@ -307,12 +298,7 @@ export const TrainingSessionEncounter: MysteryEncounter =
|
||||
|
||||
// Only Pokemon that are not KOed/legal can be trained
|
||||
const selectableFilter = (pokemon: Pokemon) => {
|
||||
const meetsReqs = pokemon.isAllowedInBattle();
|
||||
if (!meetsReqs) {
|
||||
return getEncounterText(scene, `${namespace}.invalid_selection`) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
return isPokemonValidForEncounterOptionSelection(pokemon, scene, `${namespace}.invalid_selection`);
|
||||
};
|
||||
|
||||
return selectPokemonForOption(scene, onPokemonSelected, undefined, selectableFilter);
|
||||
|
@ -82,6 +82,9 @@ const SUPER_LEGENDARY_BST_THRESHOLD = 600;
|
||||
const NON_LEGENDARY_BST_THRESHOLD = 570;
|
||||
const GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD = 450;
|
||||
|
||||
/** 0-100 */
|
||||
const PERCENT_LEVEL_LOSS_ON_REFUSE = 12.5;
|
||||
|
||||
/**
|
||||
* Value ranges of the resulting species BST transformations after adding values to original species
|
||||
* 2 Pokemon in the party use this range
|
||||
@ -207,7 +210,7 @@ export const WeirdDreamEncounter: MysteryEncounter =
|
||||
async (scene: BattleScene) => {
|
||||
// Reduce party levels by 20%
|
||||
for (const pokemon of scene.getParty()) {
|
||||
pokemon.level = Math.max(Math.ceil(0.8 * pokemon.level), 1);
|
||||
pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1);
|
||||
pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate);
|
||||
pokemon.levelExp = 0;
|
||||
|
||||
@ -339,6 +342,9 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
}
|
||||
}
|
||||
|
||||
// If the previous pokemon had pokerus, transfer to new pokemon
|
||||
newPokemon.pokerus = previousPokemon.pokerus;
|
||||
|
||||
// If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock)
|
||||
newPokemon.ivs = newPokemon.ivs.map((iv, index) => {
|
||||
return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv;
|
||||
@ -349,22 +355,46 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon
|
||||
scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1);
|
||||
}
|
||||
|
||||
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move of the new species
|
||||
// Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species
|
||||
newPokemon.generateAndPopulateMoveset();
|
||||
|
||||
// Try to find a favored STAB move
|
||||
let favoredMove;
|
||||
for (const move of newPokemon.moveset) {
|
||||
// Needs to match first type, second type will be replaced
|
||||
if (move?.getMove().type === newPokemon.getTypes()[0]) {
|
||||
favoredMove = move;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If was unable to find a move, uses first move in moveset (typically a high power STAB move)
|
||||
favoredMove = favoredMove ?? newPokemon.moveset[0];
|
||||
|
||||
newPokemon.moveset = previousPokemon.moveset;
|
||||
let eggMoveIndex: null | number = null;
|
||||
if (speciesEggMoves.hasOwnProperty(speciesRootForm)) {
|
||||
const eggMoves = speciesEggMoves[speciesRootForm];
|
||||
const eggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove = eggMoves[eggMoveIndex];
|
||||
const randomEggMoveIndex = randSeedInt(4);
|
||||
const randomEggMove = eggMoves[randomEggMoveIndex];
|
||||
if (newPokemon.moveset.length < 4) {
|
||||
newPokemon.moveset.push(new PokemonMove(randomEggMove));
|
||||
} else {
|
||||
newPokemon.moveset[randSeedInt(4)] = new PokemonMove(randomEggMove);
|
||||
eggMoveIndex = randSeedInt(4);
|
||||
newPokemon.moveset[eggMoveIndex] = new PokemonMove(randomEggMove);
|
||||
}
|
||||
// For pokemon that the player owns (including ones just caught), unlock the egg move
|
||||
if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) {
|
||||
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), eggMoveIndex, true);
|
||||
await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true);
|
||||
}
|
||||
}
|
||||
if (favoredMove) {
|
||||
let favoredMoveIndex = randSeedInt(4);
|
||||
while (favoredMoveIndex === eggMoveIndex) {
|
||||
favoredMoveIndex = randSeedInt(4);
|
||||
}
|
||||
|
||||
newPokemon.moveset[favoredMoveIndex] = favoredMove;
|
||||
}
|
||||
|
||||
// Randomize the second type of the pokemon
|
||||
// If the pokemon does not normally have a second type, it will gain 1
|
||||
|
@ -259,23 +259,23 @@ export class WeatherRequirement extends EncounterSceneRequirement {
|
||||
|
||||
export class PartySizeRequirement extends EncounterSceneRequirement {
|
||||
partySizeRange: [number, number];
|
||||
excludeFainted: boolean;
|
||||
excludeDisallowedPokemon: boolean;
|
||||
|
||||
/**
|
||||
* Used for specifying a party size requirement
|
||||
* If min and max are equivalent, will check for exact size
|
||||
* @param partySizeRange
|
||||
* @param excludeFainted
|
||||
* @param excludeDisallowedPokemon
|
||||
*/
|
||||
constructor(partySizeRange: [number, number], excludeFainted: boolean) {
|
||||
constructor(partySizeRange: [number, number], excludeDisallowedPokemon: boolean) {
|
||||
super();
|
||||
this.partySizeRange = partySizeRange;
|
||||
this.excludeFainted = excludeFainted;
|
||||
this.excludeDisallowedPokemon = excludeDisallowedPokemon;
|
||||
}
|
||||
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
if (!isNullOrUndefined(this.partySizeRange) && this.partySizeRange?.[0] <= this.partySizeRange?.[1]) {
|
||||
const partySize = this.excludeFainted ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
||||
const partySize = this.excludeDisallowedPokemon ? scene.getParty().filter(p => p.isAllowedInBattle()).length : scene.getParty().length;
|
||||
if (partySize >= 0 && (this.partySizeRange?.[0] >= 0 && this.partySizeRange?.[0] > partySize) || (this.partySizeRange?.[1] >= 0 && this.partySizeRange?.[1] < partySize)) {
|
||||
return false;
|
||||
}
|
||||
@ -767,12 +767,14 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
requiredHeldItemModifiers: string[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
constructor(heldItem: string | string[], minNumberOfPokemon: number = 1, invertQuery: boolean = false, requireTransferable: boolean = true) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemModifiers = Array.isArray(heldItem) ? heldItem : [heldItem];
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
@ -787,21 +789,23 @@ export class HeldItemRequirement extends EncounterPokemonRequirement {
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemModifiers.some((heldItem) => {
|
||||
return pokemon.getHeldItems().some((it) => {
|
||||
return it.constructor.name === heldItem;
|
||||
return it.constructor.name === heldItem && (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
||||
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
return !this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem)
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
}).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter((it) => {
|
||||
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem);
|
||||
return this.requiredHeldItemModifiers.some(heldItem => it.constructor.name === heldItem)
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
@ -814,12 +818,14 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
requiredHeldItemTypes: Type[];
|
||||
minNumberOfPokemon: number;
|
||||
invertQuery: boolean;
|
||||
requireTransferable: boolean;
|
||||
|
||||
constructor(heldItemTypes: Type | Type[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) {
|
||||
constructor(heldItemTypes: Type | Type[], minNumberOfPokemon: number = 1, invertQuery: boolean = false, requireTransferable: boolean = true) {
|
||||
super();
|
||||
this.minNumberOfPokemon = minNumberOfPokemon;
|
||||
this.invertQuery = invertQuery;
|
||||
this.requiredHeldItemTypes = Array.isArray(heldItemTypes) ? heldItemTypes : [heldItemTypes];
|
||||
this.requireTransferable = requireTransferable;
|
||||
}
|
||||
|
||||
override meetsRequirement(scene: BattleScene): boolean {
|
||||
@ -834,21 +840,29 @@ export class AttackTypeBoosterHeldItemTypeRequirement extends EncounterPokemonRe
|
||||
if (!this.invertQuery) {
|
||||
return partyPokemon.filter((pokemon) => this.requiredHeldItemTypes.some((heldItemType) => {
|
||||
return pokemon.getHeldItems().some((it) => {
|
||||
return it instanceof AttackTypeBoosterModifier && (it.type as AttackTypeBoosterModifierType).moveType === heldItemType;
|
||||
return it instanceof AttackTypeBoosterModifier
|
||||
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
// for an inverted query, we only want to get the pokemon that have any held items that are NOT in requiredHeldItemModifiers
|
||||
// E.g. functions as a blacklist
|
||||
return partyPokemon.filter((pokemon) => pokemon.getHeldItems().filter((it) => {
|
||||
return !this.requiredHeldItemTypes.some(heldItemType => it instanceof AttackTypeBoosterModifier && (it.type as AttackTypeBoosterModifierType).moveType === heldItemType);
|
||||
return !this.requiredHeldItemTypes.some(heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier
|
||||
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType
|
||||
&& (!this.requireTransferable || it.isTransferable));
|
||||
}).length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] {
|
||||
const requiredItems = pokemon?.getHeldItems().filter((it) => {
|
||||
return this.requiredHeldItemTypes.some(heldItemType => it instanceof AttackTypeBoosterModifier && (it.type as AttackTypeBoosterModifierType).moveType === heldItemType);
|
||||
return this.requiredHeldItemTypes.some(heldItemType =>
|
||||
it instanceof AttackTypeBoosterModifier
|
||||
&& (it.type as AttackTypeBoosterModifierType).moveType === heldItemType)
|
||||
&& (!this.requireTransferable || it.isTransferable);
|
||||
});
|
||||
if (requiredItems && requiredItems.length > 0) {
|
||||
return ["heldItem", requiredItems[0].type.name];
|
||||
|
@ -161,6 +161,11 @@ export default class MysteryEncounter implements IMysteryEncounter {
|
||||
doEncounterRewards?: (scene: BattleScene) => boolean;
|
||||
/** Will execute callback during VictoryPhase of a continuousEncounter */
|
||||
doContinueEncounter?: (scene: BattleScene) => Promise<void>;
|
||||
/**
|
||||
* Can perform special logic when a ME battle is lost, before GameOver/battle retry prompt.
|
||||
* Should return `true` if it is treated as "real" Game Over, `false` if not.
|
||||
*/
|
||||
onGameOver?: (scene: BattleScene) => boolean;
|
||||
|
||||
/**
|
||||
* Requirements
|
||||
@ -742,11 +747,11 @@ export class MysteryEncounterBuilder implements Partial<IMysteryEncounter> {
|
||||
*
|
||||
* @param min min wave (or exact size if only min is given)
|
||||
* @param max optional max size. If not given, defaults to min => exact wave
|
||||
* @param excludeFainted if true, only counts unfainted mons
|
||||
* @param excludeDisallowedPokemon if true, only counts allowed (legal in Challenge/unfainted) mons
|
||||
* @returns
|
||||
*/
|
||||
withScenePartySizeRequirement(min: number, max?: number, excludeFainted: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted));
|
||||
withScenePartySizeRequirement(min: number, max?: number, excludeDisallowedPokemon: boolean = false): this & Required<Pick<IMysteryEncounter, "requirements">> {
|
||||
return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeDisallowedPokemon));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase));
|
||||
|
||||
if (!encounter.doContinueEncounter) {
|
||||
// Only lapse eggs once for multi-battle encounters
|
||||
scene.pushPhase(new EggLapsePhase(scene));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param scene
|
||||
|
@ -13,7 +13,7 @@ import { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import { Species } from "#enums/species";
|
||||
import { Type } from "#app/data/type";
|
||||
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species";
|
||||
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getEncounterText, queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
|
||||
import { Gender } from "#app/data/gender";
|
||||
@ -170,15 +170,24 @@ export function getHighestStatTotalPlayerPokemon(scene: BattleScene, unfainted:
|
||||
* @param starterTiers
|
||||
* @param excludedSpecies
|
||||
* @param types
|
||||
* @param allowSubLegendary
|
||||
* @param allowLegendary
|
||||
* @param allowMythical
|
||||
* @returns
|
||||
*/
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[]): Species {
|
||||
export function getRandomSpeciesByStarterTier(starterTiers: number | [number, number], excludedSpecies?: Species[], types?: Type[], allowSubLegendary: boolean = true, allowLegendary: boolean = true, allowMythical: boolean = true): Species {
|
||||
let min = Array.isArray(starterTiers) ? starterTiers[0] : starterTiers;
|
||||
let max = Array.isArray(starterTiers) ? starterTiers[1] : starterTiers;
|
||||
|
||||
let filteredSpecies: [PokemonSpecies, number][] = Object.keys(speciesStarters)
|
||||
.map(s => [parseInt(s) as Species, speciesStarters[s] as number])
|
||||
.filter(s => getPokemonSpecies(s[0]) && (!excludedSpecies || !excludedSpecies.includes(s[0])))
|
||||
.filter(s => {
|
||||
const pokemonSpecies = getPokemonSpecies(s[0]);
|
||||
return pokemonSpecies && (!excludedSpecies || !excludedSpecies.includes(s[0])
|
||||
&& (allowSubLegendary || !pokemonSpecies.subLegendary)
|
||||
&& (allowLegendary || !pokemonSpecies.legendary)
|
||||
&& (allowMythical || !pokemonSpecies.mythical));
|
||||
})
|
||||
.map(s => [getPokemonSpecies(s[0]), s[1]]);
|
||||
|
||||
if (types && types.length > 0) {
|
||||
@ -773,3 +782,23 @@ export async function addPokemonDataToDexAndValidateAchievements(scene: BattleSc
|
||||
scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs);
|
||||
return scene.gameData.setPokemonCaught(pokemon, true, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Pokemon is allowed under a challenge, and allowed in battle.
|
||||
* If both are true, returns `null`.
|
||||
* If one of them is not true, returns message content that the Pokemon is invalid.
|
||||
* Typically used for cheecking whether a Pokemon can be selected for a {@linkcode MysteryEncounterOption}
|
||||
* @param pokemon
|
||||
* @param scene
|
||||
* @param invalidSelectionKey
|
||||
*/
|
||||
export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, scene: BattleScene, invalidSelectionKey: string): string | null {
|
||||
if (!pokemon.isAllowed()) {
|
||||
return i18next.t("partyUiHandler:cantBeUsed", { pokemonName: pokemon.getNameToRender() }) ?? null;
|
||||
}
|
||||
if (!pokemon.isAllowedInBattle()) {
|
||||
return getEncounterText(scene, invalidSelectionKey) ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
public fusionVariant: Variant;
|
||||
public fusionGender: Gender;
|
||||
public fusionLuck: integer;
|
||||
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null;
|
||||
|
||||
private summonDataPrimer: PokemonSummonData | null;
|
||||
|
||||
@ -206,6 +207,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.fusionVariant = dataSource.fusionVariant || 0;
|
||||
this.fusionGender = dataSource.fusionGender;
|
||||
this.fusionLuck = dataSource.fusionLuck;
|
||||
this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData;
|
||||
this.usedTMs = dataSource.usedTMs ?? [];
|
||||
this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData);
|
||||
} else {
|
||||
@ -1164,11 +1166,31 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
if (!types.length || !includeTeraType) {
|
||||
if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) {
|
||||
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
||||
this.mysteryEncounterPokemonData.types.forEach(t => types.push(t));
|
||||
} else if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
|
||||
if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) {
|
||||
this.summonData.types.forEach(t => types.push(t));
|
||||
} else if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) {
|
||||
// "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters
|
||||
types.push(this.mysteryEncounterPokemonData.types[0]);
|
||||
|
||||
// Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal
|
||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
||||
if (fusionSpeciesForm) {
|
||||
// Check if the fusion Pokemon also had "permanently changed" types
|
||||
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types;
|
||||
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
||||
types.push(fusionMETypes[1]);
|
||||
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
||||
types.push(fusionMETypes[0]);
|
||||
} else if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== types[0]) {
|
||||
types.push(fusionSpeciesForm.type2);
|
||||
} else if (fusionSpeciesForm.type1 !== types[0]) {
|
||||
types.push(fusionSpeciesForm.type1);
|
||||
}
|
||||
}
|
||||
|
||||
if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) {
|
||||
types.push(this.mysteryEncounterPokemonData.types[1]);
|
||||
}
|
||||
} else {
|
||||
const speciesForm = this.getSpeciesForm(ignoreOverride);
|
||||
|
||||
@ -1176,7 +1198,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride);
|
||||
if (fusionSpeciesForm) {
|
||||
if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) {
|
||||
// Check if the fusion Pokemon also had "permanently changed" types
|
||||
// Otherwise, use standard fusion type logic
|
||||
const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types;
|
||||
if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) {
|
||||
types.push(fusionMETypes[1]);
|
||||
} else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) {
|
||||
types.push(fusionMETypes[0]);
|
||||
} else if (fusionSpeciesForm.type2 !== null && fusionSpeciesForm.type2 !== speciesForm.type1) {
|
||||
types.push(fusionSpeciesForm.type2);
|
||||
} else if (fusionSpeciesForm.type1 !== speciesForm.type1) {
|
||||
types.push(fusionSpeciesForm.type1);
|
||||
@ -1228,12 +1257,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) {
|
||||
return allAbilities[Overrides.OPP_ABILITY_OVERRIDE];
|
||||
}
|
||||
if (this.isFusion()) {
|
||||
if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData!.ability !== -1) {
|
||||
return allAbilities[this.fusionMysteryEncounterPokemonData!.ability];
|
||||
} else {
|
||||
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
||||
}
|
||||
}
|
||||
if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) {
|
||||
return allAbilities[this.mysteryEncounterPokemonData.ability];
|
||||
}
|
||||
if (this.isFusion()) {
|
||||
return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)];
|
||||
}
|
||||
let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex);
|
||||
if (abilityId === Abilities.NONE) {
|
||||
abilityId = this.species.ability1;
|
||||
@ -1927,6 +1960,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.fusionVariant = 0;
|
||||
this.fusionGender = 0;
|
||||
this.fusionLuck = 0;
|
||||
this.fusionMysteryEncounterPokemonData = null;
|
||||
|
||||
this.generateName();
|
||||
this.calculateStats();
|
||||
@ -4207,6 +4241,7 @@ export class PlayerPokemon extends Pokemon {
|
||||
this.fusionVariant = pokemon.variant;
|
||||
this.fusionGender = pokemon.gender;
|
||||
this.fusionLuck = pokemon.luck;
|
||||
this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData;
|
||||
if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) {
|
||||
this.pauseEvolutions = true;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
"selected": "Let's do this!"
|
||||
},
|
||||
"outro": "Look how happy your {{chosenPokemon}} is now!$Here, you can have these as well.",
|
||||
"outro_dialogue": "How disappointing...$It looks like you still have a long way\nto go to earn your Pokémon's trust!",
|
||||
"gained_eggs": "@s{item_fanfare}You received {{numEggs}}!",
|
||||
"eggs_tooltip": "\n(+) Earn {{eggs}}",
|
||||
"numEggs_one": "{{count}} {{rarity}} Egg",
|
||||
|
@ -888,7 +888,7 @@ export class PokemonBaseStatTotalModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
override matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof PokemonBaseStatTotalModifier;
|
||||
return modifier instanceof PokemonBaseStatTotalModifier && this.statModifier === modifier.statModifier;
|
||||
}
|
||||
|
||||
override clone(): PersistentModifier {
|
||||
@ -939,7 +939,7 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
|
||||
override matchType(modifier: Modifier): boolean {
|
||||
return modifier instanceof PokemonBaseStatFlatModifier;
|
||||
return modifier instanceof PokemonBaseStatFlatModifier && modifier.statModifier === this.statModifier && this.stats.every(s => modifier.stats.some(stat => s === stat));
|
||||
}
|
||||
|
||||
override clone(): PersistentModifier {
|
||||
|
@ -48,6 +48,14 @@ export class GameOverPhase extends BattlePhase {
|
||||
this.victory = true;
|
||||
}
|
||||
|
||||
// Handle Mystery Encounter special Game Over cases
|
||||
// Situations such as when player lost a battle, but it isn't treated as full Game Over
|
||||
if (!this.victory && this.scene.currentBattle.mysteryEncounter?.onGameOver && !this.scene.currentBattle.mysteryEncounter.onGameOver(this.scene)) {
|
||||
// Do not end the game
|
||||
return this.end();
|
||||
}
|
||||
// Otherwise, continue standard Game Over logic
|
||||
|
||||
if (this.victory && this.scene.gameMode.isEndless) {
|
||||
const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET;
|
||||
const genderStr = PlayerGender[genderIndex].toLowerCase();
|
||||
@ -60,11 +68,6 @@ export class GameOverPhase extends BattlePhase {
|
||||
this.scene.ui.fadeOut(1250).then(() => {
|
||||
this.scene.reset();
|
||||
this.scene.clearPhaseQueue();
|
||||
// If this is a ME, clear any residual visual sprites before reloading
|
||||
const encounter = this.scene.currentBattle.mysteryEncounter;
|
||||
if (encounter?.introVisuals) {
|
||||
this.scene.field.remove(encounter.introVisuals, true);
|
||||
}
|
||||
this.scene.gameData.loadSession(this.scene, this.scene.sessionSlotId).then(() => {
|
||||
this.scene.pushPhase(new EncounterPhase(this.scene, true));
|
||||
|
||||
|
@ -54,6 +54,7 @@ export default class PokemonData {
|
||||
public fusionVariant: Variant;
|
||||
public fusionGender: Gender;
|
||||
public fusionLuck: integer;
|
||||
public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData;
|
||||
|
||||
public boss: boolean;
|
||||
public bossSegments?: integer;
|
||||
|
@ -175,7 +175,16 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
|
||||
expect(TRANSPORT_BIOMES).toContain(scene.arena.biomeType);
|
||||
});
|
||||
|
||||
it("should start a battle against an enraged boss", { retry: 5 }, async () => {
|
||||
it("should start a battle against an enraged boss below wave 50", { retry: 5 }, async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 1, 0, 0]);
|
||||
expect(enemyField[0].isBoss()).toBe(true);
|
||||
});
|
||||
|
||||
it("should start a battle against an extra enraged boss above wave 50", { retry: 5 }, async () => {
|
||||
game.override.startingWave(56);
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
const enemyField = scene.getEnemyField();
|
||||
@ -238,10 +247,19 @@ describe("Teleporting Hijinks - Mystery Encounter", () => {
|
||||
expect(TRANSPORT_BIOMES).toContain(scene.arena.biomeType);
|
||||
});
|
||||
|
||||
it("should start a battle against an enraged boss", async () => {
|
||||
it("should start a battle against an enraged boss below wave 50", async () => {
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, [Species.PIKACHU]);
|
||||
await runMysteryEncounterToEnd(game, 2, undefined, true);
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(enemyField[0].summonData.statStages).toEqual([0, 1, 0, 1, 1, 0, 0]);
|
||||
expect(enemyField[0].isBoss()).toBe(true);
|
||||
});
|
||||
|
||||
it("should start a battle against an extra enraged boss above wave 50", { retry: 5 }, async () => {
|
||||
game.override.startingWave(56);
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.TELEPORTING_HIJINKS, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1, undefined, true);
|
||||
const enemyField = scene.getEnemyField();
|
||||
expect(enemyField[0].summonData.statStages).toEqual([1, 1, 1, 1, 1, 0, 0]);
|
||||
expect(enemyField[0].isBoss()).toBe(true);
|
||||
});
|
||||
|
@ -169,12 +169,14 @@ export class UiInputs {
|
||||
}
|
||||
switch (this.scene.ui?.getMode()) {
|
||||
case Mode.MESSAGE:
|
||||
if (!(this.scene.ui.getHandler() as MessageUiHandler).pendingPrompt) {
|
||||
const messageHandler = this.scene.ui.getHandler<MessageUiHandler>();
|
||||
if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) {
|
||||
return;
|
||||
}
|
||||
case Mode.TITLE:
|
||||
case Mode.COMMAND:
|
||||
case Mode.MODIFIER_SELECT:
|
||||
case Mode.MYSTERY_ENCOUNTER:
|
||||
this.scene.ui.setOverlayMode(Mode.MENU);
|
||||
break;
|
||||
case Mode.STARTER_SELECT:
|
||||
|
@ -223,6 +223,14 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler {
|
||||
};
|
||||
}
|
||||
|
||||
isTextAnimationInProgress() {
|
||||
if (this.textTimer) {
|
||||
return this.textTimer.repeatCount < this.textTimer.repeat;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
clearText() {
|
||||
this.message.setText("");
|
||||
this.pendingPrompt = false;
|
||||
|
@ -701,6 +701,7 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
const profileContainer = this.scene.add.container(0, -pageBg.height);
|
||||
pageContainer.add(profileContainer);
|
||||
|
||||
// TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs
|
||||
const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT);
|
||||
trainerText.setOrigin(0, 0);
|
||||
profileContainer.add(trainerText);
|
||||
|
Loading…
Reference in New Issue
Block a user