Merge branch 'beta' into filter-test-gh-action-trigger
BIN
public/images/events/spr25event-de.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
public/images/events/spr25event-en.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
public/images/events/spr25event-es-ES.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
public/images/events/spr25event-es-MX.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
public/images/events/spr25event-fr.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
public/images/events/spr25event-it.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
public/images/events/spr25event-ja.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
public/images/events/spr25event-ko.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
public/images/events/spr25event-pt-BR.png
Normal file
After Width: | Height: | Size: 30 KiB |
@ -14,3 +14,6 @@ export const MAX_INT_ATTR_VALUE = 0x80000000;
|
||||
export const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const;
|
||||
/** The min and max waves for mystery encounters to spawn in challenge mode */
|
||||
export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const;
|
||||
|
||||
/** The raw percentage power boost for type boost items*/
|
||||
export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20;
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
SelfStatusMove,
|
||||
VariablePowerAttr,
|
||||
applyMoveAttrs,
|
||||
VariableMoveTypeAttr,
|
||||
RandomMovesetMoveAttr,
|
||||
RandomMoveAttr,
|
||||
NaturePowerAttr,
|
||||
@ -73,6 +72,7 @@ import type { BattlerIndex } from "#app/battle";
|
||||
import type Move from "#app/data/moves/move";
|
||||
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
|
||||
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
|
||||
import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves";
|
||||
|
||||
export class BlockRecoilDamageAttr extends AbAttr {
|
||||
constructor() {
|
||||
@ -1240,12 +1240,39 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
super(false);
|
||||
}
|
||||
|
||||
override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean {
|
||||
return (this.condition && this.condition(pokemon, defender, move)) ?? false;
|
||||
/**
|
||||
* Determine if the move type change attribute can be applied
|
||||
*
|
||||
* Can be applied if:
|
||||
* - The ability's condition is met, e.g. pixilate only boosts normal moves,
|
||||
* - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode Moves.MULTI_ATTACK}
|
||||
* - The user is not terastallized and using tera blast
|
||||
* - The user is not a terastallized terapagos with tera stellar using tera starstorm
|
||||
* @param pokemon - The pokemon that has the move type changing ability and is using the attacking move
|
||||
* @param _passive - Unused
|
||||
* @param _simulated - Unused
|
||||
* @param _defender - The pokemon being attacked (unused)
|
||||
* @param move - The move being used
|
||||
* @param _args - args[0] holds the type that the move is changed to, args[1] holds the multiplier
|
||||
* @returns whether the move type change attribute can be applied
|
||||
*/
|
||||
override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean {
|
||||
return (!this.condition || this.condition(pokemon, _defender, move)) &&
|
||||
!noAbilityTypeOverrideMoves.has(move.id) &&
|
||||
(!pokemon.isTerastallized ||
|
||||
(move.id !== Moves.TERA_BLAST &&
|
||||
(move.id !== Moves.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(Species.TERAPAGOS))));
|
||||
}
|
||||
|
||||
// TODO: Decouple this into two attributes (type change / power boost)
|
||||
override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void {
|
||||
/**
|
||||
* @param pokemon - The pokemon that has the move type changing ability and is using the attacking move
|
||||
* @param passive - Unused
|
||||
* @param simulated - Unused
|
||||
* @param defender - The pokemon being attacked (unused)
|
||||
* @param move - The move being used
|
||||
* @param args - args[0] holds the type that the move is changed to, args[1] holds the multiplier
|
||||
*/
|
||||
override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: [NumberHolder?, NumberHolder?, ...any]): void {
|
||||
if (args[0] && args[0] instanceof NumberHolder) {
|
||||
args[0].value = this.newType;
|
||||
}
|
||||
@ -6629,9 +6656,7 @@ export function initAbilities() {
|
||||
.conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, StatMultiplierAbAttr, Stat.SPD, 2)
|
||||
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.SPD, 1.5),
|
||||
new Ability(Abilities.NORMALIZE, 4)
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.NORMAL, 1.2, (user, target, move) => {
|
||||
return ![ Moves.MULTI_ATTACK, Moves.REVELATION_DANCE, Moves.TERRAIN_PULSE, Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST ].includes(move.id);
|
||||
}),
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.NORMAL, 1.2),
|
||||
new Ability(Abilities.SNIPER, 4)
|
||||
.attr(MultCritAbAttr, 1.5),
|
||||
new Ability(Abilities.MAGIC_GUARD, 4)
|
||||
@ -6896,7 +6921,7 @@ export function initAbilities() {
|
||||
new Ability(Abilities.STRONG_JAW, 6)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5),
|
||||
new Ability(Abilities.REFRIGERATE, 6)
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.ICE, 1.2, (user, target, move) => move.type === PokemonType.NORMAL),
|
||||
new Ability(Abilities.SWEET_VEIL, 6)
|
||||
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
||||
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.SLEEP)
|
||||
@ -6920,11 +6945,11 @@ export function initAbilities() {
|
||||
new Ability(Abilities.TOUGH_CLAWS, 6)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3),
|
||||
new Ability(Abilities.PIXILATE, 6)
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.FAIRY, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.FAIRY, 1.2, (user, target, move) => move.type === PokemonType.NORMAL),
|
||||
new Ability(Abilities.GOOEY, 6)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false),
|
||||
new Ability(Abilities.AERILATE, 6)
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (user, target, move) => move.type === PokemonType.NORMAL),
|
||||
new Ability(Abilities.PARENTAL_BOND, 6)
|
||||
.attr(AddSecondStrikeAbAttr, 0.25),
|
||||
new Ability(Abilities.DARK_AURA, 6)
|
||||
@ -7001,7 +7026,7 @@ export function initAbilities() {
|
||||
new Ability(Abilities.TRIAGE, 7)
|
||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
|
||||
new Ability(Abilities.GALVANIZE, 7)
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.ELECTRIC, 1.2, (user, target, move) => move.type === PokemonType.NORMAL && !move.hasAttr(VariableMoveTypeAttr)),
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.ELECTRIC, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL),
|
||||
new Ability(Abilities.SURGE_SURFER, 7)
|
||||
.conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), StatMultiplierAbAttr, Stat.SPD, 2),
|
||||
new Ability(Abilities.SCHOOLING, 7)
|
||||
|
@ -240,3 +240,18 @@ export const invalidMirrorMoveMoves: ReadonlySet<Moves> = new Set([
|
||||
Moves.WATER_SPORT,
|
||||
Moves.WIDE_GUARD,
|
||||
]);
|
||||
|
||||
/** Set of moves that can never have their type overridden by an ability like Pixilate or Normalize
|
||||
*
|
||||
* Excludes tera blast and tera starstorm, as these are only conditionally forbidden
|
||||
*/
|
||||
export const noAbilityTypeOverrideMoves: ReadonlySet<Moves> = new Set([
|
||||
Moves.WEATHER_BALL,
|
||||
Moves.JUDGMENT,
|
||||
Moves.REVELATION_DANCE,
|
||||
Moves.MULTI_ATTACK,
|
||||
Moves.TERRAIN_PULSE,
|
||||
Moves.NATURAL_GIFT,
|
||||
Moves.TECHNO_BLAST,
|
||||
Moves.HIDDEN_POWER,
|
||||
]);
|
||||
|
@ -810,8 +810,9 @@ export default class Move implements Localizable {
|
||||
|
||||
const power = new NumberHolder(this.power);
|
||||
const typeChangeMovePowerMultiplier = new NumberHolder(1);
|
||||
const typeChangeHolder = new NumberHolder(this.type);
|
||||
|
||||
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier);
|
||||
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
|
||||
|
||||
const sourceTeraType = source.getTeraType();
|
||||
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
||||
@ -841,7 +842,7 @@ export default class Move implements Localizable {
|
||||
|
||||
power.value *= typeChangeMovePowerMultiplier.value;
|
||||
|
||||
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === this.type) as TypeBoostTag;
|
||||
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === typeChangeHolder.value) as TypeBoostTag;
|
||||
if (typeBoost) {
|
||||
power.value *= typeBoost.boostValue;
|
||||
}
|
||||
@ -849,8 +850,8 @@ export default class Move implements Localizable {
|
||||
applyMoveAttrs(VariablePowerAttr, source, target, this, power);
|
||||
|
||||
if (!this.hasAttr(TypelessAttr)) {
|
||||
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power);
|
||||
globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power);
|
||||
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power);
|
||||
globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power);
|
||||
}
|
||||
|
||||
if (source.getTag(HelpingHandTag)) {
|
||||
@ -4826,8 +4827,13 @@ export class FormChangeItemTypeAttr extends VariableMoveTypeAttr {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Force move to have its original typing if it changed
|
||||
if (moveType.value === move.type) {
|
||||
return false;
|
||||
}
|
||||
moveType.value = move.type
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class TechnoBlastTypeAttr extends VariableMoveTypeAttr {
|
||||
@ -4977,8 +4983,12 @@ export class WeatherBallTypeAttr extends VariableMoveTypeAttr {
|
||||
moveType.value = PokemonType.ICE;
|
||||
break;
|
||||
default:
|
||||
if (moveType.value === move.type) {
|
||||
return false;
|
||||
}
|
||||
moveType.value = move.type;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -5025,8 +5035,13 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
|
||||
moveType.value = PokemonType.PSYCHIC;
|
||||
break;
|
||||
default:
|
||||
if (moveType.value === move.type) {
|
||||
return false;
|
||||
}
|
||||
// force move to have its original typing if it was changed
|
||||
moveType.value = move.type;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -6130,7 +6145,7 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
const faintedPokemon = globalScene.getEnemyParty().filter((p) => p.isFainted() && !p.isBoss());
|
||||
const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)];
|
||||
const slotIndex = globalScene.getEnemyParty().findIndex((p) => pokemon.id === p.id);
|
||||
pokemon.resetStatus();
|
||||
pokemon.resetStatus(true, false, false, true);
|
||||
pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:revivalBlessing", { pokemonName: getPokemonNameWithAffix(pokemon) }), 0, true);
|
||||
const allyPokemon = user.getAlly();
|
||||
|
@ -106,7 +106,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
* If an event with more than 1 valid event encounter species is active, you have 20% chance to get one of those
|
||||
* If the rolled species has no HA, and there are valid event encounters, you will get one of those
|
||||
* If the rolled species has no HA and there are no valid event encounters, you will get Shiny Magikarp
|
||||
* Mons rolled from the event encounter pool get 2 extra shiny rolls
|
||||
* Mons rolled from the event encounter pool get 3 extra shiny rolls
|
||||
*/
|
||||
if (
|
||||
r === 0 ||
|
||||
@ -120,12 +120,13 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
|
||||
(validEventEncounters.length > 0 && (r <= EVENT_THRESHOLD ||
|
||||
(isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE)))
|
||||
) {
|
||||
// If you roll 20%, give event encounter with 2 extra shiny rolls and its HA, if it has one
|
||||
// If you roll 20%, give event encounter with 3 extra shiny rolls and its HA, if it has one
|
||||
const enc = randSeedItem(validEventEncounters);
|
||||
species = getPokemonSpecies(enc.species);
|
||||
pokemon = new PlayerPokemon(species, 5, species.abilityHidden === Abilities.NONE ? undefined : 2, enc.formIndex);
|
||||
pokemon.trySetShinySeed();
|
||||
pokemon.trySetShinySeed();
|
||||
pokemon.trySetShinySeed();
|
||||
} else {
|
||||
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
|
||||
}
|
||||
|
@ -2591,9 +2591,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
null,
|
||||
move,
|
||||
simulated,
|
||||
moveTypeHolder,
|
||||
moveTypeHolder
|
||||
);
|
||||
|
||||
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
|
||||
// then bypass the check for ion deluge and electrify
|
||||
if (this.isTerastallized && (move.id === Moves.TERA_BLAST || move.id === Moves.TERA_STARSTORM && moveTypeHolder.value === PokemonType.STELLAR)) {
|
||||
return moveTypeHolder.value as PokemonType;
|
||||
}
|
||||
|
||||
globalScene.arena.applyTags(
|
||||
ArenaTagType.ION_DELUGE,
|
||||
simulated,
|
||||
@ -5602,13 +5608,44 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param revive Whether revive should be cured; defaults to true.
|
||||
* @param confusion Whether resetStatus should include confusion or not; defaults to false.
|
||||
* @param reloadAssets Whether to reload the assets or not; defaults to false.
|
||||
* @param asPhase Whether to reset the status in a phase or immediately
|
||||
*/
|
||||
resetStatus(revive = true, confusion = false, reloadAssets = false): void {
|
||||
resetStatus(revive = true, confusion = false, reloadAssets = false, asPhase = true): void {
|
||||
const lastStatus = this.status?.effect;
|
||||
if (!revive && lastStatus === StatusEffect.FAINT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (asPhase) {
|
||||
globalScene.unshiftPhase(new ResetStatusPhase(this, confusion, reloadAssets));
|
||||
} else {
|
||||
this.clearStatus(confusion, reloadAssets);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the action of clearing a Pokemon's status
|
||||
*
|
||||
* This is a helper to {@linkcode resetStatus}, which should be called directly instead of this method
|
||||
*/
|
||||
public clearStatus(confusion: boolean, reloadAssets: boolean) {
|
||||
const lastStatus = this.status?.effect;
|
||||
this.status = null;
|
||||
if (lastStatus === StatusEffect.SLEEP) {
|
||||
this.setFrameRate(10);
|
||||
if (this.getTag(BattlerTagType.NIGHTMARE)) {
|
||||
this.lapseTag(BattlerTagType.NIGHTMARE);
|
||||
}
|
||||
}
|
||||
if (confusion) {
|
||||
if (this.getTag(BattlerTagType.CONFUSED)) {
|
||||
this.lapseTag(BattlerTagType.CONFUSED);
|
||||
}
|
||||
}
|
||||
if (reloadAssets) {
|
||||
this.loadAssets(false).then(() => this.playAnim());
|
||||
}
|
||||
this.updateInfo(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,6 +128,7 @@ import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import i18next from "i18next";
|
||||
import { timedEventManager } from "#app/global-event-manager";
|
||||
import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants";
|
||||
|
||||
const outputModifierData = false;
|
||||
const useMaxWeightForOutput = false;
|
||||
@ -1329,7 +1330,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
|
||||
constructor() {
|
||||
super((party: Pokemon[], pregenArgs?: any[]) => {
|
||||
if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) {
|
||||
return new AttackTypeBoosterModifierType(pregenArgs[0] as PokemonType, 20);
|
||||
return new AttackTypeBoosterModifierType(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT);
|
||||
}
|
||||
|
||||
const attackMoveTypes = party.flatMap(p =>
|
||||
@ -1377,7 +1378,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
|
||||
weight += typeWeight;
|
||||
}
|
||||
|
||||
return new AttackTypeBoosterModifierType(type!, 20);
|
||||
return new AttackTypeBoosterModifierType(type!, TYPE_BOOST_ITEM_BOOST_PERCENT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1479,7 +1479,8 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier {
|
||||
return (
|
||||
super.shouldApply(pokemon, moveType, movePower) &&
|
||||
typeof moveType === "number" &&
|
||||
movePower instanceof NumberHolder
|
||||
movePower instanceof NumberHolder &&
|
||||
this.moveType === moveType
|
||||
);
|
||||
}
|
||||
|
||||
@ -1952,7 +1953,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier {
|
||||
);
|
||||
|
||||
// Remove the Pokemon's FAINT status
|
||||
pokemon.resetStatus(true, false, true);
|
||||
pokemon.resetStatus(true, false, true, false);
|
||||
|
||||
// Reapply Commander on the Pokemon's side of the field, if applicable
|
||||
const field = pokemon.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField();
|
||||
@ -2160,7 +2161,7 @@ export class PokemonHpRestoreModifier extends ConsumablePokemonModifier {
|
||||
restorePoints = Math.floor(restorePoints * multiplier);
|
||||
}
|
||||
if (this.fainted || this.healStatus) {
|
||||
pokemon.resetStatus(true, true);
|
||||
pokemon.resetStatus(true, true, false, false);
|
||||
}
|
||||
pokemon.hp = Math.min(
|
||||
pokemon.hp +
|
||||
@ -2180,7 +2181,7 @@ export class PokemonStatusHealModifier extends ConsumablePokemonModifier {
|
||||
* @returns always `true`
|
||||
*/
|
||||
override apply(playerPokemon: PlayerPokemon): boolean {
|
||||
playerPokemon.resetStatus(true, true);
|
||||
playerPokemon.resetStatus(true, true, false, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export class PartyHealPhase extends BattlePhase {
|
||||
globalScene.ui.fadeOut(1000).then(() => {
|
||||
for (const pokemon of globalScene.getPlayerParty()) {
|
||||
pokemon.hp = pokemon.getMaxHp();
|
||||
pokemon.resetStatus();
|
||||
pokemon.resetStatus(true, false, false, true);
|
||||
for (const move of pokemon.moveset) {
|
||||
move.ppUsed = 0;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { BattlePhase } from "#app/phases/battle-phase";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
|
||||
/**
|
||||
* Phase which handles resetting a Pokemon's status to none
|
||||
@ -22,23 +20,7 @@ export class ResetStatusPhase extends BattlePhase {
|
||||
}
|
||||
|
||||
public override start() {
|
||||
const lastStatus = this.pokemon.status?.effect;
|
||||
this.pokemon.status = null;
|
||||
if (lastStatus === StatusEffect.SLEEP) {
|
||||
this.pokemon.setFrameRate(10);
|
||||
if (this.pokemon.getTag(BattlerTagType.NIGHTMARE)) {
|
||||
this.pokemon.lapseTag(BattlerTagType.NIGHTMARE);
|
||||
}
|
||||
}
|
||||
if (this.affectConfusion) {
|
||||
if (this.pokemon.getTag(BattlerTagType.CONFUSED)) {
|
||||
this.pokemon.lapseTag(BattlerTagType.CONFUSED);
|
||||
}
|
||||
}
|
||||
if (this.reloadAssets) {
|
||||
this.pokemon.loadAssets(false).then(() => this.pokemon.playAnim());
|
||||
}
|
||||
this.pokemon.updateInfo(true);
|
||||
this.pokemon.clearStatus(this.affectConfusion, this.reloadAssets);
|
||||
this.end();
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export class RevivalBlessingPhase extends BattlePhase {
|
||||
}
|
||||
|
||||
pokemon.resetTurnData();
|
||||
pokemon.resetStatus();
|
||||
pokemon.resetStatus(true, false, false, false);
|
||||
pokemon.heal(Math.min(toDmgValue(0.5 * pokemon.getMaxHp()), pokemon.getMaxHp()));
|
||||
globalScene.queueMessage(
|
||||
i18next.t("moveTriggers:revivalBlessing", {
|
||||
|
@ -310,6 +310,48 @@ const timedEvents: TimedEvent[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Shining Spring",
|
||||
eventType: EventType.SHINY,
|
||||
startDate: new Date(Date.UTC(2025, 4, 2)),
|
||||
endDate: new Date(Date.UTC(2025, 4, 12)),
|
||||
bannerKey: "spr25event",
|
||||
scale: 0.21,
|
||||
availableLangs: ["en", "de", "it", "fr", "ja", "ko", "es-ES", "es-MX", "pt-BR", "zh-CN"],
|
||||
shinyMultiplier: 2,
|
||||
upgradeUnlockedVouchers: true,
|
||||
eventEncounters: [
|
||||
{ species: Species.HOPPIP },
|
||||
{ species: Species.CELEBI },
|
||||
{ species: Species.VOLBEAT },
|
||||
{ species: Species.ILLUMISE },
|
||||
{ species: Species.SPOINK },
|
||||
{ species: Species.LILEEP },
|
||||
{ species: Species.SHINX },
|
||||
{ species: Species.PACHIRISU },
|
||||
{ species: Species.CHERUBI },
|
||||
{ species: Species.MUNCHLAX },
|
||||
{ species: Species.TEPIG },
|
||||
{ species: Species.PANSAGE },
|
||||
{ species: Species.PANSEAR },
|
||||
{ species: Species.PANPOUR },
|
||||
{ species: Species.DARUMAKA },
|
||||
{ species: Species.ARCHEN },
|
||||
{ species: Species.DEERLING, formIndex: 0 }, // Spring Deerling
|
||||
{ species: Species.CLAUNCHER },
|
||||
{ species: Species.WISHIWASHI },
|
||||
{ species: Species.MUDBRAY },
|
||||
{ species: Species.DRAMPA },
|
||||
{ species: Species.JANGMO_O },
|
||||
{ species: Species.APPLIN },
|
||||
],
|
||||
classicWaveRewards: [
|
||||
{ wave: 8, type: "SHINY_CHARM" },
|
||||
{ wave: 8, type: "ABILITY_CHARM" },
|
||||
{ wave: 8, type: "CATCHING_CHARM" },
|
||||
{ wave: 25, type: "SHINY_CHARM" },
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
export class TimedEventManager {
|
||||
|
@ -1,131 +0,0 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves } from "#app/data/moves/move";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Abilities - Galvanize", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
|
||||
game.override
|
||||
.battleStyle("single")
|
||||
.startingLevel(100)
|
||||
.ability(Abilities.GALVANIZE)
|
||||
.moveset([Moves.TACKLE, Moves.REVELATION_DANCE, Moves.FURY_SWIPES])
|
||||
.enemySpecies(Species.DUSCLOPS)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
it("should change Normal-type attacks to Electric type and boost their power", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
const move = allMoves[Moves.TACKLE];
|
||||
vi.spyOn(move, "calculateBattlePower");
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(spy).toHaveReturnedWith(1);
|
||||
expect(move.calculateBattlePower).toHaveReturnedWith(48);
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("should cause Normal-type attacks to activate Volt Absorb", async () => {
|
||||
game.override.enemyAbility(Abilities.VOLT_ABSORB);
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8);
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(spy).toHaveReturnedWith(0);
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
});
|
||||
|
||||
it("should not change the type of variable-type moves", async () => {
|
||||
game.override.enemySpecies(Species.MIGHTYENA);
|
||||
|
||||
await game.classicMode.startBattle([Species.ESPEON]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.REVELATION_DANCE);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(spy).toHaveReturnedWith(0);
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
});
|
||||
|
||||
it("should affect all hits of a Normal-type multi-hit move", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
const spy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
game.move.select(Moves.FURY_SWIPES);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.move.forceHit();
|
||||
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
expect(playerPokemon.turnData.hitCount).toBeGreaterThan(1);
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
||||
|
||||
while (playerPokemon.turnData.hitsLeft > 0) {
|
||||
const enemyStartingHp = enemyPokemon.hp;
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp);
|
||||
}
|
||||
|
||||
expect(spy).not.toHaveReturnedWith(0);
|
||||
});
|
||||
});
|
190
test/abilities/normal-move-type-change.test.ts
Normal file
@ -0,0 +1,190 @@
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { allMoves } from "#app/data/moves/move";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { Abilities } from "#app/enums/abilities";
|
||||
import { Moves } from "#app/enums/moves";
|
||||
import { Species } from "#app/enums/species";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants";
|
||||
import { allAbilities } from "#app/data/data-lists";
|
||||
import { MoveTypeChangeAbAttr } from "#app/data/abilities/ability";
|
||||
import { toDmgValue } from "#app/utils/common";
|
||||
|
||||
/**
|
||||
* Tests for abilities that change the type of normal moves to
|
||||
* a different type and boost their power
|
||||
*
|
||||
* Includes
|
||||
* - Aerialate
|
||||
* - Galvanize
|
||||
* - Pixilate
|
||||
* - Refrigerate
|
||||
*/
|
||||
|
||||
describe.each([
|
||||
{ ab: Abilities.GALVANIZE, ab_name: "Galvanize", ty: PokemonType.ELECTRIC, tyName: "electric" },
|
||||
{ ab: Abilities.PIXILATE, ab_name: "Pixilate", ty: PokemonType.FAIRY, tyName: "fairy" },
|
||||
{ ab: Abilities.REFRIGERATE, ab_name: "Refrigerate", ty: PokemonType.ICE, tyName: "ice" },
|
||||
{ ab: Abilities.AERILATE, ab_name: "Aerilate", ty: PokemonType.FLYING, tyName: "flying" },
|
||||
])("Abilities - $ab_name", ({ ab, ty, tyName }) => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
|
||||
game.override
|
||||
.battleStyle("single")
|
||||
.startingLevel(100)
|
||||
.starterSpecies(Species.MAGIKARP)
|
||||
.ability(ab)
|
||||
.moveset([Moves.TACKLE, Moves.REVELATION_DANCE, Moves.FURY_SWIPES])
|
||||
.enemySpecies(Species.DUSCLOPS)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH)
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
it(`should change Normal-type attacks to ${tyName} type and boost their power`, async () => {
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const typeSpy = vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
const enemySpy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
const powerSpy = vi.spyOn(allMoves[Moves.TACKLE], "calculateBattlePower");
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(typeSpy).toHaveLastReturnedWith(ty);
|
||||
expect(enemySpy).toHaveReturnedWith(1);
|
||||
expect(powerSpy).toHaveReturnedWith(48);
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
||||
});
|
||||
|
||||
// Galvanize specifically would like to check for volt absorb's activation
|
||||
if (ab === Abilities.GALVANIZE) {
|
||||
it("should cause Normal-type attacks to activate Volt Absorb", async () => {
|
||||
game.override.enemyAbility(Abilities.VOLT_ABSORB);
|
||||
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const tySpy = vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
const enemyEffectivenessSpy = vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||
|
||||
enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8);
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(tySpy).toHaveLastReturnedWith(PokemonType.ELECTRIC);
|
||||
expect(enemyEffectivenessSpy).toHaveReturnedWith(0);
|
||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||
});
|
||||
}
|
||||
|
||||
it.each([
|
||||
{ moveName: "Revelation Dance", move: Moves.REVELATION_DANCE, expected_ty: PokemonType.WATER },
|
||||
{ moveName: "Judgement", move: Moves.JUDGMENT, expected_ty: PokemonType.NORMAL },
|
||||
{ moveName: "Terrain Pulse", move: Moves.TERRAIN_PULSE, expected_ty: PokemonType.NORMAL },
|
||||
{ moveName: "Weather Ball", move: Moves.WEATHER_BALL, expected_ty: PokemonType.NORMAL },
|
||||
{ moveName: "Multi Attack", move: Moves.MULTI_ATTACK, expected_ty: PokemonType.NORMAL },
|
||||
{ moveName: "Techno Blast", move: Moves.TECHNO_BLAST, expected_ty: PokemonType.NORMAL },
|
||||
])("should not change the type of $moveName", async ({ move, expected_ty: expectedTy }) => {
|
||||
game.override
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.moveset([move])
|
||||
.starterSpecies(Species.MAGIKARP);
|
||||
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const tySpy = vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
game.move.select(move);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
|
||||
expect(tySpy).toHaveLastReturnedWith(expectedTy);
|
||||
});
|
||||
|
||||
it("should affect all hits of a Normal-type multi-hit move", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const tySpy = vi.spyOn(playerPokemon, "getMoveType");
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
game.move.select(Moves.FURY_SWIPES);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.move.forceHit();
|
||||
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
expect(playerPokemon.turnData.hitCount).toBeGreaterThan(1);
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
||||
|
||||
while (playerPokemon.turnData.hitsLeft > 0) {
|
||||
const enemyStartingHp = enemyPokemon.hp;
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(tySpy).toHaveLastReturnedWith(ty);
|
||||
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp);
|
||||
}
|
||||
});
|
||||
|
||||
it("should not be affected by silk scarf after changing the move's type", async () => {
|
||||
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
const testMoveInstance = allMoves[Moves.TACKLE];
|
||||
|
||||
// get the power boost from the ability so we can compare it to the item
|
||||
// @ts-expect-error power multiplier is private
|
||||
const boost = allAbilities[ab]?.getAttrs(MoveTypeChangeAbAttr)[0]?.powerMultiplier;
|
||||
expect(boost, "power boost should be defined").toBeDefined();
|
||||
|
||||
const powerSpy = vi.spyOn(testMoveInstance, "calculateBattlePower");
|
||||
const typeSpy = vi.spyOn(game.scene.getPlayerPokemon()!, "getMoveType");
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(typeSpy, "type was not changed").toHaveLastReturnedWith(ty);
|
||||
expect(powerSpy).toHaveLastReturnedWith(toDmgValue(testMoveInstance.power * boost));
|
||||
});
|
||||
|
||||
it("should be affected by the type boosting item after changing the move's type", async () => {
|
||||
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: ty }]);
|
||||
await game.classicMode.startBattle();
|
||||
|
||||
// get the power boost from the ability so we can compare it to the item
|
||||
// @ts-expect-error power multiplier is private
|
||||
const boost = allAbilities[ab]?.getAttrs(MoveTypeChangeAbAttr)[0]?.powerMultiplier;
|
||||
expect(boost, "power boost should be defined").toBeDefined();
|
||||
|
||||
const tackle = allMoves[Moves.TACKLE];
|
||||
|
||||
const spy = vi.spyOn(tackle, "calculateBattlePower");
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.phaseInterceptor.to("BerryPhase", false);
|
||||
expect(spy).toHaveLastReturnedWith(toDmgValue(tackle.power * boost * (1 + TYPE_BOOST_ITEM_BOOST_PERCENT / 100)));
|
||||
});
|
||||
});
|
92
test/abilities/normalize.test.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants";
|
||||
import { allMoves } from "#app/data/moves/move";
|
||||
import { toDmgValue } from "#app/utils/common";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { PokemonType } from "#enums/pokemon-type";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Abilities - Normalize", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.TACKLE])
|
||||
.ability(Abilities.NORMALIZE)
|
||||
.battleStyle("single")
|
||||
.disableCrits()
|
||||
.enemySpecies(Species.MAGIKARP)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("should boost the power of normal type moves by 20%", async () => {
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
const powerSpy = vi.spyOn(allMoves[Moves.TACKLE], "calculateBattlePower");
|
||||
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(powerSpy).toHaveLastReturnedWith(toDmgValue(allMoves[Moves.TACKLE].power * 1.2));
|
||||
});
|
||||
|
||||
it("should not apply the old type boost item after changing a move's type", async () => {
|
||||
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.GRASS }]);
|
||||
game.override.moveset([Moves.LEAFAGE]);
|
||||
|
||||
const powerSpy = vi.spyOn(allMoves[Moves.LEAFAGE], "calculateBattlePower");
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
game.move.select(Moves.LEAFAGE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
// It should return with 1.2 (that is, only the power boost from the ability)
|
||||
expect(powerSpy).toHaveLastReturnedWith(toDmgValue(allMoves[Moves.LEAFAGE].power * 1.2));
|
||||
});
|
||||
|
||||
it("should apply silk scarf's power boost after changing a move's type", async () => {
|
||||
game.override.startingHeldItems([{ name: "ATTACK_TYPE_BOOSTER", count: 1, type: PokemonType.NORMAL }]);
|
||||
game.override.moveset([Moves.LEAFAGE]);
|
||||
|
||||
const powerSpy = vi.spyOn(allMoves[Moves.LEAFAGE], "calculateBattlePower");
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
game.move.select(Moves.LEAFAGE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
// 1.2 from normalize boost, second 1.2 from
|
||||
expect(powerSpy).toHaveLastReturnedWith(
|
||||
toDmgValue(allMoves[Moves.LEAFAGE].power * 1.2 * (1 + TYPE_BOOST_ITEM_BOOST_PERCENT / 100)),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ moveName: "Revelation Dance", move: Moves.REVELATION_DANCE },
|
||||
{ moveName: "Judgement", move: Moves.JUDGMENT, expected_ty: PokemonType.NORMAL },
|
||||
{ moveName: "Terrain Pulse", move: Moves.TERRAIN_PULSE },
|
||||
{ moveName: "Weather Ball", move: Moves.WEATHER_BALL },
|
||||
{ moveName: "Multi Attack", move: Moves.MULTI_ATTACK },
|
||||
{ moveName: "Techno Blast", move: Moves.TECHNO_BLAST },
|
||||
{ moveName: "Hidden Power", move: Moves.HIDDEN_POWER },
|
||||
])("should not boost the power of $moveName", async ({ move }) => {
|
||||
game.override.moveset([move]);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
const powerSpy = vi.spyOn(allMoves[move], "calculateBattlePower");
|
||||
|
||||
game.move.select(move);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(powerSpy).toHaveLastReturnedWith(allMoves[move].power);
|
||||
});
|
||||
});
|
@ -75,7 +75,7 @@ describe("Moves - Tera Blast", () => {
|
||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||
|
||||
expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(100);
|
||||
}, 20000);
|
||||
});
|
||||
|
||||
it("is super effective against terastallized targets if user is Stellar tera type", async () => {
|
||||
await game.classicMode.startBattle();
|
||||
@ -189,5 +189,33 @@ describe("Moves - Tera Blast", () => {
|
||||
|
||||
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
|
||||
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
|
||||
}, 20000);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ ab: "galvanize", ty: "electric", ab_id: Abilities.GALVANIZE, ty_id: PokemonType.ELECTRIC },
|
||||
{ ab: "refrigerate", ty: "ice", ab_id: Abilities.REFRIGERATE, ty_id: PokemonType.ICE },
|
||||
{ ab: "pixilate", ty: "fairy", ab_id: Abilities.PIXILATE, ty_id: PokemonType.FAIRY },
|
||||
{ ab: "aerilate", ty: "flying", ab_id: Abilities.AERILATE, ty_id: PokemonType.FLYING },
|
||||
])("should be $ty type if the user has $ab", async ({ ab_id, ty_id }) => {
|
||||
game.override.ability(ab_id).moveset([Moves.TERA_BLAST]).enemyAbility(Abilities.BALL_FETCH);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
expect(playerPokemon.getMoveType(allMoves[Moves.TERA_BLAST])).toBe(ty_id);
|
||||
});
|
||||
|
||||
it("should not be affected by normalize when the user is terastallized with tera normal", async () => {
|
||||
game.override.moveset([Moves.TERA_BLAST]).ability(Abilities.NORMALIZE);
|
||||
await game.classicMode.startBattle([Species.MAGIKARP]);
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
// override the tera state for the pokemon
|
||||
playerPokemon.isTerastallized = true;
|
||||
playerPokemon.teraType = PokemonType.NORMAL;
|
||||
|
||||
const move = allMoves[Moves.TERA_BLAST];
|
||||
const powerSpy = vi.spyOn(move, "calculateBattlePower");
|
||||
|
||||
game.move.select(Moves.TERA_BLAST);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(powerSpy).toHaveLastReturnedWith(move.power);
|
||||
});
|
||||
});
|
||||
|