mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-15 12:52:20 +02:00
Rewrite move phase
Co-authored-by: innerthunder <brandonerickson98@gmail.com>
This commit is contained in:
parent
607dc70b9b
commit
f85e008202
@ -7,7 +7,7 @@ import { MoveTarget } from "#enums/MoveTarget";
|
||||
import { MoveCategory } from "#enums/MoveCategory";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import type Pokemon from "#app/field/pokemon";
|
||||
import { HitResult, PokemonMove } from "#app/field/pokemon";
|
||||
import { HitResult } from "#app/field/pokemon";
|
||||
import { StatusEffect } from "#enums/status-effect";
|
||||
import type { BattlerIndex } from "#app/battle";
|
||||
import {
|
||||
@ -335,7 +335,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
* @param arena the {@linkcode Arena} containing this tag
|
||||
* @param simulated `true` if the tag is applied quietly; `false` otherwise.
|
||||
* @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against
|
||||
* @param attacker the attacking {@linkcode Pokemon}
|
||||
* @param _attacker the attacking {@linkcode Pokemon}
|
||||
* @param defender the defending {@linkcode Pokemon}
|
||||
* @param moveId the {@linkcode Moves | identifier} for the move being used
|
||||
* @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection
|
||||
@ -345,7 +345,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
arena: Arena,
|
||||
simulated: boolean,
|
||||
isProtected: BooleanHolder,
|
||||
attacker: Pokemon,
|
||||
_attacker: Pokemon,
|
||||
defender: Pokemon,
|
||||
moveId: Moves,
|
||||
ignoresProtectBypass: BooleanHolder,
|
||||
@ -354,8 +354,6 @@ export class ConditionalProtectTag extends ArenaTag {
|
||||
if (!isProtected.value) {
|
||||
isProtected.value = true;
|
||||
if (!simulated) {
|
||||
attacker.stopMultiHit(defender);
|
||||
|
||||
new CommonBattleAnim(CommonAnim.PROTECT, defender).play();
|
||||
globalScene.queueMessage(
|
||||
i18next.t("arenaTag:conditionalProtectApply", {
|
||||
@ -899,7 +897,7 @@ export class DelayedAttackTag extends ArenaTag {
|
||||
|
||||
if (!ret) {
|
||||
globalScene.unshiftPhase(
|
||||
new MoveEffectPhase(this.sourceId!, [this.targetIndex], new PokemonMove(this.sourceMove!, 0, 0, true)),
|
||||
new MoveEffectPhase(this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], false, true),
|
||||
); // TODO: are those bangs correct?
|
||||
}
|
||||
|
||||
|
@ -2637,7 +2637,7 @@ export class GulpMissileTag extends BattlerTag {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) {
|
||||
if (moveEffectPhase.move.hitsSubstitute(attacker, pokemon)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2993,7 +2993,7 @@ export class SubstituteTag extends BattlerTag {
|
||||
if (!attacker) {
|
||||
return;
|
||||
}
|
||||
const move = moveEffectPhase.move.getMove();
|
||||
const move = moveEffectPhase.move;
|
||||
const firstHit = attacker.turnData.hitCount === attacker.turnData.hitsLeft;
|
||||
|
||||
if (firstHit && move.hitsSubstitute(attacker, pokemon)) {
|
||||
@ -3681,7 +3681,7 @@ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; at
|
||||
return {
|
||||
phase: phase,
|
||||
attacker: phase.getPokemon(),
|
||||
move: phase.move.getMove(),
|
||||
move: phase.move,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
20
src/data/moves/move-utils.ts
Normal file
20
src/data/moves/move-utils.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { MoveTarget } from "#enums/MoveTarget";
|
||||
import type Move from "./move";
|
||||
|
||||
/**
|
||||
* Return whether the move targets the field
|
||||
*
|
||||
* Examples include
|
||||
* - Hazard moves like spikes
|
||||
* - Weather moves like rain dance
|
||||
* - User side moves like reflect and safeguard
|
||||
*/
|
||||
export function isFieldTargeted(move: Move): boolean {
|
||||
switch (move.moveTarget) {
|
||||
case MoveTarget.BOTH_SIDES:
|
||||
case MoveTarget.USER_SIDE:
|
||||
case MoveTarget.ENEMY_SIDE:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
@ -60,6 +60,7 @@ import {
|
||||
MoveTypeChangeAbAttr,
|
||||
PostDamageForceSwitchAbAttr,
|
||||
PostItemLostAbAttr,
|
||||
ReflectStatusMoveAbAttr,
|
||||
ReverseDrainAbAttr,
|
||||
UserFieldMoveTypePowerBoostAbAttr,
|
||||
VariableMovePowerAbAttr,
|
||||
@ -665,6 +666,17 @@ export default class Move implements Localizable {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case MoveFlags.REFLECTABLE:
|
||||
// If the target is not semi-invulnerable and either has magic coat active or an unignored magic bounce ability
|
||||
if (
|
||||
target?.getTag(SemiInvulnerableTag) &&
|
||||
(target.getTag(BattlerTagType.MAGIC_COAT) ||
|
||||
(!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
|
||||
target.hasAbilityWithAttr(ReflectStatusMoveAbAttr)))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return !!(this.flags & flag);
|
||||
@ -1716,7 +1728,7 @@ export class SacrificialAttr extends MoveEffectAttr {
|
||||
**/
|
||||
export class SacrificialAttrOnHit extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1955,6 +1967,14 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
|
||||
* @extends MoveEffectAttr
|
||||
*/
|
||||
export class FlameBurstAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
/**
|
||||
* This is self-targeted to bypass immunity to target-facing secondary
|
||||
* effects when the target has an active Substitute doll.
|
||||
* TODO: Find a more intuitive way to implement Substitute bypassing.
|
||||
*/
|
||||
super(true);
|
||||
}
|
||||
/**
|
||||
* @param user - n/a
|
||||
* @param target - The target Pokémon.
|
||||
@ -2177,7 +2197,7 @@ export class HitHealAttr extends MoveEffectAttr {
|
||||
private healStat: EffectiveStat | null;
|
||||
|
||||
constructor(healRatio?: number | null, healStat?: EffectiveStat) {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
|
||||
this.healRatio = healRatio ?? 0.5;
|
||||
this.healStat = healStat ?? null;
|
||||
@ -2426,7 +2446,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
public overrideStatus: boolean = false;
|
||||
|
||||
constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) {
|
||||
super(selfTarget, { trigger: MoveEffectTrigger.HIT });
|
||||
super(selfTarget);
|
||||
|
||||
this.effect = effect;
|
||||
this.turnsRemaining = turnsRemaining;
|
||||
@ -2434,10 +2454,6 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
|
||||
if (statusCheck) {
|
||||
@ -2495,7 +2511,7 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
|
||||
|
||||
export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2534,15 +2550,11 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
||||
private chance: number;
|
||||
|
||||
constructor(chance: number) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rand = Phaser.Math.RND.realInRange(0, 1);
|
||||
if (rand >= this.chance) {
|
||||
return false;
|
||||
@ -2590,7 +2602,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
private berriesOnly: boolean;
|
||||
|
||||
constructor(berriesOnly: boolean) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
this.berriesOnly = berriesOnly;
|
||||
}
|
||||
|
||||
@ -2600,17 +2612,13 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
* @param target Target {@linkcode Pokemon} that the moves applies to
|
||||
* @param move {@linkcode Move} that is used
|
||||
* @param args N/A
|
||||
* @returns {boolean} True if an item was removed
|
||||
* @returns True if an item was removed
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft
|
||||
|
||||
@ -2664,8 +2672,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
|
||||
*/
|
||||
export class EatBerryAttr extends MoveEffectAttr {
|
||||
protected chosenBerry: BerryModifier | undefined;
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
constructor(selfTarget: boolean) {
|
||||
super(selfTarget);
|
||||
}
|
||||
/**
|
||||
* Causes the target to eat a berry.
|
||||
@ -2680,17 +2688,20 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
const heldBerries = this.getTargetHeldBerries(target);
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
|
||||
const heldBerries = this.getTargetHeldBerries(pokemon);
|
||||
if (heldBerries.length <= 0) {
|
||||
return false;
|
||||
}
|
||||
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
|
||||
const preserve = new BooleanHolder(false);
|
||||
globalScene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation
|
||||
// check for berry pouch preservation
|
||||
globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve);
|
||||
if (!preserve.value) {
|
||||
this.reduceBerryModifier(target);
|
||||
this.reduceBerryModifier(pokemon);
|
||||
}
|
||||
this.eatBerry(target);
|
||||
this.eatBerry(pokemon);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2718,7 +2729,7 @@ export class EatBerryAttr extends MoveEffectAttr {
|
||||
*/
|
||||
export class StealEatBerryAttr extends EatBerryAttr {
|
||||
constructor() {
|
||||
super();
|
||||
super(true);
|
||||
}
|
||||
/**
|
||||
* User steals a random berry from the target and then eats it.
|
||||
@ -2729,9 +2740,6 @@ export class StealEatBerryAttr extends EatBerryAttr {
|
||||
* @returns {boolean} true if the function succeeds
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
const cancelled = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft
|
||||
if (cancelled.value === true) {
|
||||
@ -2782,10 +2790,6 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Special edge case for shield dust blocking Sparkling Aria curing burn
|
||||
const moveTargets = getMoveTargets(user, move.id);
|
||||
if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === Moves.SPARKLING_ARIA && moveTargets.targets.length === 1) {
|
||||
@ -3163,14 +3167,6 @@ export class StatStageChangeAttr extends MoveEffectAttr {
|
||||
return this.options?.showMessage ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates when the stat change should trigger
|
||||
* @default MoveEffectTrigger.HIT
|
||||
*/
|
||||
public override get trigger () {
|
||||
return this.options?.trigger ?? MoveEffectTrigger.HIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to change stats of the user or target (depending on value of selfTarget) if conditions are met
|
||||
* @param user {@linkcode Pokemon} the user of the move
|
||||
@ -3184,10 +3180,6 @@ export class StatStageChangeAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||
const stages = this.getLevels(user);
|
||||
@ -3471,7 +3463,7 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
|
||||
*/
|
||||
export class OrderUpStatBoostAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
}
|
||||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
|
||||
@ -3548,17 +3540,15 @@ export class ResetStatsAttr extends MoveEffectAttr {
|
||||
this.targetAllPokemon = targetAllPokemon;
|
||||
}
|
||||
|
||||
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
override apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||
if (this.targetAllPokemon) {
|
||||
// Target all pokemon on the field when Freezy Frost or Haze are used
|
||||
const activePokemon = globalScene.getField(true);
|
||||
activePokemon.forEach((p) => this.resetStats(p));
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:statEliminated"));
|
||||
} else { // Affects only the single target when Clear Smog is used
|
||||
if (!move.hitsSubstitute(user, target)) {
|
||||
this.resetStats(target);
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
this.resetStats(target);
|
||||
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -4217,7 +4207,8 @@ export class PresentPowerAttr extends VariablePowerAttr {
|
||||
(args[0] as NumberHolder).value = 120;
|
||||
} else if (80 < powerSeed && powerSeed <= 100) {
|
||||
// If this move is multi-hit, disable all other hits
|
||||
user.stopMultiHit();
|
||||
user.turnData.hitCount = 1;
|
||||
user.turnData.hitsLeft = 1;
|
||||
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(),
|
||||
toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true));
|
||||
}
|
||||
@ -5371,7 +5362,7 @@ export class BypassRedirectAttr extends MoveAttr {
|
||||
|
||||
export class FrenzyAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT, lastHitOnly: true });
|
||||
super(true, { lastHitOnly: true });
|
||||
}
|
||||
|
||||
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
||||
@ -5443,22 +5434,20 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
||||
protected cancelOnFail: boolean;
|
||||
private failOnOverlap: boolean;
|
||||
|
||||
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: number = 0, turnCountMax?: number, lastHitOnly: boolean = false, cancelOnFail: boolean = false) {
|
||||
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: number = 0, turnCountMax?: number, lastHitOnly: boolean = false) {
|
||||
super(selfTarget, { lastHitOnly: lastHitOnly });
|
||||
|
||||
this.tagType = tagType;
|
||||
this.turnCountMin = turnCountMin;
|
||||
this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin;
|
||||
this.failOnOverlap = !!failOnOverlap;
|
||||
this.cancelOnFail = cancelOnFail;
|
||||
}
|
||||
|
||||
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) {
|
||||
if (!super.canApply(user, target, move, args)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
@ -5549,19 +5538,6 @@ export class LeechSeedAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.SEEDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Seeding effect to the target if the target does not have an active Substitute.
|
||||
* @param user the {@linkcode Pokemon} using the move
|
||||
* @param target the {@linkcode Pokemon} targeted by the move
|
||||
* @param move the {@linkcode Move} invoking this effect
|
||||
* @param args n/a
|
||||
* @returns `true` if the effect successfully applies; `false` otherwise
|
||||
*/
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
return !move.hitsSubstitute(user, target)
|
||||
&& super.apply(user, target, move, args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5737,13 +5713,6 @@ export class FlinchAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.FLINCHED, false);
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
if (!move.hitsSubstitute(user, target)) {
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfuseAttr extends AddBattlerTagAttr {
|
||||
@ -5759,16 +5728,13 @@ export class ConfuseAttr extends AddBattlerTagAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!move.hitsSubstitute(user, target)) {
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
return false;
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
}
|
||||
|
||||
export class RechargeAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.RECHARGING, true, false, 1, 1, true, true);
|
||||
super(BattlerTagType.RECHARGING, true, false, 1, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6151,7 +6117,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr {
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
constructor(user?: boolean) {
|
||||
constructor() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@ -6392,10 +6358,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
const player = switchOutTarget instanceof PlayerPokemon;
|
||||
|
||||
if (!this.selfSwitch) {
|
||||
if (move.hitsSubstitute(user, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Dondozo with an allied Tatsugiri in its mouth cannot be forced out
|
||||
const commandedTag = switchOutTarget.getTag(BattlerTagType.COMMANDED);
|
||||
if (commandedTag?.getSourcePokemon()?.isActive(true)) {
|
||||
@ -6650,7 +6612,7 @@ export class ChangeTypeAttr extends MoveEffectAttr {
|
||||
private type: PokemonType;
|
||||
|
||||
constructor(type: PokemonType) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
|
||||
this.type = type;
|
||||
}
|
||||
@ -6673,7 +6635,7 @@ export class AddTypeAttr extends MoveEffectAttr {
|
||||
private type: PokemonType;
|
||||
|
||||
constructor(type: PokemonType) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
|
||||
this.type = type;
|
||||
}
|
||||
@ -7369,7 +7331,7 @@ export class AbilityChangeAttr extends MoveEffectAttr {
|
||||
public ability: Abilities;
|
||||
|
||||
constructor(ability: Abilities, selfTarget?: boolean) {
|
||||
super(selfTarget, { trigger: MoveEffectTrigger.HIT });
|
||||
super(selfTarget);
|
||||
|
||||
this.ability = ability;
|
||||
}
|
||||
@ -7400,7 +7362,7 @@ export class AbilityCopyAttr extends MoveEffectAttr {
|
||||
public copyToPartner: boolean;
|
||||
|
||||
constructor(copyToPartner: boolean = false) {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
|
||||
this.copyToPartner = copyToPartner;
|
||||
}
|
||||
@ -7441,7 +7403,7 @@ export class AbilityGiveAttr extends MoveEffectAttr {
|
||||
public copyToPartner: boolean;
|
||||
|
||||
constructor() {
|
||||
super(false, { trigger: MoveEffectTrigger.HIT });
|
||||
super(false);
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
@ -7720,7 +7682,7 @@ export class DiscourageFrequentUseAttr extends MoveAttr {
|
||||
|
||||
export class MoneyAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT, firstHitOnly: true });
|
||||
super(true, {firstHitOnly: true });
|
||||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
||||
@ -7787,7 +7749,7 @@ export class StatusIfBoostedAttr extends MoveEffectAttr {
|
||||
public effect: StatusEffect;
|
||||
|
||||
constructor(effect: StatusEffect) {
|
||||
super(true, { trigger: MoveEffectTrigger.HIT });
|
||||
super(true);
|
||||
this.effect = effect;
|
||||
}
|
||||
|
||||
@ -10566,7 +10528,7 @@ export function initMoves() {
|
||||
.attr(JawLockAttr)
|
||||
.bitingMove(),
|
||||
new SelfStatusMove(Moves.STUFF_CHEEKS, PokemonType.NORMAL, -1, 10, -1, 0, 8)
|
||||
.attr(EatBerryAttr)
|
||||
.attr(EatBerryAttr, true)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
|
||||
.condition((user) => {
|
||||
const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
|
||||
@ -10590,7 +10552,7 @@ export function initMoves() {
|
||||
.makesContact(false)
|
||||
.partial(), // smart targetting is unimplemented
|
||||
new StatusMove(Moves.TEATIME, PokemonType.NORMAL, -1, 10, -1, 0, 8)
|
||||
.attr(EatBerryAttr)
|
||||
.attr(EatBerryAttr, false)
|
||||
.target(MoveTarget.ALL),
|
||||
new StatusMove(Moves.OCTOLOCK, PokemonType.FIGHTING, 100, 15, -1, 0, 8)
|
||||
.condition(failIfGhostTypeCondition)
|
||||
|
@ -1,4 +1,4 @@
|
||||
/** Represent the result of a hit check against a target. */
|
||||
/** The result of a hit check calculation */
|
||||
export const HitCheckResult = {
|
||||
/** Hit checks haven't been evaluated yet in this pass */
|
||||
PENDING: 0,
|
||||
@ -6,14 +6,18 @@ export const HitCheckResult = {
|
||||
HIT: 1,
|
||||
/** The move has no effect on the target */
|
||||
NO_EFFECT: 2,
|
||||
/** The move has no effect on the target, but doesn't proc the default "no effect" message. */
|
||||
/** The move has no effect on the target, but doesn't proc the default "no effect" message */
|
||||
NO_EFFECT_NO_MESSAGE: 3,
|
||||
/** The target protected itself against the move */
|
||||
PROTECTED: 4,
|
||||
/** The move missed the target */
|
||||
MISS: 5,
|
||||
/** The move is reflected by magic coat or magic bounce */
|
||||
REFLECTED: 6,
|
||||
/** The target is no longer on the field */
|
||||
TARGET_NOT_ON_FIELD: 7,
|
||||
/** The move failed unexpectedly */
|
||||
ERROR: 6,
|
||||
ERROR: 8,
|
||||
} as const;
|
||||
|
||||
export type HitCheckResult = typeof HitCheckResult[keyof typeof HitCheckResult];
|
||||
|
@ -4600,212 +4600,36 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the results of a move to this pokemon
|
||||
* @param source The {@linkcode Pokemon} using the move
|
||||
* @param move The {@linkcode Move} being used
|
||||
* @returns The {@linkcode HitResult} of the attack
|
||||
*/
|
||||
apply(source: Pokemon, move: Move): HitResult {
|
||||
const defendingSide = this.isPlayer()
|
||||
? ArenaTagSide.PLAYER
|
||||
: ArenaTagSide.ENEMY;
|
||||
const moveCategory = new NumberHolder(move.category);
|
||||
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory);
|
||||
if (moveCategory.value === MoveCategory.STATUS) {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
const typeMultiplier = this.getMoveEffectiveness(
|
||||
source,
|
||||
move,
|
||||
false,
|
||||
false,
|
||||
cancelled,
|
||||
);
|
||||
|
||||
if (!cancelled.value && typeMultiplier === 0) {
|
||||
globalScene.queueMessage(
|
||||
i18next.t("battle:hitResultNoEffect", {
|
||||
pokemonName: getPokemonNameWithAffix(this),
|
||||
}),
|
||||
);
|
||||
}
|
||||
return typeMultiplier === 0 ? HitResult.NO_EFFECT : HitResult.STATUS;
|
||||
/** Calculate whether the given move critically hits this pokemon
|
||||
* @param source - The {@linkcode Pokemon} using the move
|
||||
* @param move - The {@linkcode Move} being used
|
||||
* @param simulated - If `true`, suppresses changes to game state during calculation (defaults to `true`)
|
||||
* @returns whether the move critically hits the pokemon
|
||||
*/
|
||||
getCriticalHitResult(source: Pokemon, move: Move, simulated: boolean = true): boolean {
|
||||
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
|
||||
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) {
|
||||
return false;
|
||||
}
|
||||
/** Determines whether the attack critically hits */
|
||||
let isCritical: boolean;
|
||||
const critOnly = new BooleanHolder(false);
|
||||
const critAlways = source.getTag(BattlerTagType.ALWAYS_CRIT);
|
||||
applyMoveAttrs(CritOnlyAttr, source, this, move, critOnly);
|
||||
applyAbAttrs(
|
||||
ConditionalCritAbAttr,
|
||||
source,
|
||||
null,
|
||||
false,
|
||||
critOnly,
|
||||
this,
|
||||
move,
|
||||
);
|
||||
if (critOnly.value || critAlways) {
|
||||
isCritical = true;
|
||||
} else {
|
||||
const isCritical = new BooleanHolder(false);
|
||||
|
||||
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) {
|
||||
isCritical.value = true;
|
||||
}
|
||||
applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical);
|
||||
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move);
|
||||
if (!isCritical.value) {
|
||||
const critChance = [24, 8, 2, 1][
|
||||
Math.max(0, Math.min(this.getCritStage(source, move), 3))
|
||||
];
|
||||
isCritical =
|
||||
critChance === 1 || !globalScene.randBattleSeedInt(critChance);
|
||||
isCritical.value = critChance === 1 || !globalScene.randBattleSeedInt(critChance);
|
||||
}
|
||||
|
||||
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
|
||||
const blockCrit = new BooleanHolder(false);
|
||||
applyAbAttrs(BlockCritAbAttr, this, null, false, blockCrit);
|
||||
if (noCritTag || blockCrit.value || Overrides.NEVER_CRIT_OVERRIDE) {
|
||||
isCritical = false;
|
||||
}
|
||||
applyAbAttrs(BlockCritAbAttr, this, null, simulated, isCritical);
|
||||
|
||||
/**
|
||||
* Applies stat changes from {@linkcode move} and gives it to {@linkcode source}
|
||||
* before damage calculation
|
||||
*/
|
||||
applyMoveAttrs(StatChangeBeforeDmgCalcAttr, source, this, move);
|
||||
return isCritical.value;
|
||||
|
||||
const {
|
||||
cancelled,
|
||||
result,
|
||||
damage: dmg,
|
||||
} = this.getAttackDamage(
|
||||
{source, move, isCritical, simulated: false});
|
||||
|
||||
const typeBoost = source.findTag(
|
||||
t =>
|
||||
t instanceof TypeBoostTag && t.boostedType === source.getMoveType(move),
|
||||
) as TypeBoostTag;
|
||||
if (typeBoost?.oneUse) {
|
||||
source.removeTag(typeBoost.tagType);
|
||||
}
|
||||
|
||||
if (
|
||||
cancelled ||
|
||||
result === HitResult.IMMUNE ||
|
||||
result === HitResult.NO_EFFECT
|
||||
) {
|
||||
source.stopMultiHit(this);
|
||||
|
||||
if (!cancelled) {
|
||||
if (result === HitResult.IMMUNE) {
|
||||
globalScene.queueMessage(
|
||||
i18next.t("battle:hitResultImmune", {
|
||||
pokemonName: getPokemonNameWithAffix(this),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
globalScene.queueMessage(
|
||||
i18next.t("battle:hitResultNoEffect", {
|
||||
pokemonName: getPokemonNameWithAffix(this),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// In case of fatal damage, this tag would have gotten cleared before we could lapse it.
|
||||
const destinyTag = this.getTag(BattlerTagType.DESTINY_BOND);
|
||||
const grudgeTag = this.getTag(BattlerTagType.GRUDGE);
|
||||
|
||||
if (dmg) {
|
||||
this.lapseTags(BattlerTagLapseType.HIT);
|
||||
|
||||
const substitute = this.getTag(SubstituteTag);
|
||||
const isBlockedBySubstitute =
|
||||
!!substitute && move.hitsSubstitute(source, this);
|
||||
if (isBlockedBySubstitute) {
|
||||
substitute.hp -= dmg;
|
||||
}
|
||||
if (!this.isPlayer() && dmg >= this.hp) {
|
||||
globalScene.applyModifiers(EnemyEndureChanceModifier, false, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* We explicitly require to ignore the faint phase here, as we want to show the messages
|
||||
* about the critical hit and the super effective/not very effective messages before the faint phase.
|
||||
*/
|
||||
const damage = this.damageAndUpdate(isBlockedBySubstitute ? 0 : dmg,
|
||||
{
|
||||
result: result as DamageResult,
|
||||
isCritical,
|
||||
ignoreFaintPhase: true,
|
||||
source
|
||||
});
|
||||
|
||||
if (damage > 0) {
|
||||
if (source.isPlayer()) {
|
||||
globalScene.validateAchvs(DamageAchv, new NumberHolder(damage));
|
||||
if (damage > globalScene.gameData.gameStats.highestDamage) {
|
||||
globalScene.gameData.gameStats.highestDamage = damage;
|
||||
}
|
||||
}
|
||||
source.turnData.totalDamageDealt += damage;
|
||||
source.turnData.singleHitDamageDealt = damage;
|
||||
this.turnData.damageTaken += damage;
|
||||
this.battleData.hitCount++;
|
||||
|
||||
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()) {
|
||||
globalScene.applyModifiers(
|
||||
DamageMoneyRewardModifier,
|
||||
true,
|
||||
source,
|
||||
new NumberHolder(damage),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCritical) {
|
||||
globalScene.queueMessage(i18next.t("battle:hitResultCriticalHit"));
|
||||
}
|
||||
|
||||
// want to include is.Fainted() in case multi hit move ends early, still want to render message
|
||||
if (source.turnData.hitsLeft === 1 || this.isFainted()) {
|
||||
switch (result) {
|
||||
case HitResult.SUPER_EFFECTIVE:
|
||||
globalScene.queueMessage(i18next.t("battle:hitResultSuperEffective"));
|
||||
break;
|
||||
case HitResult.NOT_VERY_EFFECTIVE:
|
||||
globalScene.queueMessage(
|
||||
i18next.t("battle:hitResultNotVeryEffective"),
|
||||
);
|
||||
break;
|
||||
case HitResult.ONE_HIT_KO:
|
||||
globalScene.queueMessage(i18next.t("battle:hitResultOneHitKO"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isFainted()) {
|
||||
// set splice index here, so future scene queues happen before FaintedPhase
|
||||
globalScene.setPhaseQueueSplice();
|
||||
globalScene.unshiftPhase(
|
||||
new FaintPhase(
|
||||
this.getBattlerIndex(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
);
|
||||
|
||||
this.destroySubstitute();
|
||||
this.lapseTag(BattlerTagType.COMMANDED);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4869,7 +4693,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by apply(), given the damage, adds a new DamagePhase and actually updates HP values, etc.
|
||||
* Given the damage, adds a new DamagePhase and update HP values, etc.
|
||||
*
|
||||
* Checks for 'Indirect' HitResults to account for Endure/Reviver Seed applying correctly
|
||||
* @param damage integer - passed to damage()
|
||||
* @param result an enum if it's super effective, not very, etc.
|
||||
@ -5172,8 +4997,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Gets whether the given move is currently disabled for this Pokemon.
|
||||
*
|
||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||
* @returns {boolean} `true` if the move is disabled for this Pokemon, otherwise `false`
|
||||
* @param moveId - The {@linkcode Moves} ID of the move to check
|
||||
* @returns `true` if the move is disabled for this Pokemon, otherwise `false`
|
||||
*
|
||||
* @see {@linkcode MoveRestrictionBattlerTag}
|
||||
*/
|
||||
@ -5184,9 +5009,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Gets whether the given move is currently disabled for the user based on the player's target selection
|
||||
*
|
||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||
* @param {Pokemon} user {@linkcode Pokemon} the move user
|
||||
* @param {Pokemon} target {@linkcode Pokemon} the target of the move
|
||||
* @param moveId - The {@linkcode Moves} ID of the move to check
|
||||
* @param user - The move user
|
||||
* @param target - The target of the move
|
||||
*
|
||||
* @returns {boolean} `true` if the move is disabled for this Pokemon due to the player's target selection
|
||||
*
|
||||
@ -5216,10 +5041,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
/**
|
||||
* Gets the {@link MoveRestrictionBattlerTag} that is restricting a move, if it exists.
|
||||
*
|
||||
* @param {Moves} moveId {@linkcode Moves} ID of the move to check
|
||||
* @param {Pokemon} user {@linkcode Pokemon} the move user, optional and used when the target is a factor in the move's restricted status
|
||||
* @param {Pokemon} target {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status
|
||||
* @returns {MoveRestrictionBattlerTag | null} the first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
|
||||
* @param moveId - {@linkcode Moves} ID of the move to check
|
||||
* @param user - {@linkcode Pokemon} the move user, optional and used when the target is a factor in the move's restricted status
|
||||
* @param target - {@linkcode Pokemon} the target of the move, optional and used when the target is a factor in the move's restricted status
|
||||
* @returns The first tag on this Pokemon that restricts the move, or `null` if the move is not restricted.
|
||||
*/
|
||||
getRestrictingTag(
|
||||
moveId: Moves,
|
||||
@ -5281,20 +5106,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return this.summonData.moveQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this Pokemon is using a multi-hit move, cancels all subsequent strikes
|
||||
* @param {Pokemon} target If specified, this only cancels subsequent strikes against the given target
|
||||
*/
|
||||
stopMultiHit(target?: Pokemon): void {
|
||||
const effectPhase = globalScene.getCurrentPhase();
|
||||
if (
|
||||
effectPhase instanceof MoveEffectPhase &&
|
||||
effectPhase.getUserPokemon() === this
|
||||
) {
|
||||
effectPhase.stopMultiHit(target);
|
||||
}
|
||||
}
|
||||
|
||||
changeForm(formChange: SpeciesFormChange): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
this.formIndex = Math.max(
|
||||
@ -5712,7 +5523,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* cancel the attack's subsequent hits.
|
||||
*/
|
||||
if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) {
|
||||
this.stopMultiHit();
|
||||
const currentPhase = globalScene.getCurrentPhase();
|
||||
if (currentPhase instanceof MoveEffectPhase && currentPhase.getUserPokemon() === this) {
|
||||
this.turnData.hitCount = 1;
|
||||
this.turnData.hitsLeft = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (asPhase) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -404,9 +404,10 @@ export class MovePhase extends BattlePhase {
|
||||
* if the move fails.
|
||||
*/
|
||||
if (success) {
|
||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
|
||||
const move = this.move.getMove();
|
||||
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, move);
|
||||
globalScene.unshiftPhase(
|
||||
new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, this.move, this.reflected),
|
||||
new MoveEffectPhase(this.pokemon.getBattlerIndex(), this.targets, move, this.reflected, this.move.virtual),
|
||||
);
|
||||
} else {
|
||||
if ([Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) {
|
||||
|
@ -50,7 +50,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
game.move.select(dynamaxCannon.id);
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||
}, 20000);
|
||||
@ -62,7 +62,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
game.move.select(dynamaxCannon.id);
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(100);
|
||||
}, 20000);
|
||||
@ -75,7 +75,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||
expect(phase.move.id).toBe(dynamaxCannon.id);
|
||||
// Force level cap to be 100
|
||||
vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
@ -90,7 +90,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||
expect(phase.move.id).toBe(dynamaxCannon.id);
|
||||
// Force level cap to be 100
|
||||
vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
@ -105,7 +105,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||
expect(phase.move.id).toBe(dynamaxCannon.id);
|
||||
// Force level cap to be 100
|
||||
vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
@ -120,7 +120,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||
expect(phase.move.id).toBe(dynamaxCannon.id);
|
||||
// Force level cap to be 100
|
||||
vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
@ -135,7 +135,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
expect(phase.move.moveId).toBe(dynamaxCannon.id);
|
||||
expect(phase.move.id).toBe(dynamaxCannon.id);
|
||||
// Force level cap to be 100
|
||||
vi.spyOn(game.scene, "getMaxExpLevel").mockReturnValue(100);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
@ -150,7 +150,7 @@ describe("Moves - Dynamax Cannon", () => {
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.moveId).toBe(dynamaxCannon.id);
|
||||
expect((game.scene.getCurrentPhase() as MoveEffectPhase).move.id).toBe(dynamaxCannon.id);
|
||||
await game.phaseInterceptor.to(DamageAnimPhase, false);
|
||||
expect(dynamaxCannon.calculateBattlePower).toHaveLastReturnedWith(200);
|
||||
}, 20000);
|
||||
|
Loading…
Reference in New Issue
Block a user