mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-31 12:42:22 +02:00
Merge branch 'beta' into instruct
This commit is contained in:
commit
ba2a292410
@ -4,7 +4,7 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
|
||||
import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species";
|
||||
import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils";
|
||||
import * as Utils from "#app/utils";
|
||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
|
||||
import { Phase } from "#app/phase";
|
||||
@ -47,7 +47,7 @@ import PokemonInfoContainer from "#app/ui/pokemon-info-container";
|
||||
import { biomeDepths, getBiomeName } from "#app/data/balance/biomes";
|
||||
import { SceneBase } from "#app/scene-base";
|
||||
import CandyBar from "#app/ui/candy-bar";
|
||||
import { Variant, variantData } from "#app/data/variant";
|
||||
import { Variant, variantColorCache, variantData, VariantSet } from "#app/data/variant";
|
||||
import { Localizable } from "#app/interfaces/locales";
|
||||
import Overrides from "#app/overrides";
|
||||
import { InputsController } from "#app/inputs-controller";
|
||||
@ -345,6 +345,33 @@ export default class BattleScene extends SceneBase {
|
||||
this.load.atlas(key, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.png`, `images/pokemon/${variant ? "variant/" : ""}${experimental ? "exp/" : ""}${atlasPath}.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the variant assets for the given sprite and stores them in {@linkcode variantColorCache}
|
||||
*/
|
||||
loadPokemonVariantAssets(spriteKey: string, fileRoot: string, variant?: Variant) {
|
||||
const useExpSprite = this.experimentalSprites && this.hasExpSprite(spriteKey);
|
||||
if (useExpSprite) {
|
||||
fileRoot = `exp/${fileRoot}`;
|
||||
}
|
||||
let variantConfig = variantData;
|
||||
fileRoot.split("/").map(p => variantConfig ? variantConfig = variantConfig[p] : null);
|
||||
const variantSet = variantConfig as VariantSet;
|
||||
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
|
||||
const populateVariantColors = (key: string): Promise<void> => {
|
||||
return new Promise(resolve => {
|
||||
if (variantColorCache.hasOwnProperty(key)) {
|
||||
return resolve();
|
||||
}
|
||||
this.cachedFetch(`./images/pokemon/variant/${fileRoot}.json`).then(res => res.json()).then(c => {
|
||||
variantColorCache[key] = c;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
populateVariantColors(spriteKey);
|
||||
}
|
||||
}
|
||||
|
||||
async preload() {
|
||||
if (DEBUG_RNG) {
|
||||
const scene = this;
|
||||
@ -891,7 +918,7 @@ export default class BattleScene extends SceneBase {
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
|
||||
addEnemyPokemon(species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean = false, shinyLock: boolean = false, dataSource?: PokemonData, postProcess?: (enemyPokemon: EnemyPokemon) => void): EnemyPokemon {
|
||||
if (Overrides.OPP_LEVEL_OVERRIDE > 0) {
|
||||
level = Overrides.OPP_LEVEL_OVERRIDE;
|
||||
}
|
||||
@ -901,13 +928,11 @@ export default class BattleScene extends SceneBase {
|
||||
boss = this.getEncounterBossSegments(this.currentBattle.waveIndex, level, species) > 1;
|
||||
}
|
||||
|
||||
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, dataSource);
|
||||
const pokemon = new EnemyPokemon(this, species, level, trainerSlot, boss, shinyLock, dataSource);
|
||||
if (Overrides.OPP_FUSION_OVERRIDE) {
|
||||
pokemon.generateFusionSpecies();
|
||||
}
|
||||
|
||||
overrideModifiers(this, false);
|
||||
overrideHeldItems(this, pokemon, false);
|
||||
if (boss && !dataSource) {
|
||||
const secondaryIvs = Utils.getIvsFromId(Utils.randSeedInt(4294967296));
|
||||
|
||||
|
@ -3720,16 +3720,16 @@ export class PostTurnHurtIfSleepingAbAttr extends PostTurnAbAttr {
|
||||
|
||||
/**
|
||||
* Deals damage to all sleeping opponents equal to 1/8 of their max hp (min 1)
|
||||
* @param {Pokemon} pokemon Pokemon that has this ability
|
||||
* @param {boolean} passive N/A
|
||||
* @param {boolean} simulated true if applying in a simulated call.
|
||||
* @param {any[]} args N/A
|
||||
* @returns {boolean} true if any opponents are sleeping
|
||||
* @param pokemon Pokemon that has this ability
|
||||
* @param passive N/A
|
||||
* @param simulated `true` if applying in a simulated call.
|
||||
* @param args N/A
|
||||
* @returns `true` if any opponents are sleeping
|
||||
*/
|
||||
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
let hadEffect: boolean = false;
|
||||
for (const opp of pokemon.getOpponents()) {
|
||||
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
||||
if ((opp.status?.effect === StatusEffect.SLEEP || opp.hasAbility(Abilities.COMATOSE)) && !opp.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !opp.switchOutStatus) {
|
||||
if (!simulated) {
|
||||
opp.damageAndUpdate(Utils.toDmgValue(opp.getMaxHp() / 8), HitResult.OTHER);
|
||||
pokemon.scene.queueMessage(i18next.t("abilityTriggers:badDreams", { pokemonName: getPokemonNameWithAffix(opp) }));
|
||||
@ -5713,9 +5713,7 @@ export function initAbilities() {
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
new Ability(Abilities.SHEER_FORCE, 5)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
|
||||
.attr(MoveEffectChanceMultiplierAbAttr, 0)
|
||||
.edgeCase() // Should disable shell bell and Meloetta's relic song transformation
|
||||
.edgeCase(), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
|
||||
.attr(MoveEffectChanceMultiplierAbAttr, 0), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
|
||||
new Ability(Abilities.CONTRARY, 5)
|
||||
.attr(StatStageChangeMultiplierAbAttr, -1)
|
||||
.ignorable(),
|
||||
|
@ -1144,7 +1144,7 @@ class FireGrassPledgeTag extends ArenaTag {
|
||||
? arena.scene.getPlayerField()
|
||||
: arena.scene.getEnemyField();
|
||||
|
||||
field.filter(pokemon => !pokemon.isOfType(Type.FIRE)).forEach(pokemon => {
|
||||
field.filter(pokemon => !pokemon.isOfType(Type.FIRE) && !pokemon.switchOutStatus).forEach(pokemon => {
|
||||
// "{pokemonNameWithAffix} was hurt by the sea of fire!"
|
||||
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
// TODO: Replace this with a proper animation
|
||||
|
@ -16,9 +16,9 @@ interface PokemonSpeciesFormLevelMoves {
|
||||
}
|
||||
|
||||
/** Moves that can only be learned with a memory-mushroom */
|
||||
const RELEARN_MOVE = -1;
|
||||
export const RELEARN_MOVE = -1;
|
||||
/** Moves that can only be learned with an evolve */
|
||||
const EVOLVE_MOVE = 0;
|
||||
export const EVOLVE_MOVE = 0;
|
||||
|
||||
export const pokemonSpeciesLevelMoves: PokemonSpeciesLevelMoves = {
|
||||
[Species.BULBASAUR]: [
|
||||
|
@ -3,10 +3,10 @@ import { Species } from "#enums/species";
|
||||
export const POKERUS_STARTER_COUNT = 5;
|
||||
|
||||
// #region Friendship constants
|
||||
export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2;
|
||||
export const FRIENDSHIP_GAIN_FROM_BATTLE = 2;
|
||||
export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5;
|
||||
export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
|
||||
export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 3;
|
||||
export const FRIENDSHIP_GAIN_FROM_BATTLE = 3;
|
||||
export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 6;
|
||||
export const FRIENDSHIP_LOSS_FROM_FAINT = 5;
|
||||
|
||||
/**
|
||||
* Function to get the cumulative friendship threshold at which a candy is earned
|
||||
@ -16,19 +16,19 @@ export const FRIENDSHIP_LOSS_FROM_FAINT = 10;
|
||||
export function getStarterValueFriendshipCap(starterCost: number): number {
|
||||
switch (starterCost) {
|
||||
case 1:
|
||||
return 20;
|
||||
return 25;
|
||||
case 2:
|
||||
return 40;
|
||||
return 50;
|
||||
case 3:
|
||||
return 60;
|
||||
return 75;
|
||||
case 4:
|
||||
return 100;
|
||||
case 5:
|
||||
return 140;
|
||||
return 150;
|
||||
case 6:
|
||||
return 200;
|
||||
case 7:
|
||||
return 280;
|
||||
return 300;
|
||||
case 8:
|
||||
case 9:
|
||||
return 450;
|
||||
|
@ -1867,7 +1867,7 @@ export class FlameBurstAttr extends MoveEffectAttr {
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, targetAlly, cancelled);
|
||||
}
|
||||
|
||||
if (cancelled.value || !targetAlly) {
|
||||
if (cancelled.value || !targetAlly || targetAlly.switchOutStatus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -216,6 +216,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
species: getPokemonSpecies(Species.GREEDENT),
|
||||
isBoss: true,
|
||||
bossSegments: 3,
|
||||
shiny: false, // Shiny lock because of consistency issues between the different options
|
||||
moveSet: [ Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.CRUNCH ],
|
||||
modifierConfigs: bossModifierConfigs,
|
||||
tags: [ BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON ],
|
||||
@ -353,9 +354,9 @@ export const AbsoluteAvariceEncounter: MysteryEncounter =
|
||||
})
|
||||
.withOptionPhase(async (scene: BattleScene) => {
|
||||
// Let it have the food
|
||||
// Greedent joins the team, level equal to 2 below highest party member
|
||||
// Greedent joins the team, level equal to 2 below highest party member (shiny locked)
|
||||
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, true);
|
||||
greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ];
|
||||
greedent.passive = true;
|
||||
|
||||
|
@ -98,7 +98,9 @@ export const BerriesAboundEncounter: MysteryEncounter =
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
isPokemon: true,
|
||||
isShiny: bossPokemon.shiny,
|
||||
variant: bossPokemon.variant
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -276,6 +276,8 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
||||
generateItemsOfTier(scene, mostHeldItemsPokemon, numBerries, "Berries");
|
||||
|
||||
// Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm)
|
||||
// For the purpose of this ME, Soothe Bells and Lucky Eggs are counted as Ultra tier
|
||||
// And Golden Eggs as Rogue tier
|
||||
let numUltra = 0;
|
||||
let numRogue = 0;
|
||||
items.filter(m => m.isTransferable && !(m instanceof BerryModifier))
|
||||
@ -285,7 +287,7 @@ export const ClowningAroundEncounter: MysteryEncounter =
|
||||
if (type.id === "GOLDEN_EGG" || tier === ModifierTier.ROGUE) {
|
||||
numRogue += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
} else if (type.id === "LUCKY_EGG" || tier === ModifierTier.ULTRA) {
|
||||
} else if (type.id === "LUCKY_EGG" || type.id === "SOOTHE_BELL" || tier === ModifierTier.ULTRA) {
|
||||
numUltra += m.stackCount;
|
||||
scene.removeModifier(m);
|
||||
}
|
||||
@ -456,7 +458,6 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem
|
||||
[ modifierTypes.LEFTOVERS, 4 ],
|
||||
[ modifierTypes.SHELL_BELL, 4 ],
|
||||
[ modifierTypes.SOUL_DEW, 10 ],
|
||||
[ modifierTypes.SOOTHE_BELL, 3 ],
|
||||
[ modifierTypes.SCOPE_LENS, 1 ],
|
||||
[ modifierTypes.BATON, 1 ],
|
||||
[ modifierTypes.FOCUS_BAND, 5 ],
|
||||
|
@ -92,9 +92,13 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||
.withCatchAllowed(true)
|
||||
.withFleeAllowed(false)
|
||||
.withOnVisualsStart((scene: BattleScene) => {
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, scene.getEnemyPokemon()!, scene.getPlayerPokemon()!);
|
||||
danceAnim.play(scene);
|
||||
|
||||
const oricorio = scene.getEnemyPokemon()!;
|
||||
const danceAnim = new EncounterBattleAnim(EncounterAnim.DANCE, oricorio, scene.getPlayerPokemon()!);
|
||||
danceAnim.play(scene, false, () => {
|
||||
if (oricorio.shiny) {
|
||||
oricorio.sparkle();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
})
|
||||
.withIntroDialogue([
|
||||
@ -136,7 +140,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||
}
|
||||
|
||||
const oricorioData = new PokemonData(enemyPokemon);
|
||||
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, oricorioData);
|
||||
const oricorio = scene.addEnemyPokemon(species, level, TrainerSlot.NONE, false, false, oricorioData);
|
||||
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
scene.getEnemyParty().forEach(enemyPokemon => {
|
||||
|
@ -114,7 +114,9 @@ export const FightOrFlightEncounter: MysteryEncounter =
|
||||
tint: 0.25,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
isPokemon: true,
|
||||
isShiny: bossPokemon.shiny,
|
||||
variant: bossPokemon.variant
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -194,10 +194,10 @@ async function summonPlayerPokemon(scene: BattleScene) {
|
||||
playerAnimationPromise = summonPlayerPokemonAnimation(scene, playerPokemon);
|
||||
});
|
||||
|
||||
// Also loads Wobbuffet data
|
||||
// Also loads Wobbuffet data (cannot be shiny)
|
||||
const enemySpecies = getPokemonSpecies(Species.WOBBUFFET);
|
||||
scene.currentBattle.enemyParty = [];
|
||||
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false);
|
||||
const wobbuffet = scene.addEnemyPokemon(enemySpecies, encounter.misc.playerPokemon.level, TrainerSlot.NONE, false, true);
|
||||
wobbuffet.ivs = [ 0, 0, 0, 0, 0, 0 ];
|
||||
wobbuffet.setNature(Nature.MILD);
|
||||
wobbuffet.setAlpha(0);
|
||||
|
@ -12,8 +12,7 @@ import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon
|
||||
import { getTypeRgb } from "#app/data/type";
|
||||
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
|
||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||
import * as Utils from "#app/utils";
|
||||
import { IntegerHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle } from "#app/utils";
|
||||
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
|
||||
import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, ShinyRateBoosterModifier, SpeciesStatBoosterModifier } from "#app/modifier/modifier";
|
||||
import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
|
||||
@ -27,6 +26,7 @@ import { trainerNamePools } from "#app/data/trainer-names";
|
||||
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
|
||||
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import type { PokeballType } from "#enums/pokeball";
|
||||
import { doShinySparkleAnim } from "#app/field/anims";
|
||||
|
||||
/** the i18n namespace for the encounter */
|
||||
const namespace = "mysteryEncounters/globalTradeSystem";
|
||||
@ -230,7 +230,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
const tradePokemon = new EnemyPokemon(scene, randomTradeOption, pokemon.level, TrainerSlot.NONE, false);
|
||||
// Extra shiny roll at 1/128 odds (boosted by events and charms)
|
||||
if (!tradePokemon.shiny) {
|
||||
const shinyThreshold = new Utils.IntegerHolder(WONDER_TRADE_SHINY_CHANCE);
|
||||
const shinyThreshold = new NumberHolder(WONDER_TRADE_SHINY_CHANCE);
|
||||
if (scene.eventManager.isEventActive()) {
|
||||
shinyThreshold.value *= scene.eventManager.getShinyMultiplier();
|
||||
}
|
||||
@ -247,7 +247,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter =
|
||||
const hiddenIndex = tradePokemon.species.ability2 ? 2 : 1;
|
||||
if (tradePokemon.species.abilityHidden) {
|
||||
if (tradePokemon.abilityIndex < hiddenIndex) {
|
||||
const hiddenAbilityChance = new IntegerHolder(64);
|
||||
const hiddenAbilityChance = new NumberHolder(64);
|
||||
scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance);
|
||||
|
||||
const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value);
|
||||
@ -582,7 +582,13 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
|
||||
receivedPokemonTintSprite.setTintFill(getPokeballTintColor(receivedPokemon.pokeball));
|
||||
|
||||
[ tradedPokemonSprite, tradedPokemonTintSprite ].map(sprite => {
|
||||
sprite.play(tradedPokemon.getSpriteKey(true));
|
||||
const spriteKey = tradedPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
|
||||
@ -597,7 +603,13 @@ function doPokemonTradeSequence(scene: BattleScene, tradedPokemon: PlayerPokemon
|
||||
});
|
||||
|
||||
[ receivedPokemonSprite, receivedPokemonTintSprite ].map(sprite => {
|
||||
sprite.play(receivedPokemon.getSpriteKey(true));
|
||||
const spriteKey = receivedPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) });
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
|
||||
@ -797,6 +809,14 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
|
||||
receivedPokeballSprite.x = tradeBaseBg.displayWidth / 2;
|
||||
receivedPokeballSprite.y = tradeBaseBg.displayHeight / 2 - 100;
|
||||
|
||||
// Received pokemon sparkles
|
||||
let pokemonShinySparkle: Phaser.GameObjects.Sprite;
|
||||
if (receivedPokemon.shiny) {
|
||||
pokemonShinySparkle = scene.add.sprite(receivedPokemonSprite.x, receivedPokemonSprite.y, "shiny");
|
||||
pokemonShinySparkle.setVisible(false);
|
||||
tradeContainer.add(pokemonShinySparkle);
|
||||
}
|
||||
|
||||
const BASE_ANIM_DURATION = 1000;
|
||||
|
||||
// Pokeball falls to the screen
|
||||
@ -835,6 +855,11 @@ function doTradeReceivedSequence(scene: BattleScene, receivedPokemon: PlayerPoke
|
||||
scale: 1,
|
||||
alpha: 0,
|
||||
onComplete: () => {
|
||||
if (receivedPokemon.shiny) {
|
||||
scene.time.delayedCall(500, () => {
|
||||
doShinySparkleAnim(scene, pokemonShinySparkle, receivedPokemon.variant);
|
||||
});
|
||||
}
|
||||
receivedPokeballSprite.destroy();
|
||||
scene.time.delayedCall(2000, () => resolve());
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ export const MysteriousChestEncounter: MysteryEncounter =
|
||||
MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST)
|
||||
.withEncounterTier(MysteryEncounterTier.COMMON)
|
||||
.withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES)
|
||||
.withScenePartySizeRequirement(2, 6, true)
|
||||
.withAutoHideIntroVisuals(false)
|
||||
.withCatchAllowed(true)
|
||||
.withIntroSpriteConfigs([
|
||||
|
@ -60,6 +60,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter =
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||
status: [ StatusEffect.SLEEP, 5 ], // Extra turns on timer for Snorlax's start of fight moves
|
||||
moveSet: [ Moves.REST, Moves.SLEEP_TALK, Moves.CRUNCH, Moves.GIGA_IMPACT ],
|
||||
modifierConfigs: [
|
||||
|
@ -72,13 +72,11 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
|
||||
let pokemon: PlayerPokemon;
|
||||
if (randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 || isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) {
|
||||
// If no HA mon found or you roll 1%, give shiny Magikarp
|
||||
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
|
||||
species = getPokemonSpecies(Species.MAGIKARP);
|
||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex, undefined, true, 0);
|
||||
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex, undefined, true);
|
||||
} else {
|
||||
const hiddenIndex = species.ability2 ? 2 : 1;
|
||||
pokemon = new PlayerPokemon(scene, species, 5, hiddenIndex, species.formIndex);
|
||||
pokemon = new PlayerPokemon(scene, species, 5, 2, species.formIndex);
|
||||
}
|
||||
pokemon.generateAndPopulateMoveset();
|
||||
|
||||
@ -88,7 +86,9 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter =
|
||||
fileRoot: fileRoot,
|
||||
hasShadow: true,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
isPokemon: true,
|
||||
isShiny: pokemon.shiny,
|
||||
variant: pokemon.variant
|
||||
});
|
||||
|
||||
const starterTier = speciesStarterCosts[species.speciesId];
|
||||
|
@ -79,6 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter =
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
shiny: false, // Shiny lock because shiny is rolled only if the battle option is picked
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
||||
|
@ -61,11 +61,12 @@ export const TrashToTreasureEncounter: MysteryEncounter =
|
||||
.withOnInit((scene: BattleScene) => {
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
|
||||
// Calculate boss mon
|
||||
// Calculate boss mon (shiny locked)
|
||||
const bossSpecies = getPokemonSpecies(Species.GARBODOR);
|
||||
const pokemonConfig: EnemyPokemonConfig = {
|
||||
species: bossSpecies,
|
||||
isBoss: true,
|
||||
shiny: false, // Shiny lock because of custom intro sprite
|
||||
formIndex: 1, // Gmax
|
||||
bossSegmentModifier: 1, // +1 Segment from normal
|
||||
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ]
|
||||
|
@ -100,7 +100,9 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
hasShadow: true,
|
||||
x: -5,
|
||||
repeat: true,
|
||||
isPokemon: true
|
||||
isPokemon: true,
|
||||
isShiny: pokemon.shiny,
|
||||
variant: pokemon.variant
|
||||
},
|
||||
];
|
||||
|
||||
@ -113,13 +115,15 @@ export const UncommonBreedEncounter: MysteryEncounter =
|
||||
const encounter = scene.currentBattle.mysteryEncounter!;
|
||||
const pokemonSprite = encounter.introVisuals!.getSprites();
|
||||
|
||||
scene.tweens.add({ // Bounce at the end
|
||||
// Bounce at the end, then shiny sparkle if the Pokemon is shiny
|
||||
scene.tweens.add({
|
||||
targets: pokemonSprite,
|
||||
duration: 300,
|
||||
ease: "Cubic.easeOut",
|
||||
yoyo: true,
|
||||
y: "-=20",
|
||||
loop: 1,
|
||||
onComplete: () => encounter.introVisuals?.playShinySparkles()
|
||||
});
|
||||
|
||||
scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2"));
|
||||
|
@ -184,7 +184,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
dataSource = config.dataSource;
|
||||
enemySpecies = config.species;
|
||||
isBoss = config.isBoss;
|
||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, dataSource);
|
||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.TRAINER, isBoss, false, dataSource);
|
||||
} else {
|
||||
battle.enemyParty[e] = battle.trainer.genPartyMember(e);
|
||||
}
|
||||
@ -202,7 +202,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig:
|
||||
enemySpecies = scene.randomSpecies(battle.waveIndex, level, true);
|
||||
}
|
||||
|
||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, dataSource);
|
||||
battle.enemyParty[e] = scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, isBoss, false, dataSource);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,7 +290,10 @@ export function applyDamageToPokemon(scene: BattleScene, pokemon: PlayerPokemon,
|
||||
if (damage <= 0) {
|
||||
console.warn("Healing pokemon with `applyDamageToPokemon` is not recommended! Please use `applyHealToPokemon` instead.");
|
||||
}
|
||||
|
||||
// If a Pokemon would faint from the damage applied, its HP is instead set to 1.
|
||||
if (pokemon.isAllowedInBattle() && pokemon.hp - damage <= 0) {
|
||||
damage = pokemon.hp - 1;
|
||||
}
|
||||
applyHpChangeToPokemon(scene, pokemon, -damage);
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,13 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
|
||||
pokemonEvoTintSprite.setTintFill(0xFFFFFF);
|
||||
|
||||
[ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||
sprite.play(previousPokemon.getSpriteKey(true));
|
||||
const spriteKey = previousPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) });
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
|
||||
@ -69,7 +75,13 @@ export function doPokemonTransformationSequence(scene: BattleScene, previousPoke
|
||||
});
|
||||
|
||||
[ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => {
|
||||
sprite.play(transformPokemon.getSpriteKey(true));
|
||||
const spriteKey = transformPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", transformPokemon.shiny);
|
||||
|
@ -351,6 +351,10 @@ export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMove
|
||||
if (pokemon.scene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
|
||||
return false;
|
||||
} else {
|
||||
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
|
||||
if (pokemon.hasAbility(Abilities.SHEER_FORCE)) {
|
||||
return false;
|
||||
}
|
||||
return super.canChange(pokemon);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { EvolutionLevel, SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPr
|
||||
import { Type } from "#enums/type";
|
||||
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { Variant, VariantSet, variantColorCache, variantData } from "#app/data/variant";
|
||||
import { Variant, VariantSet, variantData } from "#app/data/variant";
|
||||
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
|
||||
import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
|
||||
@ -511,29 +511,8 @@ export abstract class PokemonSpeciesForm {
|
||||
} else {
|
||||
scene.anims.get(spriteKey).frameRate = 10;
|
||||
}
|
||||
let spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
|
||||
const useExpSprite = scene.experimentalSprites && scene.hasExpSprite(spriteKey);
|
||||
if (useExpSprite) {
|
||||
spritePath = `exp/${spritePath}`;
|
||||
}
|
||||
let config = variantData;
|
||||
spritePath.split("/").map(p => config ? config = config[p] : null);
|
||||
const variantSet = config as VariantSet;
|
||||
if (variantSet && (variant !== undefined && variantSet[variant] === 1)) {
|
||||
const populateVariantColors = (key: string): Promise<void> => {
|
||||
return new Promise(resolve => {
|
||||
if (variantColorCache.hasOwnProperty(key)) {
|
||||
return resolve();
|
||||
}
|
||||
scene.cachedFetch(`./images/pokemon/variant/${spritePath}.json`).then(res => res.json()).then(c => {
|
||||
variantColorCache[key] = c;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
populateVariantColors(spriteKey).then(() => resolve());
|
||||
return;
|
||||
}
|
||||
const spritePath = this.getSpriteAtlasPath(female, formIndex, shiny, variant).replace("variant/", "").replace(/_[1-3]$/, "");
|
||||
scene.loadPokemonVariantAssets(spriteKey, spritePath, variant);
|
||||
resolve();
|
||||
});
|
||||
if (startLoad) {
|
||||
|
@ -1173,16 +1173,28 @@ export function getRandomPartyMemberFunc(speciesPool: Species[], trainerSlot: Tr
|
||||
if (!ignoreEvolution) {
|
||||
species = getPokemonSpecies(species).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex);
|
||||
}
|
||||
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, undefined, postProcess);
|
||||
return scene.addEnemyPokemon(getPokemonSpecies(species), level, trainerSlot, undefined, false, undefined, postProcess);
|
||||
};
|
||||
}
|
||||
|
||||
function getSpeciesFilterRandomPartyMemberFunc(speciesFilter: PokemonSpeciesFilter, trainerSlot: TrainerSlot = TrainerSlot.TRAINER, allowLegendaries?: boolean, postProcess?: (EnemyPokemon: EnemyPokemon) => void): PartyMemberFunc {
|
||||
const originalSpeciesFilter = speciesFilter;
|
||||
speciesFilter = (species: PokemonSpecies) => (allowLegendaries || (!species.legendary && !species.subLegendary && !species.mythical)) && !species.isTrainerForbidden() && originalSpeciesFilter(species);
|
||||
return (scene: BattleScene, level: integer, strength: PartyMemberStrength) => {
|
||||
const ret = scene.addEnemyPokemon(getPokemonSpecies(scene.randomSpecies(scene.currentBattle.waveIndex, level, false, speciesFilter).getTrainerSpeciesForLevel(level, true, strength, scene.currentBattle.waveIndex)), level, trainerSlot, undefined, undefined, postProcess);
|
||||
return ret;
|
||||
function getSpeciesFilterRandomPartyMemberFunc(
|
||||
originalSpeciesFilter: PokemonSpeciesFilter,
|
||||
trainerSlot: TrainerSlot = TrainerSlot.TRAINER,
|
||||
allowLegendaries?: boolean,
|
||||
postProcess?: (EnemyPokemon: EnemyPokemon) => void
|
||||
): PartyMemberFunc {
|
||||
|
||||
const speciesFilter = (species: PokemonSpecies): boolean => {
|
||||
const notLegendary = !species.legendary && !species.subLegendary && !species.mythical;
|
||||
return (allowLegendaries || notLegendary) && !species.isTrainerForbidden() && originalSpeciesFilter(species);
|
||||
};
|
||||
|
||||
return (scene: BattleScene, level: number, strength: PartyMemberStrength) => {
|
||||
const waveIndex = scene.currentBattle.waveIndex;
|
||||
const species = getPokemonSpecies(scene.randomSpecies(waveIndex, level, false, speciesFilter)
|
||||
.getTrainerSpeciesForLevel(level, true, strength, waveIndex));
|
||||
|
||||
return scene.addEnemyPokemon(species, level, trainerSlot, undefined, false, undefined, postProcess);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import BattleScene from "../battle-scene";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { PokeballType } from "#enums/pokeball";
|
||||
import * as Utils from "../utils";
|
||||
import { Variant } from "#app/data/variant";
|
||||
import { getFrameMs, randGauss } from "#app/utils";
|
||||
|
||||
export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void {
|
||||
switch (pokeballType) {
|
||||
@ -127,7 +128,7 @@ function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y:
|
||||
|
||||
const particleTimer = scene.tweens.addCounter({
|
||||
repeat: -1,
|
||||
duration: Utils.getFrameMs(1),
|
||||
duration: getFrameMs(1),
|
||||
onRepeat: () => {
|
||||
updateParticle();
|
||||
}
|
||||
@ -159,7 +160,7 @@ export function addPokeballCaptureStars(scene: BattleScene, pokeball: Phaser.Gam
|
||||
}
|
||||
});
|
||||
|
||||
const dist = Utils.randGauss(25);
|
||||
const dist = randGauss(25);
|
||||
scene.tweens.add({
|
||||
targets: particle,
|
||||
x: pokeball.x + dist,
|
||||
@ -185,3 +186,31 @@ export function sin(index: integer, amplitude: integer): number {
|
||||
export function cos(index: integer, amplitude: integer): number {
|
||||
return amplitude * Math.cos(index * (Math.PI / 128));
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the shiny sparkle animation and sound effect for the given sprite
|
||||
* First ensures that the animation has been properly initialized
|
||||
* @param sparkleSprite the Sprite to play the animation on
|
||||
* @param variant which shiny {@linkcode variant} to play the animation for
|
||||
*/
|
||||
export function doShinySparkleAnim(scene: BattleScene, sparkleSprite: Phaser.GameObjects.Sprite, variant: Variant) {
|
||||
const keySuffix = variant ? `_${variant + 1}` : "";
|
||||
const spriteKey = `shiny${keySuffix}`;
|
||||
const animationKey = `sparkle${keySuffix}`;
|
||||
|
||||
// Make sure the animation exists, and create it if not
|
||||
if (!scene.anims.exists(animationKey)) {
|
||||
const frameNames = scene.anims.generateFrameNames(spriteKey, { suffix: ".png", end: 34 });
|
||||
scene.anims.create({
|
||||
key: `sparkle${keySuffix}`,
|
||||
frames: frameNames,
|
||||
frameRate: 32,
|
||||
showOnStart: true,
|
||||
hideOnComplete: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Play the animation
|
||||
sparkleSprite.play(animationKey);
|
||||
scene.playSound("se/sparkle");
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { GameObjects } from "phaser";
|
||||
import BattleScene from "../battle-scene";
|
||||
import MysteryEncounter from "../data/mystery-encounters/mystery-encounter";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
|
||||
import { Species } from "#enums/species";
|
||||
import { isNullOrUndefined } from "#app/utils";
|
||||
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
|
||||
import PlayAnimationConfig = Phaser.Types.Animations.PlayAnimationConfig;
|
||||
import { Variant } from "#app/data/variant";
|
||||
import { doShinySparkleAnim } from "#app/field/anims";
|
||||
|
||||
type KnownFileRoot =
|
||||
| "arenas"
|
||||
@ -59,6 +61,10 @@ export class MysteryEncounterSpriteConfig {
|
||||
scale?: number;
|
||||
/** If you are using a Pokemon sprite, set to `true`. This will ensure variant, form, gender, shiny sprites are loaded properly */
|
||||
isPokemon?: boolean;
|
||||
/** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */
|
||||
isShiny?: boolean;
|
||||
/** If using a Pokemon shiny sprite, needs to be set to ensure the correct variant assets get loaded and displayed */
|
||||
variant?: Variant;
|
||||
/** If you are using an item sprite, set to `true` */
|
||||
isItem?: boolean;
|
||||
/** The sprites alpha. `0` - `1` The lower the number, the more transparent */
|
||||
@ -74,6 +80,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
public encounter: MysteryEncounter;
|
||||
public spriteConfigs: MysteryEncounterSpriteConfig[];
|
||||
public enterFromRight: boolean;
|
||||
private shinySparkleSprites: { sprite: Phaser.GameObjects.Sprite, variant: Variant }[];
|
||||
|
||||
constructor(scene: BattleScene, encounter: MysteryEncounter) {
|
||||
super(scene, -72, 76);
|
||||
@ -86,7 +93,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
};
|
||||
|
||||
if (!isNullOrUndefined(result.species)) {
|
||||
const keys = getSpriteKeysFromSpecies(result.species);
|
||||
const keys = getSpriteKeysFromSpecies(result.species, undefined, undefined, result.isShiny, result.variant);
|
||||
result.spriteKey = keys.spriteKey;
|
||||
result.fileRoot = keys.fileRoot;
|
||||
result.isPokemon = true;
|
||||
@ -120,18 +127,36 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
// Sprites with custom X or Y defined will not count for normal spacing requirements
|
||||
const spacingValue = Math.round((maxX - minX) / Math.max(this.spriteConfigs.filter(s => !s.x && !s.y).length, 1));
|
||||
|
||||
this.shinySparkleSprites = [];
|
||||
const shinySparkleSprites = scene.add.container(0, 0);
|
||||
this.spriteConfigs?.forEach((config) => {
|
||||
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha } = config;
|
||||
const { spriteKey, isItem, hasShadow, scale, x, y, yShadow, alpha, isPokemon, isShiny, variant } = config;
|
||||
|
||||
let sprite: GameObjects.Sprite;
|
||||
let tintSprite: GameObjects.Sprite;
|
||||
let pokemonShinySparkle: Phaser.GameObjects.Sprite | undefined;
|
||||
|
||||
if (!isItem) {
|
||||
sprite = getSprite(spriteKey, hasShadow, yShadow);
|
||||
tintSprite = getSprite(spriteKey);
|
||||
} else {
|
||||
if (isItem) {
|
||||
sprite = getItemSprite(spriteKey, hasShadow, yShadow);
|
||||
tintSprite = getItemSprite(spriteKey);
|
||||
} else {
|
||||
sprite = getSprite(spriteKey, hasShadow, yShadow);
|
||||
tintSprite = getSprite(spriteKey);
|
||||
if (isPokemon && isShiny) {
|
||||
// Set Pipeline for shiny variant
|
||||
sprite.setPipelineData("spriteKey", spriteKey);
|
||||
tintSprite.setPipelineData("spriteKey", spriteKey);
|
||||
sprite.setPipelineData("shiny", true);
|
||||
sprite.setPipelineData("variant", variant);
|
||||
tintSprite.setPipelineData("shiny", true);
|
||||
tintSprite.setPipelineData("variant", variant);
|
||||
// Create Sprite for shiny Sparkle
|
||||
pokemonShinySparkle = scene.add.sprite(sprite.x, sprite.y, "shiny");
|
||||
pokemonShinySparkle.setOrigin(0.5, 1);
|
||||
pokemonShinySparkle.setVisible(false);
|
||||
this.shinySparkleSprites.push({ sprite: pokemonShinySparkle, variant: variant ?? 0 });
|
||||
shinySparkleSprites.add(pokemonShinySparkle);
|
||||
}
|
||||
}
|
||||
|
||||
sprite.setVisible(!config.hidden);
|
||||
@ -165,6 +190,11 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNullOrUndefined(pokemonShinySparkle)) {
|
||||
// Offset the sparkle to match the Pokemon's position
|
||||
pokemonShinySparkle.setPosition(sprite.x, sprite.y);
|
||||
}
|
||||
|
||||
if (!isNullOrUndefined(alpha)) {
|
||||
sprite.setAlpha(alpha);
|
||||
tintSprite.setAlpha(alpha);
|
||||
@ -173,6 +203,7 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
this.add(sprite);
|
||||
this.add(tintSprite);
|
||||
});
|
||||
this.add(shinySparkleSprites);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,6 +218,9 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
this.spriteConfigs.forEach((config) => {
|
||||
if (config.isPokemon) {
|
||||
this.scene.loadPokemonAtlas(config.spriteKey, config.fileRoot);
|
||||
if (config.isShiny) {
|
||||
this.scene.loadPokemonVariantAssets(config.spriteKey, config.fileRoot, config.variant);
|
||||
}
|
||||
} else if (config.isItem) {
|
||||
this.scene.loadAtlas("items", "");
|
||||
} else {
|
||||
@ -240,11 +274,21 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
this.getSprites().map((sprite, i) => {
|
||||
if (!this.spriteConfigs[i].isItem) {
|
||||
sprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
||||
if (sprite.texture.frameTotal > 1) {
|
||||
// Show the first animation frame for a smooth transition when the animation starts.
|
||||
const firstFrame = sprite.texture.frames["0001.png"];
|
||||
sprite.setFrame(firstFrame ?? 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.getTintSprites().map((tintSprite, i) => {
|
||||
if (!this.spriteConfigs[i].isItem) {
|
||||
tintSprite.setTexture(this.spriteConfigs[i].spriteKey).setFrame(0);
|
||||
if (tintSprite.texture.frameTotal > 1) {
|
||||
// Show the first frame for a smooth transition when the animation starts.
|
||||
const firstFrame = tintSprite.texture.frames["0001.png"];
|
||||
tintSprite.setFrame(firstFrame ?? 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -288,6 +332,17 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play shiny sparkle animations if there are shiny Pokemon
|
||||
*/
|
||||
playShinySparkles() {
|
||||
for (const sparkleConfig of this.shinySparkleSprites) {
|
||||
this.scene.time.delayedCall(500, () => {
|
||||
doShinySparkleAnim(this.scene, sparkleConfig.sprite, sparkleConfig.variant);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For sprites with animation and that do not have animation disabled, will begin frame animation
|
||||
*/
|
||||
|
@ -23,13 +23,13 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/
|
||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, 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, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, PostDamageForceSwitchAbAttr, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability";
|
||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, 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, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handler";
|
||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||
import { LevelMoves } from "#app/data/balance/pokemon-level-moves";
|
||||
import { EVOLVE_MOVE, LevelMoves, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves";
|
||||
import { DamageAchv, achvs } from "#app/system/achv";
|
||||
import { DexAttr, StarterDataEntry, StarterMoveset } from "#app/system/game-data";
|
||||
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
|
||||
@ -69,6 +69,16 @@ import { SpeciesFormKey } from "#enums/species-form-key";
|
||||
import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates";
|
||||
import { Nature } from "#enums/nature";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import { doShinySparkleAnim } from "#app/field/anims";
|
||||
|
||||
export enum LearnMoveSituation {
|
||||
MISC,
|
||||
LEVEL_UP,
|
||||
RELEARN,
|
||||
EVOLUTION,
|
||||
EVOLUTION_FUSED, // If fusionSpecies has Evolved
|
||||
EVOLUTION_FUSED_BASE // If fusion's base species has Evolved
|
||||
}
|
||||
|
||||
export enum FieldPosition {
|
||||
CENTER,
|
||||
@ -325,6 +335,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (!this.scene) {
|
||||
return false;
|
||||
}
|
||||
if (this.switchOutStatus) {
|
||||
return false;
|
||||
}
|
||||
return this.scene.field.getIndex(this) > -1;
|
||||
}
|
||||
|
||||
@ -670,21 +683,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
initShinySparkle(): void {
|
||||
const keySuffix = this.variant ? `_${this.variant + 1}` : "";
|
||||
const key = `shiny${keySuffix}`;
|
||||
const shinySparkle = this.scene.addFieldSprite(0, 0, key);
|
||||
const shinySparkle = this.scene.addFieldSprite(0, 0, "shiny");
|
||||
shinySparkle.setVisible(false);
|
||||
shinySparkle.setOrigin(0.5, 1);
|
||||
const frameNames = this.scene.anims.generateFrameNames(key, { suffix: ".png", end: 34 });
|
||||
if (!(this.scene.anims.exists(`sparkle${keySuffix}`))) {
|
||||
this.scene.anims.create({
|
||||
key: `sparkle${keySuffix}`,
|
||||
frames: frameNames,
|
||||
frameRate: 32,
|
||||
showOnStart: true,
|
||||
hideOnComplete: true,
|
||||
});
|
||||
}
|
||||
this.add(shinySparkle);
|
||||
|
||||
this.shinySparkle = shinySparkle;
|
||||
@ -1583,7 +1584,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
const trappedByAbility = new Utils.BooleanHolder(false);
|
||||
const opposingField = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
|
||||
/**
|
||||
* Contains opposing Pokemon (Enemy/Player Pokemon) depending on perspective
|
||||
* Afterwards, it filters out Pokemon that have been switched out of the field so trapped abilities/moves do not trigger
|
||||
*/
|
||||
const opposingFieldUnfiltered = this.isPlayer() ? this.scene.getEnemyField() : this.scene.getPlayerField();
|
||||
const opposingField = opposingFieldUnfiltered.filter(enemyPkm => enemyPkm.switchOutStatus === false);
|
||||
|
||||
opposingField.forEach((opponent) =>
|
||||
applyCheckTrappedAbAttrs(CheckTrappedAbAttr, opponent, trappedByAbility, this, trappedAbMessages, simulated)
|
||||
@ -1820,40 +1826,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param {boolean} includeRelearnerMoves Whether to include moves that would require a relearner. Note the move relearner inherently allows evolution moves
|
||||
* @returns {LevelMoves} A list of moves and the levels they can be learned at
|
||||
*/
|
||||
getLevelMoves(startingLevel?: integer, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false): LevelMoves {
|
||||
getLevelMoves(startingLevel?: integer, includeEvolutionMoves: boolean = false, simulateEvolutionChain: boolean = false, includeRelearnerMoves: boolean = false, learnSituation: LearnMoveSituation = LearnMoveSituation.MISC): LevelMoves {
|
||||
const ret: LevelMoves = [];
|
||||
let levelMoves: LevelMoves = [];
|
||||
if (!startingLevel) {
|
||||
startingLevel = this.level;
|
||||
}
|
||||
if (simulateEvolutionChain) {
|
||||
const evolutionChain = this.species.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
||||
for (let e = 0; e < evolutionChain.length; e++) {
|
||||
// TODO: Might need to pass specific form index in simulated evolution chain
|
||||
const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves();
|
||||
if (includeRelearnerMoves) {
|
||||
levelMoves.push(...speciesLevelMoves);
|
||||
} else {
|
||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1]))));
|
||||
}
|
||||
}
|
||||
if (learnSituation === LearnMoveSituation.EVOLUTION_FUSED && this.fusionSpecies) { // For fusion evolutions, get ONLY the moves of the component mon that evolved
|
||||
levelMoves = this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0);
|
||||
} else {
|
||||
levelMoves = this.getSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === 0) || (includeRelearnerMoves && lm[0] === -1) || lm[0] > 0);
|
||||
}
|
||||
if (this.fusionSpecies) {
|
||||
if (simulateEvolutionChain) {
|
||||
const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
||||
for (let e = 0; e < fusionEvolutionChain.length; e++) {
|
||||
const evolutionChain = this.species.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
||||
for (let e = 0; e < evolutionChain.length; e++) {
|
||||
// TODO: Might need to pass specific form index in simulated evolution chain
|
||||
const speciesLevelMoves = getPokemonSpeciesForm(fusionEvolutionChain[e][0], this.fusionFormIndex).getLevelMoves();
|
||||
const speciesLevelMoves = getPokemonSpeciesForm(evolutionChain[e][0], this.formIndex).getLevelMoves();
|
||||
if (includeRelearnerMoves) {
|
||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || lm[0] !== 0));
|
||||
levelMoves.push(...speciesLevelMoves);
|
||||
} else {
|
||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === 0) || ((!e || lm[0] > 1) && (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1]))));
|
||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && (e === evolutionChain.length - 1 || lm[0] <= evolutionChain[e + 1][1]))));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
levelMoves.push(...this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === 0) || (includeRelearnerMoves && lm[0] === -1) || lm[0] > 0));
|
||||
levelMoves = this.getSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0);
|
||||
}
|
||||
if (this.fusionSpecies && learnSituation !== LearnMoveSituation.EVOLUTION_FUSED_BASE) { // For fusion evolutions, get ONLY the moves of the component mon that evolved
|
||||
if (simulateEvolutionChain) {
|
||||
const fusionEvolutionChain = this.fusionSpecies.getSimulatedEvolutionChain(this.level, this.hasTrainer(), this.isBoss(), this.isPlayer());
|
||||
for (let e = 0; e < fusionEvolutionChain.length; e++) {
|
||||
// TODO: Might need to pass specific form index in simulated evolution chain
|
||||
const speciesLevelMoves = getPokemonSpeciesForm(fusionEvolutionChain[e][0], this.fusionFormIndex).getLevelMoves();
|
||||
if (includeRelearnerMoves) {
|
||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || lm[0] !== EVOLVE_MOVE));
|
||||
} else {
|
||||
levelMoves.push(...speciesLevelMoves.filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || ((!e || lm[0] > 1) && (e === fusionEvolutionChain.length - 1 || lm[0] <= fusionEvolutionChain[e + 1][1]))));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
levelMoves.push(...this.getFusionSpeciesForm(true).getLevelMoves().filter(lm => (includeEvolutionMoves && lm[0] === EVOLVE_MOVE) || (includeRelearnerMoves && lm[0] === RELEARN_MOVE) || lm[0] > 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
levelMoves.sort((lma: [integer, integer], lmb: [integer, integer]) => lma[0] > lmb[0] ? 1 : lma[0] < lmb[0] ? -1 : 0);
|
||||
@ -1968,6 +1978,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Function that tries to set a Pokemon shiny based on seed.
|
||||
* For manual use only, usually to roll a Pokemon's shiny chance a second time.
|
||||
* If it rolls shiny, also sets a random variant and give the Pokemon the associated luck.
|
||||
*
|
||||
* The base shiny odds are {@linkcode BASE_SHINY_CHANCE} / `65536`
|
||||
* @param thresholdOverride number that is divided by `2^16` (`65536`) to get the shiny chance, overrides {@linkcode shinyThreshold} if set (bypassing shiny rate modifiers such as Shiny Charm)
|
||||
@ -1993,6 +2004,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.shiny = randSeedInt(65536) < shinyThreshold.value;
|
||||
|
||||
if (this.shiny) {
|
||||
this.variant = this.generateShinyVariant();
|
||||
this.luck = this.variant + 1 + (this.fusionShiny ? this.fusionVariant + 1 : 0);
|
||||
this.initShinySparkle();
|
||||
}
|
||||
|
||||
@ -2896,14 +2909,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
this.turnData.damageTaken += damage;
|
||||
this.battleData.hitCount++;
|
||||
|
||||
// Multi-Lens and Parental Bond check for Wimp Out/Emergency Exit
|
||||
if (this.hasAbilityWithAttr(PostDamageForceSwitchAbAttr)) {
|
||||
const multiHitModifier = source.getHeldItems().find(m => m instanceof PokemonMultiHitModifier);
|
||||
if (multiHitModifier || source.hasAbilityWithAttr(AddSecondStrikeAbAttr)) {
|
||||
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source);
|
||||
}
|
||||
}
|
||||
|
||||
const attackResult = { move: move.id, result: result as DamageResult, damage: damage, critical: isCritical, sourceId: source.id, sourceBattlerIndex: source.getBattlerIndex() };
|
||||
this.turnData.attacksReceived.unshift(attackResult);
|
||||
if (source.isPlayer() && !this.isPlayer()) {
|
||||
@ -3004,13 +3009,22 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param ignoreFaintPhase boolean to ignore adding a FaintPhase, passsed to damage()
|
||||
* @returns integer of damage done
|
||||
*/
|
||||
damageAndUpdate(damage: integer, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): integer {
|
||||
damageAndUpdate(damage: number, result?: DamageResult, critical: boolean = false, ignoreSegments: boolean = false, preventEndure: boolean = false, ignoreFaintPhase: boolean = false, source?: Pokemon): number {
|
||||
const damagePhase = new DamageAnimPhase(this.scene, this.getBattlerIndex(), damage, result as DamageResult, critical);
|
||||
this.scene.unshiftPhase(damagePhase);
|
||||
if (this.switchOutStatus && source) {
|
||||
damage = 0;
|
||||
}
|
||||
damage = this.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase);
|
||||
// Damage amount may have changed, but needed to be queued before calling damage function
|
||||
damagePhase.updateAmount(damage);
|
||||
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source);
|
||||
/**
|
||||
* Run PostDamageAbAttr from any source of damage that is not from a multi-hit
|
||||
* Multi-hits are handled in move-effect-phase.ts for PostDamageAbAttr
|
||||
*/
|
||||
if (!source || source.turnData.hitCount <= 1) {
|
||||
applyPostDamageAbAttrs(PostDamageAbAttr, this, damage, this.hasPassive(), false, [], source);
|
||||
}
|
||||
return damage;
|
||||
}
|
||||
|
||||
@ -3727,8 +3741,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
setFrameRate(frameRate: integer) {
|
||||
this.scene.anims.get(this.getBattleSpriteKey()).frameRate = frameRate;
|
||||
this.getSprite().play(this.getBattleSpriteKey());
|
||||
this.getTintSprite()?.play(this.getBattleSpriteKey());
|
||||
try {
|
||||
this.getSprite().play(this.getBattleSpriteKey());
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err);
|
||||
}
|
||||
try {
|
||||
this.getTintSprite()?.play(this.getBattleSpriteKey());
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${this.getBattleSpriteKey()}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
tint(color: number, alpha?: number, duration?: integer, ease?: string) {
|
||||
@ -3793,8 +3815,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
|
||||
sparkle(): void {
|
||||
if (this.shinySparkle) {
|
||||
this.shinySparkle.play(`sparkle${this.variant ? `_${this.variant + 1}` : ""}`);
|
||||
this.scene.playSound("se/sparkle");
|
||||
doShinySparkleAnim(this.scene, this.shinySparkle, this.variant);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4271,28 +4292,29 @@ export class PlayerPokemon extends Pokemon {
|
||||
});
|
||||
}
|
||||
|
||||
addFriendship(friendship: integer): void {
|
||||
const starterSpeciesId = this.species.getRootSpeciesId();
|
||||
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
||||
const starterData = [
|
||||
this.scene.gameData.starterData[starterSpeciesId],
|
||||
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
|
||||
].filter(d => !!d);
|
||||
const amount = new Utils.IntegerHolder(friendship);
|
||||
let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER;
|
||||
if (this.scene.eventManager.isEventActive()) {
|
||||
candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier();
|
||||
}
|
||||
const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
||||
if (amount.value > 0) {
|
||||
addFriendship(friendship: number): void {
|
||||
if (friendship > 0) {
|
||||
const starterSpeciesId = this.species.getRootSpeciesId();
|
||||
const fusionStarterSpeciesId = this.isFusion() && this.fusionSpecies ? this.fusionSpecies.getRootSpeciesId() : 0;
|
||||
const starterData = [
|
||||
this.scene.gameData.starterData[starterSpeciesId],
|
||||
fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null
|
||||
].filter(d => !!d);
|
||||
const amount = new Utils.NumberHolder(friendship);
|
||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount);
|
||||
this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount);
|
||||
let candyFriendshipMultiplier = CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER;
|
||||
if (this.scene.eventManager.isEventActive()) {
|
||||
candyFriendshipMultiplier *= this.scene.eventManager.getFriendshipMultiplier();
|
||||
}
|
||||
const starterAmount = new Utils.NumberHolder(Math.floor(amount.value * (this.scene.gameMode.isClassic ? candyFriendshipMultiplier : 1) / (fusionStarterSpeciesId ? 2 : 1)));
|
||||
|
||||
// Add friendship to this PlayerPokemon
|
||||
this.friendship = Math.min(this.friendship + amount.value, 255);
|
||||
if (this.friendship === 255) {
|
||||
this.scene.validateAchv(achvs.MAX_FRIENDSHIP);
|
||||
}
|
||||
starterData.forEach((sd: StarterDataEntry, i: integer) => {
|
||||
// Add to candy progress for this mon's starter species and its fused species (if it has one)
|
||||
starterData.forEach((sd: StarterDataEntry, i: number) => {
|
||||
const speciesId = !i ? starterSpeciesId : fusionStarterSpeciesId as Species;
|
||||
sd.friendship = (sd.friendship || 0) + starterAmount.value;
|
||||
if (sd.friendship >= getStarterValueFriendshipCap(speciesStarterCosts[speciesId])) {
|
||||
@ -4301,10 +4323,8 @@ export class PlayerPokemon extends Pokemon {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.friendship = Math.max(this.friendship + amount.value, 0);
|
||||
for (const sd of starterData) {
|
||||
sd.friendship = Math.max((sd.friendship || 0) + starterAmount.value, 0);
|
||||
}
|
||||
// Lose friendship upon fainting
|
||||
this.friendship = Math.max(this.friendship + friendship, 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -4637,12 +4657,13 @@ export class EnemyPokemon extends Pokemon {
|
||||
public aiType: AiType;
|
||||
public bossSegments: integer;
|
||||
public bossSegmentIndex: integer;
|
||||
/** To indicate of the instance was populated with a dataSource -> e.g. loaded & populated from session data */
|
||||
/** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */
|
||||
public readonly isPopulatedFromDataSource: boolean;
|
||||
|
||||
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, dataSource?: PokemonData) {
|
||||
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex,
|
||||
dataSource?.gender, dataSource ? dataSource.shiny : false, dataSource ? dataSource.variant : undefined, undefined, dataSource ? dataSource.nature : undefined, dataSource);
|
||||
constructor(scene: BattleScene, species: PokemonSpecies, level: integer, trainerSlot: TrainerSlot, boss: boolean, shinyLock: boolean = false, dataSource?: PokemonData) {
|
||||
super(scene, 236, 84, species, level, dataSource?.abilityIndex, dataSource?.formIndex, dataSource?.gender,
|
||||
(!shinyLock && dataSource) ? dataSource.shiny : false, (!shinyLock && dataSource) ? dataSource.variant : undefined,
|
||||
undefined, dataSource ? dataSource.nature : undefined, dataSource);
|
||||
|
||||
this.trainerSlot = trainerSlot;
|
||||
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
|
||||
@ -4671,12 +4692,15 @@ export class EnemyPokemon extends Pokemon {
|
||||
if (!dataSource) {
|
||||
this.generateAndPopulateMoveset();
|
||||
|
||||
this.trySetShiny();
|
||||
if (Overrides.OPP_SHINY_OVERRIDE) {
|
||||
if (shinyLock || Overrides.OPP_SHINY_OVERRIDE === false) {
|
||||
this.shiny = false;
|
||||
} else {
|
||||
this.trySetShiny();
|
||||
}
|
||||
|
||||
if (!this.shiny && Overrides.OPP_SHINY_OVERRIDE) {
|
||||
this.shiny = true;
|
||||
this.initShinySparkle();
|
||||
} else if (Overrides.OPP_SHINY_OVERRIDE === false) {
|
||||
this.shiny = false;
|
||||
}
|
||||
|
||||
if (this.shiny) {
|
||||
|
@ -1702,7 +1702,8 @@ const modifierPool: ModifierPool = {
|
||||
new WeightedModifierType(modifierTypes.EVOLUTION_ITEM, (party: Pokemon[]) => {
|
||||
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8);
|
||||
}, 8),
|
||||
new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 1 : 0, 1),
|
||||
new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 2 : 0, 2),
|
||||
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 2),
|
||||
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
|
||||
new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => {
|
||||
if (!party.find(p => p.getLearnableLevelMoves().length)) {
|
||||
@ -1800,7 +1801,6 @@ const modifierPool: ModifierPool = {
|
||||
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
|
||||
//new WeightedModifierType(modifierTypes.OVAL_CHARM, 6),
|
||||
new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => !party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100 ? 4 : 0, 4),
|
||||
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 4),
|
||||
new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)),
|
||||
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
|
||||
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
|
||||
|
@ -18,7 +18,6 @@ import type { VoucherType } from "#app/system/voucher";
|
||||
import { Command } from "#app/ui/command-ui-handler";
|
||||
import { addTextObject, TextStyle } from "#app/ui/text";
|
||||
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, toDmgValue } from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { BerryType } from "#enums/berry-type";
|
||||
import { Moves } from "#enums/moves";
|
||||
@ -726,22 +725,6 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
|
||||
return 1;
|
||||
}
|
||||
|
||||
//Applies to items with chance of activating secondary effects ie Kings Rock
|
||||
getSecondaryChanceMultiplier(pokemon: Pokemon): number {
|
||||
// Temporary quickfix to stop game from freezing when the opponet uses u-turn while holding on to king's rock
|
||||
if (!pokemon.getLastXMoves()[0]) {
|
||||
return 1;
|
||||
}
|
||||
const sheerForceAffected = allMoves[pokemon.getLastXMoves()[0].move].chance >= 0 && pokemon.hasAbility(Abilities.SHEER_FORCE);
|
||||
|
||||
if (sheerForceAffected) {
|
||||
return 0;
|
||||
} else if (pokemon.hasAbility(Abilities.SERENE_GRACE)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): number {
|
||||
const pokemon = this.getPokemon(scene);
|
||||
if (!pokemon) {
|
||||
@ -1614,9 +1597,16 @@ export class BypassSpeedChanceModifier extends PokemonHeldItemModifier {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for Pokemon held items like King's Rock
|
||||
* Because King's Rock can be stacked in PokeRogue, unlike mainline, it does not receive a boost from Abilities.SERENE_GRACE
|
||||
*/
|
||||
export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
||||
private chance: number;
|
||||
constructor(type: ModifierType, pokemonId: number, stackCount?: number) {
|
||||
super(type, pokemonId, stackCount);
|
||||
|
||||
this.chance = 10;
|
||||
}
|
||||
|
||||
matchType(modifier: Modifier) {
|
||||
@ -1644,7 +1634,8 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
||||
* @returns `true` if {@linkcode FlinchChanceModifier} has been applied
|
||||
*/
|
||||
override apply(pokemon: Pokemon, flinched: BooleanHolder): boolean {
|
||||
if (!flinched.value && pokemon.randSeedInt(10) < (this.getStackCount() * this.getSecondaryChanceMultiplier(pokemon))) {
|
||||
// The check for pokemon.battleSummonData is to ensure that a crash doesn't occur when a Pokemon with King's Rock procs a flinch
|
||||
if (pokemon.battleSummonData && !flinched.value && pokemon.randSeedInt(100) < (this.getStackCount() * this.chance)) {
|
||||
flinched.value = true;
|
||||
return true;
|
||||
}
|
||||
@ -1652,7 +1643,7 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
getMaxHeldItemCount(pokemon: Pokemon): number {
|
||||
getMaxHeldItemCount(_pokemon: Pokemon): number {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,11 @@ class DefaultOverrides {
|
||||
// MYSTERY ENCOUNTER OVERRIDES
|
||||
// -------------------------
|
||||
|
||||
/** 1 to 256, set to null to ignore */
|
||||
/**
|
||||
* `1` (almost never) to `256` (always), set to `null` to disable the override
|
||||
*
|
||||
* Note: Make sure `STARTING_WAVE_OVERRIDE > 10`, otherwise MEs won't trigger
|
||||
*/
|
||||
readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number | null = null;
|
||||
readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier | null = null;
|
||||
readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType | null = null;
|
||||
|
@ -5,7 +5,6 @@ import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import i18next from "i18next";
|
||||
import { BattlePhase } from "./battle-phase";
|
||||
import { PostSummonPhase } from "./post-summon-phase";
|
||||
import { SummonMissingPhase } from "./summon-missing-phase";
|
||||
import { SwitchPhase } from "./switch-phase";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
@ -54,7 +53,6 @@ export class CheckSwitchPhase extends BattlePhase {
|
||||
this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? getPokemonNameWithAffix(pokemon) : i18next.t("battle:pokemon") }), null, () => {
|
||||
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true));
|
||||
this.end();
|
||||
}, () => {
|
||||
|
@ -14,6 +14,7 @@ import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||
import * as Utils from "#app/utils";
|
||||
import { EggLapsePhase } from "./egg-lapse-phase";
|
||||
import { EggHatchData } from "#app/data/egg-hatch-data";
|
||||
import { doShinySparkleAnim } from "#app/field/anims";
|
||||
|
||||
|
||||
/**
|
||||
@ -329,7 +330,12 @@ export class EggHatchPhase extends Phase {
|
||||
this.scene.validateAchv(achvs.HATCH_SHINY);
|
||||
}
|
||||
this.eggContainer.setVisible(false);
|
||||
this.pokemonSprite.play(this.pokemon.getSpriteKey(true));
|
||||
const spriteKey = this.pokemon.getSpriteKey(true);
|
||||
try {
|
||||
this.pokemonSprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
|
||||
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
||||
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);
|
||||
@ -341,8 +347,7 @@ export class EggHatchPhase extends Phase {
|
||||
this.pokemon.cry();
|
||||
if (isShiny) {
|
||||
this.scene.time.delayedCall(Utils.fixedInt(500), () => {
|
||||
this.pokemonShinySparkle.play(`sparkle${this.pokemon.variant ? `_${this.pokemon.variant + 1}` : ""}`);
|
||||
this.scene.playSound("se/sparkle");
|
||||
doShinySparkleAnim(this.scene, this.pokemonShinySparkle, this.pokemon.variant);
|
||||
});
|
||||
}
|
||||
this.scene.time.delayedCall(Utils.fixedInt(!this.skipped ? !isShiny ? 1250 : 1750 : !isShiny ? 250 : 750), () => {
|
||||
|
@ -34,6 +34,7 @@ import { Biome } from "#enums/biome";
|
||||
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { Species } from "#enums/species";
|
||||
import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier";
|
||||
import i18next from "i18next";
|
||||
import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters";
|
||||
|
||||
@ -216,6 +217,11 @@ export class EncounterPhase extends BattlePhase {
|
||||
if (!this.loaded && battle.battleType !== BattleType.MYSTERY_ENCOUNTER) {
|
||||
regenerateModifierPoolThresholds(this.scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD);
|
||||
this.scene.generateEnemyModifiers();
|
||||
overrideModifiers(this.scene, false);
|
||||
this.scene.getEnemyField().forEach(enemy => {
|
||||
overrideHeldItems(this.scene, enemy, false);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
|
||||
@ -379,6 +385,9 @@ export class EncounterPhase extends BattlePhase {
|
||||
|
||||
if (encounter.onVisualsStart) {
|
||||
encounter.onVisualsStart(this.scene);
|
||||
} else if (encounter.spriteConfigs && introVisuals) {
|
||||
// If the encounter doesn't have any special visual intro, show sparkle for shiny Pokemon
|
||||
introVisuals.playShinySparkles();
|
||||
}
|
||||
|
||||
const doEncounter = () => {
|
||||
|
@ -1,17 +1,18 @@
|
||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||
import { Phase } from "#app/phase";
|
||||
import BattleScene, { AnySound } from "#app/battle-scene";
|
||||
import { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
||||
import { FusionSpeciesFormEvolution, SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
|
||||
import EvolutionSceneHandler from "#app/ui/evolution-scene-handler";
|
||||
import * as Utils from "#app/utils";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { cos, sin } from "#app/field/anims";
|
||||
import Pokemon, { PlayerPokemon } from "#app/field/pokemon";
|
||||
import Pokemon, { LearnMoveSituation, PlayerPokemon } from "#app/field/pokemon";
|
||||
import { getTypeRgb } from "#app/data/type";
|
||||
import i18next from "i18next";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { LearnMovePhase } from "#app/phases/learn-move-phase";
|
||||
import { EndEvolutionPhase } from "#app/phases/end-evolution-phase";
|
||||
import { EVOLVE_MOVE } from "#app/data/balance/pokemon-level-moves";
|
||||
|
||||
export class EvolutionPhase extends Phase {
|
||||
protected pokemon: PlayerPokemon;
|
||||
@ -20,6 +21,7 @@ export class EvolutionPhase extends Phase {
|
||||
private preEvolvedPokemonName: string;
|
||||
|
||||
private evolution: SpeciesFormEvolution | null;
|
||||
private fusionSpeciesEvolved: boolean; // Whether the evolution is of the fused species
|
||||
private evolutionBgm: AnySound;
|
||||
private evolutionHandler: EvolutionSceneHandler;
|
||||
|
||||
@ -39,6 +41,7 @@ export class EvolutionPhase extends Phase {
|
||||
this.pokemon = pokemon;
|
||||
this.evolution = evolution;
|
||||
this.lastLevel = lastLevel;
|
||||
this.fusionSpeciesEvolved = evolution instanceof FusionSpeciesFormEvolution;
|
||||
}
|
||||
|
||||
validate(): boolean {
|
||||
@ -102,7 +105,13 @@ export class EvolutionPhase extends Phase {
|
||||
this.scene.ui.add(this.evolutionOverlay);
|
||||
|
||||
[ this.pokemonSprite, this.pokemonTintSprite, this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
||||
sprite.play(this.pokemon.getSpriteKey(true));
|
||||
const spriteKey = this.pokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) });
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
||||
@ -127,7 +136,13 @@ export class EvolutionPhase extends Phase {
|
||||
this.pokemon.getPossibleEvolution(this.evolution).then(evolvedPokemon => {
|
||||
|
||||
[ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
||||
sprite.play(evolvedPokemon.getSpriteKey(true));
|
||||
const spriteKey = evolvedPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", evolvedPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", evolvedPokemon.shiny);
|
||||
@ -261,7 +276,8 @@ export class EvolutionPhase extends Phase {
|
||||
this.evolutionHandler.canCancel = false;
|
||||
|
||||
this.pokemon.evolve(this.evolution, this.pokemon.species).then(() => {
|
||||
const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true);
|
||||
const learnSituation: LearnMoveSituation = this.fusionSpeciesEvolved ? LearnMoveSituation.EVOLUTION_FUSED : this.pokemon.fusionSpecies ? LearnMoveSituation.EVOLUTION_FUSED_BASE : LearnMoveSituation.EVOLUTION;
|
||||
const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true, false, false, learnSituation).filter(lm => lm[0] === EVOLVE_MOVE);
|
||||
for (const lm of levelMoves) {
|
||||
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.scene.getPlayerParty().indexOf(this.pokemon), lm[1]));
|
||||
}
|
||||
|
@ -39,7 +39,13 @@ export class FormChangePhase extends EvolutionPhase {
|
||||
this.pokemon.getPossibleForm(this.formChange).then(transformedPokemon => {
|
||||
|
||||
[ this.pokemonEvoSprite, this.pokemonEvoTintSprite ].map(sprite => {
|
||||
sprite.play(transformedPokemon.getSpriteKey(true));
|
||||
const spriteKey = transformedPokemon.getSpriteKey(true);
|
||||
try {
|
||||
sprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
|
||||
sprite.setPipelineData("ignoreTimeTint", true);
|
||||
sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey());
|
||||
sprite.setPipelineData("shiny", transformedPokemon.shiny);
|
||||
|
@ -23,6 +23,12 @@ import * as Utils from "#app/utils";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import i18next from "i18next";
|
||||
import { SessionSaveData } from "#app/system/game-data";
|
||||
import PersistentModifierData from "#app/system/modifier-data";
|
||||
import PokemonData from "#app/system/pokemon-data";
|
||||
import ChallengeData from "#app/system/challenge-data";
|
||||
import TrainerData from "#app/system/trainer-data";
|
||||
import ArenaData from "#app/system/arena-data";
|
||||
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
|
||||
|
||||
export class GameOverPhase extends BattlePhase {
|
||||
@ -109,7 +115,7 @@ export class GameOverPhase extends BattlePhase {
|
||||
this.scene.gameData.gameStats.dailyRunSessionsWon++;
|
||||
}
|
||||
}
|
||||
this.scene.gameData.saveRunHistory(this.scene, this.scene.gameData.getSessionSaveData(this.scene), this.isVictory);
|
||||
|
||||
const fadeDuration = this.isVictory ? 10000 : 5000;
|
||||
this.scene.fadeOutBgm(fadeDuration, true);
|
||||
const activeBattlers = this.scene.getField().filter(p => p?.isActive(true));
|
||||
@ -135,8 +141,11 @@ export class GameOverPhase extends BattlePhase {
|
||||
this.scene.unshiftPhase(new GameOverModifierRewardPhase(this.scene, modifierTypes.VOUCHER_PREMIUM));
|
||||
}
|
||||
}
|
||||
this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase));
|
||||
this.end();
|
||||
this.getRunHistoryEntry().then(runHistoryEntry => {
|
||||
this.scene.gameData.saveRunHistory(this.scene, runHistoryEntry, this.isVictory);
|
||||
this.scene.pushPhase(new PostGameOverPhase(this.scene, endCardPhase));
|
||||
this.end();
|
||||
});
|
||||
};
|
||||
|
||||
if (this.isVictory && this.scene.gameMode.isClassic) {
|
||||
@ -212,5 +221,34 @@ export class GameOverPhase extends BattlePhase {
|
||||
this.firstRibbons.push(getPokemonSpecies(pokemon.species.getRootSpeciesId(forStarter)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Slightly modified version of {@linkcode GameData.getSessionSaveData}.
|
||||
* @returns A promise containing the {@linkcode SessionSaveData}
|
||||
*/
|
||||
private async getRunHistoryEntry(): Promise<SessionSaveData> {
|
||||
const preWaveSessionData = await this.scene.gameData.getSession(this.scene.sessionSlotId);
|
||||
return {
|
||||
seed: this.scene.seed,
|
||||
playTime: this.scene.sessionPlayTime,
|
||||
gameMode: this.scene.gameMode.modeId,
|
||||
party: this.scene.getPlayerParty().map(p => new PokemonData(p)),
|
||||
enemyParty: this.scene.getEnemyParty().map(p => new PokemonData(p)),
|
||||
modifiers: preWaveSessionData ? preWaveSessionData.modifiers : this.scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)),
|
||||
enemyModifiers: preWaveSessionData ? preWaveSessionData.enemyModifiers : this.scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)),
|
||||
arena: new ArenaData(this.scene.arena),
|
||||
pokeballCounts: this.scene.pokeballCounts,
|
||||
money: Math.floor(this.scene.money),
|
||||
score: this.scene.score,
|
||||
waveIndex: this.scene.currentBattle.waveIndex,
|
||||
battleType: this.scene.currentBattle.battleType,
|
||||
trainer: this.scene.currentBattle.trainer ? new TrainerData(this.scene.currentBattle.trainer) : null,
|
||||
gameVersion: this.scene.game.config.gameVersion,
|
||||
timestamp: new Date().getTime(),
|
||||
challenges: this.scene.gameMode.challenges.map(c => new ChallengeData(c)),
|
||||
mysteryEncounterType: this.scene.currentBattle.mysteryEncounter?.encounterType ?? -1,
|
||||
mysteryEncounterSaveData: this.scene.mysteryEncounterSaveData
|
||||
} as SessionSaveData;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,13 @@ import {
|
||||
AddSecondStrikeAbAttr,
|
||||
AlwaysHitAbAttr,
|
||||
applyPostAttackAbAttrs,
|
||||
applyPostDamageAbAttrs,
|
||||
applyPostDefendAbAttrs,
|
||||
applyPreAttackAbAttrs,
|
||||
IgnoreMoveEffectsAbAttr,
|
||||
MaxMultiHitAbAttr,
|
||||
PostAttackAbAttr,
|
||||
PostDamageAbAttr,
|
||||
PostDefendAbAttr,
|
||||
TypeImmunityAbAttr,
|
||||
} from "#app/data/ability";
|
||||
@ -236,9 +238,11 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* If the move missed a target, stop all future hits against that target
|
||||
* and move on to the next target (if there is one).
|
||||
*/
|
||||
if (isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) {
|
||||
if (target.switchOutStatus || isCommanding || (!isImmune && !isProtected && !targetHitChecks[target.getBattlerIndex()])) {
|
||||
this.stopMultiHit(target);
|
||||
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
||||
if (!target.switchOutStatus) {
|
||||
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
if (moveHistoryEntry.result === MoveResult.PENDING) {
|
||||
moveHistoryEntry.result = MoveResult.MISS;
|
||||
}
|
||||
@ -307,6 +311,13 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
*/
|
||||
if (lastHit) {
|
||||
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
||||
/**
|
||||
* Multi-Lens, Multi Hit move and Parental Bond check for PostDamageAbAttr
|
||||
* other damage source are calculated in damageAndUpdate in pokemon.ts
|
||||
*/
|
||||
if (user.turnData.hitCount > 1) {
|
||||
applyPostDamageAbAttrs(PostDamageAbAttr, target, 0, target.hasPassive(), false, [], user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,9 +27,12 @@ export class PostSummonPhase extends PokemonPhase {
|
||||
pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON);
|
||||
}
|
||||
|
||||
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end());
|
||||
applyPostSummonAbAttrs(PostSummonAbAttr, pokemon)
|
||||
.then(() => {
|
||||
const field = pokemon.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
|
||||
field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false));
|
||||
|
||||
const field = pokemon.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
|
||||
field.forEach((p) => applyAbAttrs(CommanderAbAttr, p, null, false));
|
||||
this.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
||||
|
||||
start() {
|
||||
const pokemon = this.getPokemon();
|
||||
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn()) {
|
||||
if (pokemon?.isActive(true) && pokemon.status && pokemon.status.isPostTurn() && !pokemon.switchOutStatus) {
|
||||
pokemon.status.incrementTurn();
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
|
||||
|
@ -43,7 +43,12 @@ export class QuietFormChangePhase extends BattlePhase {
|
||||
const getPokemonSprite = () => {
|
||||
const sprite = this.scene.addPokemonSprite(this.pokemon, this.pokemon.x + this.pokemon.getSprite().x, this.pokemon.y + this.pokemon.getSprite().y, "pkmn__sub");
|
||||
sprite.setOrigin(0.5, 1);
|
||||
sprite.play(this.pokemon.getBattleSpriteKey()).stop();
|
||||
const spriteKey = this.pokemon.getBattleSpriteKey();
|
||||
try {
|
||||
sprite.play(spriteKey).stop();
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
sprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) });
|
||||
[ "spriteColors", "fusionSpriteColors" ].map(k => {
|
||||
if (this.pokemon.summonData?.speciesForm) {
|
||||
@ -81,7 +86,12 @@ export class QuietFormChangePhase extends BattlePhase {
|
||||
this.pokemon.setVisible(false);
|
||||
this.pokemon.changeForm(this.formChange).then(() => {
|
||||
pokemonFormTintSprite.setScale(0.01);
|
||||
pokemonFormTintSprite.play(this.pokemon.getBattleSpriteKey()).stop();
|
||||
const spriteKey = this.pokemon.getBattleSpriteKey();
|
||||
try {
|
||||
pokemonFormTintSprite.play(spriteKey).stop();
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
pokemonFormTintSprite.setVisible(true);
|
||||
this.scene.tweens.add({
|
||||
targets: pokemonTintSprite,
|
||||
|
@ -3,6 +3,7 @@ import PartyUiHandler, { PartyOption, PartyUiMode } from "#app/ui/party-ui-handl
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { BattlePhase } from "./battle-phase";
|
||||
import { PostSummonPhase } from "./post-summon-phase";
|
||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||
|
||||
/**
|
||||
@ -63,6 +64,9 @@ export class SwitchPhase extends BattlePhase {
|
||||
|
||||
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
|
||||
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||
// Remove any pre-existing PostSummonPhase under the same field index.
|
||||
// Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave.
|
||||
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
||||
const switchType = (option === PartyOption.PASS_BATON) ? SwitchType.BATON_PASS : this.switchType;
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, fieldIndex, slotIndex, this.doReturn));
|
||||
}
|
||||
|
@ -23,22 +23,24 @@ export class TurnEndPhase extends FieldPhase {
|
||||
this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn));
|
||||
|
||||
const handlePokemon = (pokemon: Pokemon) => {
|
||||
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
|
||||
if (!pokemon.switchOutStatus) {
|
||||
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
|
||||
|
||||
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
|
||||
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
|
||||
|
||||
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
|
||||
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(),
|
||||
Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true));
|
||||
if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && pokemon.isGrounded()) {
|
||||
this.scene.unshiftPhase(new PokemonHealPhase(this.scene, pokemon.getBattlerIndex(),
|
||||
Math.max(pokemon.getMaxHp() >> 4, 1), i18next.t("battle:turnEndHpRestore", { pokemonName: getPokemonNameWithAffix(pokemon) }), true));
|
||||
}
|
||||
|
||||
if (!pokemon.isPlayer()) {
|
||||
this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon);
|
||||
this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
||||
}
|
||||
|
||||
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
|
||||
}
|
||||
|
||||
if (!pokemon.isPlayer()) {
|
||||
this.scene.applyModifiers(EnemyTurnHealModifier, false, pokemon);
|
||||
this.scene.applyModifier(EnemyStatusEffectHealChanceModifier, false, pokemon);
|
||||
}
|
||||
|
||||
applyPostTurnAbAttrs(PostTurnAbAttr, pokemon);
|
||||
|
||||
this.scene.applyModifiers(TurnStatusEffectModifier, pokemon.isPlayer(), pokemon);
|
||||
|
||||
this.scene.applyModifiers(TurnHeldItemTransferModifier, pokemon.isPlayer(), pokemon);
|
||||
|
@ -51,7 +51,7 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
||||
};
|
||||
|
||||
this.executeForAll((pokemon: Pokemon) => {
|
||||
const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length;
|
||||
const immune = !pokemon || !!pokemon.getTypes(true, true).filter(t => this.weather?.isTypeDamageImmune(t)).length || pokemon.switchOutStatus;
|
||||
if (!immune) {
|
||||
inflictDamage(pokemon);
|
||||
}
|
||||
@ -59,8 +59,12 @@ export class WeatherEffectPhase extends CommonAnimPhase {
|
||||
}
|
||||
}
|
||||
|
||||
this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType)!, null, () => { // TODO: is this bang correct?
|
||||
this.executeForAll((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather));
|
||||
this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType) ?? "", null, () => {
|
||||
this.executeForAll((pokemon: Pokemon) => {
|
||||
if (!pokemon.switchOutStatus) {
|
||||
applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather);
|
||||
}
|
||||
});
|
||||
|
||||
super.start();
|
||||
});
|
||||
|
@ -171,7 +171,7 @@ export default class PokemonData {
|
||||
playerPokemon.nickname = this.nickname;
|
||||
}
|
||||
})
|
||||
: scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, this);
|
||||
: scene.addEnemyPokemon(species, this.level, battleType === BattleType.TRAINER ? !double || !(partyMemberIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER : TrainerSlot.NONE, this.boss, false, this);
|
||||
if (this.summonData) {
|
||||
ret.primeSummonData(this.summonData);
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { allAbilities } from "#app/data/ability";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
|
||||
|
||||
describe("Abilities - Arena Trap", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -55,4 +56,39 @@ describe("Abilities - Arena Trap", () => {
|
||||
|
||||
expect(game.scene.getEnemyField().length).toBe(2);
|
||||
});
|
||||
|
||||
/**
|
||||
* This checks if the Player Pokemon is able to switch out/run away after the Enemy Pokemon with {@linkcode Abilities.ARENA_TRAP}
|
||||
* is forcefully moved out of the field from moves such as Roar {@linkcode Moves.ROAR}
|
||||
*
|
||||
* Note: It should be able to switch out/run away
|
||||
*/
|
||||
it("should lift if pokemon with this ability leaves the field", async () => {
|
||||
game.override
|
||||
.battleType("double")
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.moveset([ Moves.ROAR, Moves.SPLASH ])
|
||||
.ability(Abilities.BALL_FETCH);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP, Species.SUDOWOODO, Species.LUNATONE ]);
|
||||
|
||||
const [ enemy1, enemy2 ] = game.scene.getEnemyField();
|
||||
const [ player1, player2 ] = game.scene.getPlayerField();
|
||||
|
||||
vi.spyOn(enemy1, "getAbility").mockReturnValue(allAbilities[Abilities.ARENA_TRAP]);
|
||||
|
||||
game.move.select(Moves.ROAR);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
|
||||
// This runs the fist command phase where the moves are selected
|
||||
await game.toNextTurn();
|
||||
// During the next command phase the player pokemons should not be trapped anymore
|
||||
game.move.select(Moves.SPLASH);
|
||||
game.move.select(Moves.SPLASH, 1);
|
||||
await game.toNextTurn();
|
||||
|
||||
expect(player1.isTrapped()).toBe(false);
|
||||
expect(player2.isTrapped()).toBe(false);
|
||||
expect(enemy1.isOnField()).toBe(false);
|
||||
expect(enemy2.isOnField()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
@ -1,15 +1,12 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import * as Utils from "#app/utils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { FlinchAttr } from "#app/data/move";
|
||||
|
||||
describe("Abilities - Serene Grace", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -27,66 +24,26 @@ describe("Abilities - Serene Grace", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const movesToUse = [ Moves.AIR_SLASH, Moves.TACKLE ];
|
||||
game.override.battleType("single");
|
||||
game.override.enemySpecies(Species.ONIX);
|
||||
game.override.startingLevel(100);
|
||||
game.override.moveset(movesToUse);
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.ability(Abilities.SERENE_GRACE)
|
||||
.moveset([ Moves.AIR_SLASH, Moves.TACKLE ])
|
||||
.enemyLevel(10)
|
||||
.enemyMoveset([ Moves.SPLASH ]);
|
||||
});
|
||||
|
||||
it("Move chance without Serene Grace", async () => {
|
||||
const moveToUse = Moves.AIR_SLASH;
|
||||
await game.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
it("Serene Grace should double the secondary effect chance of a move", async () => {
|
||||
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||
|
||||
const airSlashMove = allMoves[Moves.AIR_SLASH];
|
||||
const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0];
|
||||
vi.spyOn(airSlashFlinchAttr, "getMoveChance");
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
||||
expect(game.scene.getPlayerParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.move.select(moveToUse);
|
||||
|
||||
game.move.select(Moves.AIR_SLASH);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
await game.move.forceHit();
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
// Check chance of Air Slash without Serene Grace
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
console.log(move.chance + " Their ability is " + phase.getUserPokemon()!.getAbility().name);
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
||||
expect(chance.value).toBe(30);
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Move chance with Serene Grace", async () => {
|
||||
const moveToUse = Moves.AIR_SLASH;
|
||||
game.override.ability(Abilities.SERENE_GRACE);
|
||||
await game.startBattle([
|
||||
Species.TOGEKISS
|
||||
]);
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
||||
expect(game.scene.getPlayerParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.move.select(moveToUse);
|
||||
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
// Check chance of Air Slash with Serene Grace
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
||||
expect(chance.value).toBe(60);
|
||||
|
||||
}, 20000);
|
||||
|
||||
//TODO King's Rock Interaction Unit Test
|
||||
expect(airSlashFlinchAttr.getMoveChance).toHaveLastReturnedWith(60);
|
||||
});
|
||||
});
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability";
|
||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||
import { NumberHolder } from "#app/utils";
|
||||
import { Type } from "#app/enums/type";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { allMoves } from "#app/data/move";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { allMoves, FlinchAttr } from "#app/data/move";
|
||||
|
||||
describe("Abilities - Sheer Force", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
@ -27,143 +25,91 @@ describe("Abilities - Sheer Force", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const movesToUse = [ Moves.AIR_SLASH, Moves.BIND, Moves.CRUSH_CLAW, Moves.TACKLE ];
|
||||
game.override.battleType("single");
|
||||
game.override.enemySpecies(Species.ONIX);
|
||||
game.override.startingLevel(100);
|
||||
game.override.moveset(movesToUse);
|
||||
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.ability(Abilities.SHEER_FORCE)
|
||||
.enemySpecies(Species.ONIX)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset([ Moves.SPLASH ])
|
||||
.disableCrits();
|
||||
});
|
||||
|
||||
it("Sheer Force", async () => {
|
||||
const moveToUse = Moves.AIR_SLASH;
|
||||
game.override.ability(Abilities.SHEER_FORCE);
|
||||
const SHEER_FORCE_MULT = 5461 / 4096;
|
||||
|
||||
it("Sheer Force should boost the power of the move but disable secondary effects", async () => {
|
||||
game.override.moveset([ Moves.AIR_SLASH ]);
|
||||
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||
|
||||
const airSlashMove = allMoves[Moves.AIR_SLASH];
|
||||
vi.spyOn(airSlashMove, "calculateBattlePower");
|
||||
const airSlashFlinchAttr = airSlashMove.getAttrs(FlinchAttr)[0];
|
||||
vi.spyOn(airSlashFlinchAttr, "getMoveChance");
|
||||
|
||||
game.move.select(Moves.AIR_SLASH);
|
||||
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.move.forceHit();
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(airSlashMove.calculateBattlePower).toHaveLastReturnedWith(airSlashMove.power * SHEER_FORCE_MULT);
|
||||
expect(airSlashFlinchAttr.getMoveChance).toHaveLastReturnedWith(0);
|
||||
});
|
||||
|
||||
it("Sheer Force does not affect the base damage or secondary effects of binding moves", async () => {
|
||||
game.override.moveset([ Moves.BIND ]);
|
||||
await game.classicMode.startBattle([ Species.SHUCKLE ]);
|
||||
|
||||
const bindMove = allMoves[Moves.BIND];
|
||||
vi.spyOn(bindMove, "calculateBattlePower");
|
||||
|
||||
game.move.select(Moves.BIND);
|
||||
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.move.forceHit();
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(bindMove.calculateBattlePower).toHaveLastReturnedWith(bindMove.power);
|
||||
}, 20000);
|
||||
|
||||
it("Sheer Force does not boost the base damage of moves with no secondary effect", async () => {
|
||||
game.override.moveset([ Moves.TACKLE ]);
|
||||
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
||||
|
||||
game.scene.getEnemyPokemon()!.stats[Stat.SPDEF] = 10000;
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
||||
|
||||
game.move.select(moveToUse);
|
||||
const tackleMove = allMoves[Moves.TACKLE];
|
||||
vi.spyOn(tackleMove, "calculateBattlePower");
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
await game.move.forceHit();
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
expect(tackleMove.calculateBattlePower).toHaveLastReturnedWith(tackleMove.power);
|
||||
});
|
||||
|
||||
//Verify the move is boosted and has no chance of secondary effects
|
||||
const power = new NumberHolder(move.power);
|
||||
const chance = new NumberHolder(move.chance);
|
||||
it("Sheer Force can disable the on-hit activation of specific abilities", async () => {
|
||||
game.override
|
||||
.moveset([ Moves.HEADBUTT ])
|
||||
.enemySpecies(Species.SQUIRTLE)
|
||||
.enemyLevel(10)
|
||||
.enemyAbility(Abilities.COLOR_CHANGE);
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power);
|
||||
|
||||
expect(chance.value).toBe(0);
|
||||
expect(power.value).toBe(move.power * 5461 / 4096);
|
||||
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Sheer Force with exceptions including binding moves", async () => {
|
||||
const moveToUse = Moves.BIND;
|
||||
game.override.ability(Abilities.SHEER_FORCE);
|
||||
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const headbuttMove = allMoves[Moves.HEADBUTT];
|
||||
vi.spyOn(headbuttMove, "calculateBattlePower");
|
||||
const headbuttFlinchAttr = headbuttMove.getAttrs(FlinchAttr)[0];
|
||||
vi.spyOn(headbuttFlinchAttr, "getMoveChance");
|
||||
|
||||
|
||||
game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000;
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
||||
|
||||
game.move.select(moveToUse);
|
||||
game.move.select(Moves.HEADBUTT);
|
||||
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
await game.move.forceHit();
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.BIND);
|
||||
|
||||
//Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1
|
||||
const power = new NumberHolder(move.power);
|
||||
const chance = new NumberHolder(move.chance);
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power);
|
||||
|
||||
expect(chance.value).toBe(-1);
|
||||
expect(power.value).toBe(move.power);
|
||||
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Sheer Force with moves with no secondary effect", async () => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
game.override.ability(Abilities.SHEER_FORCE);
|
||||
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
||||
|
||||
|
||||
game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000;
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
||||
|
||||
game.move.select(moveToUse);
|
||||
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.TACKLE);
|
||||
|
||||
//Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1
|
||||
const power = new NumberHolder(move.power);
|
||||
const chance = new NumberHolder(move.chance);
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon()!, null, false, chance, move, phase.getFirstTarget(), false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon()!, phase.getFirstTarget()!, move, false, power);
|
||||
|
||||
expect(chance.value).toBe(-1);
|
||||
expect(power.value).toBe(move.power);
|
||||
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Sheer Force Disabling Specific Abilities", async () => {
|
||||
const moveToUse = Moves.CRUSH_CLAW;
|
||||
game.override.enemyAbility(Abilities.COLOR_CHANGE);
|
||||
game.override.startingHeldItems([{ name: "KINGS_ROCK", count: 1 }]);
|
||||
game.override.ability(Abilities.SHEER_FORCE);
|
||||
await game.startBattle([ Species.PIDGEOT ]);
|
||||
|
||||
|
||||
game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000;
|
||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
||||
|
||||
game.move.select(moveToUse);
|
||||
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.CRUSH_CLAW);
|
||||
|
||||
//Disable color change due to being hit by Sheer Force
|
||||
const power = new NumberHolder(move.power);
|
||||
const chance = new NumberHolder(move.chance);
|
||||
const user = phase.getUserPokemon()!;
|
||||
const target = phase.getFirstTarget()!;
|
||||
const opponentType = target.getTypes()[0];
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, chance, move, target, false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, false, power);
|
||||
applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, target.apply(user, move));
|
||||
|
||||
expect(chance.value).toBe(0);
|
||||
expect(power.value).toBe(move.power * 5461 / 4096);
|
||||
expect(target.getTypes().length).toBe(2);
|
||||
expect(target.getTypes()[0]).toBe(opponentType);
|
||||
|
||||
}, 20000);
|
||||
expect(enemyPokemon?.getTypes()[0]).toBe(Type.WATER);
|
||||
expect(headbuttMove.calculateBattlePower).toHaveLastReturnedWith(headbuttMove.power * SHEER_FORCE_MULT);
|
||||
expect(headbuttFlinchAttr.getMoveChance).toHaveLastReturnedWith(0);
|
||||
});
|
||||
|
||||
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
|
||||
const moveToUse = Moves.CRUNCH;
|
||||
@ -191,5 +137,19 @@ describe("Abilities - Sheer Force", () => {
|
||||
expect(onix.getTypes()).toStrictEqual(expectedTypes);
|
||||
});
|
||||
|
||||
//TODO King's Rock Interaction Unit Test
|
||||
it("Sheer Force should disable Meloetta's transformation from Relic Song", async () => {
|
||||
game.override
|
||||
.ability(Abilities.SHEER_FORCE)
|
||||
.moveset([ Moves.RELIC_SONG ])
|
||||
.enemyMoveset([ Moves.SPLASH ])
|
||||
.enemyLevel(100);
|
||||
await game.classicMode.startBattle([ Species.MELOETTA ]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon();
|
||||
const formKeyStart = playerPokemon?.getFormKey();
|
||||
|
||||
game.move.select(Moves.RELIC_SONG);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
expect(formKeyStart).toBe(playerPokemon?.getFormKey());
|
||||
});
|
||||
});
|
||||
|
@ -632,4 +632,34 @@ describe("Abilities - Wimp Out", () => {
|
||||
const hasFled = enemyPokemon.switchOutStatus;
|
||||
expect(isVisible && !hasFled).toBe(true);
|
||||
});
|
||||
it("wimp out will not skip battles when triggered in a double battle", async () => {
|
||||
const wave = 2;
|
||||
game.override
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemySpecies(Species.WIMPOD)
|
||||
.enemyAbility(Abilities.WIMP_OUT)
|
||||
.moveset([ Moves.MATCHA_GOTCHA, Moves.FALSE_SWIPE ])
|
||||
.startingLevel(50)
|
||||
.enemyLevel(1)
|
||||
.battleType("double")
|
||||
.startingWave(wave);
|
||||
await game.classicMode.startBattle([
|
||||
Species.RAICHU,
|
||||
Species.PIKACHU
|
||||
]);
|
||||
const [ wimpod0, wimpod1 ] = game.scene.getEnemyField();
|
||||
|
||||
game.move.select(Moves.FALSE_SWIPE, 0, BattlerIndex.ENEMY);
|
||||
game.move.select(Moves.MATCHA_GOTCHA, 1);
|
||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(wimpod0.hp).toBeGreaterThan(0);
|
||||
expect(wimpod0.switchOutStatus).toBe(true);
|
||||
expect(wimpod0.isFainted()).toBe(false);
|
||||
expect(wimpod1.isFainted()).toBe(true);
|
||||
|
||||
await game.toNextWave();
|
||||
expect(game.scene.currentBattle.waveIndex).toBe(wave + 1);
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import * as Messages from "#app/messages";
|
||||
import { TerastallizeModifier } from "#app/modifier/modifier";
|
||||
import { TerastallizeModifier, overrideHeldItems } from "#app/modifier/modifier";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
@ -15,15 +15,17 @@ function testMoveEffectiveness(game: GameManager, move: Moves, targetSpecies: Sp
|
||||
expected: number, targetAbility: Abilities = Abilities.BALL_FETCH, teraType?: Type): void {
|
||||
// Suppress getPokemonNameWithAffix because it calls on a null battle spec
|
||||
vi.spyOn(Messages, "getPokemonNameWithAffix").mockReturnValue("");
|
||||
game.override.enemyAbility(targetAbility);
|
||||
|
||||
if (teraType !== undefined) {
|
||||
game.override.enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]);
|
||||
}
|
||||
game.override
|
||||
.enemyAbility(targetAbility)
|
||||
.enemyHeldItems([{ name:"TERA_SHARD", type: teraType }]);
|
||||
|
||||
const user = game.scene.addPlayerPokemon(getPokemonSpecies(Species.SNORLAX), 5);
|
||||
const target = game.scene.addEnemyPokemon(getPokemonSpecies(targetSpecies), 5, TrainerSlot.NONE);
|
||||
|
||||
if (teraType !== undefined) {
|
||||
overrideHeldItems(game.scene, target, false);
|
||||
}
|
||||
|
||||
expect(target.getMoveEffectiveness(user, allMoves[move])).toBe(expected);
|
||||
user.destroy();
|
||||
target.destroy();
|
||||
|
@ -266,6 +266,9 @@ describe("Clowning Around - Mystery Encounter", () => {
|
||||
// 5 Lucky Egg on lead (ultra)
|
||||
itemType = generateModifierType(scene, modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType);
|
||||
// 3 Soothe Bell on lead (great tier, but counted as ultra by this ME)
|
||||
itemType = generateModifierType(scene, modifierTypes.SOOTHE_BELL) as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getPlayerParty()[0], 3, itemType);
|
||||
// 5 Soul Dew on lead (rogue)
|
||||
itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType;
|
||||
await addItemToPokemon(scene, scene.getPlayerParty()[0], 5, itemType);
|
||||
@ -286,7 +289,7 @@ describe("Clowning Around - Mystery Encounter", () => {
|
||||
const rogueCountAfter = leadItemsAfter
|
||||
.filter(m => m.type.tier === ModifierTier.ROGUE)
|
||||
.reduce((a, b) => a + b.stackCount, 0);
|
||||
expect(ultraCountAfter).toBe(10);
|
||||
expect(ultraCountAfter).toBe(13);
|
||||
expect(rogueCountAfter).toBe(7);
|
||||
|
||||
const secondItemsAfter = scene.getPlayerParty()[1].getHeldItems();
|
||||
|
@ -18,6 +18,7 @@ import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
import { ModifierTier } from "#app/modifier/modifier-tier";
|
||||
import * as Utils from "#app/utils";
|
||||
|
||||
const namespace = "mysteryEncounters/globalTradeSystem";
|
||||
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||
@ -176,6 +177,23 @@ describe("Global Trade System - Mystery Encounter", () => {
|
||||
expect(defaultParty.includes(speciesAfter!)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("Should roll for shiny twice, with random variant and associated luck", async () => {
|
||||
// This ensures that the first shiny roll gets ignored, to test the ME rerolling for shiny
|
||||
game.override.enemyShiny(false);
|
||||
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.GLOBAL_TRADE_SYSTEM, defaultParty);
|
||||
|
||||
vi.spyOn(Utils, "randSeedInt").mockReturnValue(1); // force shiny on reroll
|
||||
|
||||
await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1 });
|
||||
|
||||
const receivedPokemon = scene.getPlayerParty().at(-1)!;
|
||||
|
||||
expect(receivedPokemon.shiny).toBeTruthy();
|
||||
expect(receivedPokemon.variant).toBeDefined();
|
||||
expect(receivedPokemon.luck).toBe(receivedPokemon.variant + 1);
|
||||
});
|
||||
|
||||
it("should leave encounter without battle", async () => {
|
||||
const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle");
|
||||
|
||||
|
@ -18,6 +18,7 @@ import { TheExpertPokemonBreederEncounter } from "#app/data/mystery-encounters/e
|
||||
import { TrainerType } from "#enums/trainer-type";
|
||||
import { EggTier } from "#enums/egg-type";
|
||||
import { PostMysteryEncounterPhase } from "#app/phases/mystery-encounter-phases";
|
||||
import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters";
|
||||
|
||||
const namespace = "mysteryEncounters/theExpertPokemonBreeder";
|
||||
const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ];
|
||||
@ -182,7 +183,10 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
||||
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||
|
||||
const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon1.friendship;
|
||||
expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle
|
||||
// 20 from ME + extra from winning battle (that extra is not accurate to what happens in game.
|
||||
// The Pokemon normally gets FRIENDSHIP_GAIN_FROM_BATTLE 3 times, once for each defeated Pokemon
|
||||
// but due to how skipBattleRunMysteryEncounterRewardsPhase is implemented, it only receives it once)
|
||||
expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE);
|
||||
});
|
||||
});
|
||||
|
||||
@ -261,7 +265,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
||||
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||
|
||||
const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon2.friendship;
|
||||
expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle
|
||||
expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 from ME + extra for friendship gained from winning battle
|
||||
});
|
||||
});
|
||||
|
||||
@ -340,7 +344,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
|
||||
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
|
||||
|
||||
const friendshipAfter = scene.currentBattle.mysteryEncounter!.misc.pokemon3.friendship;
|
||||
expect(friendshipAfter).toBe(friendshipBefore + 20 + 2); // +2 extra for friendship gained from winning battle
|
||||
expect(friendshipAfter).toBe(friendshipBefore + 20 + FRIENDSHIP_GAIN_FROM_BATTLE); // 20 + extra for friendship gained from winning battle
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -123,7 +123,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("Should update the player's money properly", async () => {
|
||||
it("should update the player's money properly", async () => {
|
||||
const initialMoney = 20000;
|
||||
scene.money = initialMoney;
|
||||
const updateMoneySpy = vi.spyOn(EncounterPhaseUtils, "updatePlayerMoney");
|
||||
@ -137,7 +137,7 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||
expect(scene.money).toBe(initialMoney - price);
|
||||
});
|
||||
|
||||
it("Should add the Pokemon to the party", async () => {
|
||||
it("should add the Pokemon to the party", async () => {
|
||||
scene.money = 20000;
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
||||
|
||||
@ -153,6 +153,18 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
|
||||
expect(newlyPurchasedPokemon!.moveset.length > 0).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should give the purchased Pokemon its HA or make it shiny", async () => {
|
||||
scene.money = 20000;
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
||||
await runMysteryEncounterToEnd(game, 1);
|
||||
|
||||
const newlyPurchasedPokemon = scene.getPlayerParty()[scene.getPlayerParty().length - 1];
|
||||
const isshiny = newlyPurchasedPokemon.shiny;
|
||||
const hasHA = newlyPurchasedPokemon.abilityIndex === 2;
|
||||
expect(isshiny || hasHA).toBeTruthy();
|
||||
expect(isshiny && hasHA).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should be disabled if player does not have enough money", async () => {
|
||||
scene.money = 0;
|
||||
await game.runToMysteryEncounter(MysteryEncounterType.THE_POKEMON_SALESMAN, defaultParty);
|
||||
|
@ -109,6 +109,7 @@ describe("The Strong Stuff - Mystery Encounter", () => {
|
||||
species: getPokemonSpecies(Species.SHUCKLE),
|
||||
isBoss: true,
|
||||
bossSegments: 5,
|
||||
shiny: false,
|
||||
customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }),
|
||||
nature: Nature.BOLD,
|
||||
moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ],
|
||||
|
@ -92,6 +92,7 @@ describe("Trash to Treasure - Mystery Encounter", () => {
|
||||
{
|
||||
species: getPokemonSpecies(Species.GARBODOR),
|
||||
isBoss: true,
|
||||
shiny: false,
|
||||
formIndex: 1,
|
||||
bossSegmentModifier: 1,
|
||||
moveSet: [ Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH ],
|
||||
|
@ -118,6 +118,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
this.runResultContainer = this.scene.add.container(0, 24);
|
||||
const runResultWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 65);
|
||||
runResultWindow.setOrigin(0, 0);
|
||||
runResultWindow.setName("Run_Result_Window");
|
||||
this.runResultContainer.add(runResultWindow);
|
||||
if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) {
|
||||
this.parseRunResult();
|
||||
@ -254,8 +255,6 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
* Mystery Encounters contain sprites associated with MEs + the title of the specific ME.
|
||||
*/
|
||||
private parseRunStatus() {
|
||||
const runStatusText = addTextObject(this.scene, 6, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex} - ${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize : "65px", lineSpacing: 0.1 });
|
||||
|
||||
const enemyContainer = this.scene.add.container(0, 0);
|
||||
this.runResultContainer.add(enemyContainer);
|
||||
if (this.runInfo.battleType === BattleType.WILD) {
|
||||
@ -271,7 +270,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
const pokeball = this.scene.add.sprite(0, 0, "pb");
|
||||
pokeball.setFrame(getPokeballAtlasKey(p.pokeball));
|
||||
pokeball.setScale(0.5);
|
||||
pokeball.setPosition(52 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25);
|
||||
pokeball.setPosition(58 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25);
|
||||
enemyContainer.add(pokeball);
|
||||
});
|
||||
const trainerObj = this.runInfo.trainer.toTrainer(this.scene);
|
||||
@ -286,7 +285,7 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
const descContainer = this.scene.add.container(0, 0);
|
||||
const textBox = addTextObject(this.scene, 0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }});
|
||||
descContainer.add(textBox);
|
||||
descContainer.setPosition(52, 29);
|
||||
descContainer.setPosition(55, 32);
|
||||
this.runResultContainer.add(descContainer);
|
||||
} else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) {
|
||||
const encounterExclaim = this.scene.add.sprite(0, 0, "encounter_exclaim");
|
||||
@ -303,7 +302,17 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
this.runResultContainer.add([ encounterExclaim, subSprite, descContainer ]);
|
||||
}
|
||||
|
||||
this.runResultContainer.add(runStatusText);
|
||||
const runResultWindow = this.runResultContainer.getByName("Run_Result_Window") as Phaser.GameObjects.Image;
|
||||
const windowCenterX = runResultWindow.getTopCenter().x;
|
||||
const windowBottomY = runResultWindow.getBottomCenter().y;
|
||||
|
||||
const runStatusText = addTextObject(this.scene, windowCenterX, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex}`, TextStyle.WINDOW, { fontSize : "60px", lineSpacing: 0.1 });
|
||||
runStatusText.setOrigin(0.5, 0);
|
||||
|
||||
const currentBiomeText = addTextObject(this.scene, windowCenterX, windowBottomY - 5, `${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize: "60px" });
|
||||
currentBiomeText.setOrigin(0.5, 1);
|
||||
|
||||
this.runResultContainer.add([ runStatusText, currentBiomeText ]);
|
||||
this.runContainer.add(this.runResultContainer);
|
||||
}
|
||||
|
||||
@ -387,12 +396,12 @@ export default class RunInfoUiHandler extends UiHandler {
|
||||
tObjSprite.setPosition(-9, -3);
|
||||
tObjPartnerSprite.setScale(0.55);
|
||||
doubleContainer.add([ tObjSprite, tObjPartnerSprite ]);
|
||||
doubleContainer.setPosition(28, 40);
|
||||
doubleContainer.setPosition(28, 34);
|
||||
}
|
||||
enemyContainer.add(doubleContainer);
|
||||
} else {
|
||||
const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.65;
|
||||
const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 32, 36 ];
|
||||
const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.55;
|
||||
const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 30, 32 ];
|
||||
tObjSprite.setScale(scale, scale);
|
||||
tObjSprite.setPosition(position[0], position[1]);
|
||||
enemyContainer.add(tObjSprite);
|
||||
|
@ -39,7 +39,6 @@ import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Button } from "#enums/buttons";
|
||||
import { EggSourceType } from "#enums/egg-source-types";
|
||||
import AwaitableUiHandler from "#app/ui/awaitable-ui-handler";
|
||||
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
|
||||
import { StarterContainer } from "#app/ui/starter-container";
|
||||
import { DropDownColumn, FilterBar } from "#app/ui/filter-bar";
|
||||
@ -1062,15 +1061,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) {
|
||||
showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer, moveToTop?: boolean) {
|
||||
super.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
|
||||
|
||||
if (text?.indexOf("\n") === -1) {
|
||||
this.starterSelectMessageBox.setSize(318, 28);
|
||||
this.message.setY(-22);
|
||||
const singleLine = text?.indexOf("\n") === -1;
|
||||
|
||||
this.starterSelectMessageBox.setSize(318, singleLine ? 28 : 42);
|
||||
|
||||
if (moveToTop) {
|
||||
this.starterSelectMessageBox.setOrigin(0, 0);
|
||||
this.starterSelectMessageBoxContainer.setY(0);
|
||||
this.message.setY(4);
|
||||
} else {
|
||||
this.starterSelectMessageBox.setSize(318, 42);
|
||||
this.message.setY(-37);
|
||||
this.starterSelectMessageBoxContainer.setY(this.scene.game.canvas.height / 6);
|
||||
this.starterSelectMessageBox.setOrigin(0, 1);
|
||||
this.message.setY(singleLine ? -22 : -37);
|
||||
}
|
||||
|
||||
this.starterSelectMessageBoxContainer.setVisible(!!text?.length);
|
||||
@ -1804,8 +1809,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
options.push({
|
||||
label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`,
|
||||
handler: () => {
|
||||
if ((this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE)
|
||||
&& (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) {
|
||||
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) {
|
||||
if (this.scene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
|
||||
// Egg list full, show error message at the top of the screen and abort
|
||||
this.showText(i18next.t("egg:tooManyEggs"), undefined, () => this.showText("", 0, () => this.tutorialActive = false), 2000, false, undefined, true);
|
||||
return false;
|
||||
}
|
||||
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
|
||||
starterData.candyCount -= sameSpeciesEggCost;
|
||||
}
|
||||
@ -3565,9 +3574,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
}, cancel, null, null, 19);
|
||||
});
|
||||
} else {
|
||||
const handler = this.scene.ui.getHandler() as AwaitableUiHandler;
|
||||
handler.tutorialActive = true;
|
||||
this.scene.ui.showText(i18next.t("starterSelectUiHandler:invalidParty"), null, () => this.scene.ui.showText("", 0, () => handler.tutorialActive = false), null, true);
|
||||
this.tutorialActive = true;
|
||||
this.showText(i18next.t("starterSelectUiHandler:invalidParty"), undefined, () => this.showText("", 0, () => this.tutorialActive = false), undefined, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -321,8 +321,12 @@ export default class SummaryUiHandler extends UiHandler {
|
||||
this.numberText.setText(Utils.padInt(this.pokemon.species.speciesId, 4));
|
||||
this.numberText.setColor(this.getTextColor(!this.pokemon.isShiny() ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD));
|
||||
this.numberText.setShadowColor(this.getTextColor(!this.pokemon.isShiny() ? TextStyle.SUMMARY : TextStyle.SUMMARY_GOLD, true));
|
||||
|
||||
this.pokemonSprite.play(this.pokemon.getSpriteKey(true));
|
||||
const spriteKey = this.pokemon.getSpriteKey(true);
|
||||
try {
|
||||
this.pokemonSprite.play(spriteKey);
|
||||
} catch (err: unknown) {
|
||||
console.error(`Failed to play animation for ${spriteKey}`, err);
|
||||
}
|
||||
this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType()));
|
||||
this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
|
||||
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
|
||||
|
Loading…
Reference in New Issue
Block a user