mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-20 06:19:29 +02:00
Bug fixes + test fixes
This commit is contained in:
parent
2f26afd6fd
commit
58b83b1a55
@ -3447,7 +3447,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) {
|
if (effect === StatusEffect.SLEEP || effect === StatusEffect.FREEZE) {
|
||||||
const currentPhase = this.scene.getCurrentPhase();
|
const currentPhase = this.scene.getCurrentPhase();
|
||||||
if (currentPhase instanceof MoveEffectPhase && currentPhase.getUserPokemon() === this) {
|
if (currentPhase instanceof MoveEffectPhase && currentPhase.getUserPokemon() === this) {
|
||||||
this.turnData.hitCount -= this.turnData.hitsLeft - 1;
|
this.turnData.hitCount = 1;
|
||||||
this.turnData.hitsLeft = 1;
|
this.turnData.hitsLeft = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,6 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
private firstHit: boolean;
|
private firstHit: boolean;
|
||||||
/** Is this the last strike of a move? */
|
/** Is this the last strike of a move? */
|
||||||
private lastHit: boolean;
|
private lastHit: boolean;
|
||||||
/** Is this the first target to be hit by this strike? */
|
|
||||||
private firstTarget: boolean = true;
|
|
||||||
|
|
||||||
constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) {
|
constructor(scene: BattleScene, battlerIndex: BattlerIndex, targets: BattlerIndex[], move: PokemonMove) {
|
||||||
super(scene, battlerIndex);
|
super(scene, battlerIndex);
|
||||||
@ -136,90 +134,6 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
|
||||||
|
|
||||||
this.moveHistoryEntry = { move: move.id, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
|
|
||||||
|
|
||||||
targets.forEach((t, i) => this.hitChecks[i] = this.hitCheck(t));
|
|
||||||
|
|
||||||
if (!targets.some(t => t.isActive(true))) {
|
|
||||||
this.scene.queueMessage(i18next.t("battle:attackFailed"));
|
|
||||||
this.moveHistoryEntry.result = MoveResult.FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hitChecks.some(hc => hc[0] === HitCheckResult.HIT)) {
|
|
||||||
this.moveHistoryEntry.result = MoveResult.SUCCESS;
|
|
||||||
} else if (this.hitChecks.every(hc => hc[0] === HitCheckResult.MISS)) {
|
|
||||||
this.moveHistoryEntry.result = MoveResult.MISS;
|
|
||||||
} else {
|
|
||||||
this.moveHistoryEntry.result = MoveResult.FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the move has a post-target effect (e.g. Explosion), but doesn't
|
|
||||||
// successfully hit a target, play the move's animation and return
|
|
||||||
if (move.getAttrs(MoveEffectAttr).some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET)
|
|
||||||
&& this.hitChecks.every(hc => hc[1] === 0, this)) {
|
|
||||||
const playOnEmptyField = this.scene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
|
|
||||||
return new MoveAnim(move.id, user, this.getFirstTarget()!.getBattlerIndex(), playOnEmptyField).play(this.scene, false, () =>
|
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_TARGET, user, null).then(() => this.end())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this phase represents the first strike of the given move,
|
|
||||||
// log the move in the user's move history.
|
|
||||||
if (user.turnData.hitsLeft === -1) {
|
|
||||||
user.pushMoveHistory(this.moveHistoryEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(this.hitChecks);
|
|
||||||
|
|
||||||
targets.forEach((target, i) => {
|
|
||||||
const [ hitCheckResult, effectiveness ] = this.hitChecks[i];
|
|
||||||
|
|
||||||
switch (hitCheckResult) {
|
|
||||||
case HitCheckResult.HIT:
|
|
||||||
this.applyMoveEffects(target, effectiveness);
|
|
||||||
this.firstTarget = false;
|
|
||||||
break;
|
|
||||||
case HitCheckResult.NO_EFFECT:
|
|
||||||
if (move.id === Moves.SHEER_COLD) {
|
|
||||||
this.scene.queueMessage(i18next.t("battle:hitResultImmune", { pokemonName: getPokemonNameWithAffix(target) }));
|
|
||||||
} else {
|
|
||||||
this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(target) }));
|
|
||||||
}
|
|
||||||
case HitCheckResult.PROTECTED:
|
|
||||||
case HitCheckResult.NO_EFFECT_NO_MESSAGE:
|
|
||||||
applyMoveAttrs(NoEffectAttr, user, target, move);
|
|
||||||
break;
|
|
||||||
case HitCheckResult.MISS:
|
|
||||||
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" }));
|
|
||||||
applyMoveAttrs(MissEffectAttr, user, target, move);
|
|
||||||
break;
|
|
||||||
case HitCheckResult.PENDING:
|
|
||||||
case HitCheckResult.ERROR:
|
|
||||||
console.log(`Unexpected hit check result ${HitCheckResult[hitCheckResult]}. Aborting phase.`);
|
|
||||||
return this.end();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const doPostTarget = this.lastHit ? this.triggerMoveEffects(MoveEffectTrigger.POST_TARGET, user, null) : Promise.resolve();
|
|
||||||
doPostTarget.then(() => {
|
|
||||||
this.updateSubstitutes();
|
|
||||||
this.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected applyMoveEffects(target: Pokemon, effectiveness: TypeDamageMultiplier): void {
|
|
||||||
const user = this.getUserPokemon();
|
|
||||||
const move = this.move.getMove();
|
|
||||||
|
|
||||||
if (isNullOrUndefined(user)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent field-targeted moves from activating multiple times
|
|
||||||
if (move.isFieldTarget() && target !== this.getTargets()[this.targets.length - 1]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this phase is for the first hit of the invoked move,
|
* If this phase is for the first hit of the invoked move,
|
||||||
* resolve the move's total hit count. This block combines the
|
* resolve the move's total hit count. This block combines the
|
||||||
@ -238,13 +152,109 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
user.turnData.hitsLeft = hitCount.value;
|
user.turnData.hitsLeft = hitCount.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.firstHit = user.turnData.hitsLeft === user.turnData.hitCount;
|
this.moveHistoryEntry = { move: move.id, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
|
||||||
this.lastHit = user.turnData.hitsLeft === 1 || !this.getTargets().some(t => t.isActive(true));
|
|
||||||
|
|
||||||
const playOnEmptyField = this.scene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
|
targets.forEach((t, i) => this.hitChecks[i] = this.hitCheck(t));
|
||||||
return new MoveAnim(move.id as Moves, user, this.getFirstTarget()!.getBattlerIndex()!, playOnEmptyField).play(this.scene, move.hitsSubstitute(user, this.getFirstTarget()!), async () => {
|
|
||||||
await this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
|
|
||||||
|
|
||||||
|
if (!targets.some(t => t.isActive(true))) {
|
||||||
|
this.scene.queueMessage(i18next.t("battle:attackFailed"));
|
||||||
|
this.moveHistoryEntry.result = MoveResult.FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hitChecks.some(hc => hc[0] === HitCheckResult.HIT)) {
|
||||||
|
this.moveHistoryEntry.result = MoveResult.SUCCESS;
|
||||||
|
} else {
|
||||||
|
user.turnData.hitCount = 1;
|
||||||
|
user.turnData.hitsLeft = 1;
|
||||||
|
|
||||||
|
if (this.hitChecks.every(hc => hc[0] === HitCheckResult.MISS)) {
|
||||||
|
this.moveHistoryEntry.result = MoveResult.MISS;
|
||||||
|
} else {
|
||||||
|
this.moveHistoryEntry.result = MoveResult.FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.firstHit = user.turnData.hitCount === user.turnData.hitsLeft;
|
||||||
|
this.lastHit = user.turnData.hitsLeft === 1 || !targets.some(t => t.isActive(true));
|
||||||
|
|
||||||
|
// If the move successfully hit at least 1 target, or the move has a
|
||||||
|
// post-target effect, play the move's animation
|
||||||
|
const tryPlayAnim = (this.moveHistoryEntry.result === MoveResult.SUCCESS || move.getAttrs(MoveEffectAttr).some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET))
|
||||||
|
? this.playMoveAnim(user)
|
||||||
|
: Promise.resolve();
|
||||||
|
|
||||||
|
tryPlayAnim.then(() => {
|
||||||
|
// If this phase represents the first strike of the given move,
|
||||||
|
// log the move in the user's move history.
|
||||||
|
if (this.firstHit) {
|
||||||
|
user.pushMoveHistory(this.moveHistoryEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyPromises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
for (const target of targets) {
|
||||||
|
const [ hitCheckResult, effectiveness ] = this.hitChecks[targets.indexOf(target)];
|
||||||
|
|
||||||
|
switch (hitCheckResult) {
|
||||||
|
case HitCheckResult.HIT:
|
||||||
|
applyPromises.push(this.applyMoveEffects(target, effectiveness));
|
||||||
|
break;
|
||||||
|
case HitCheckResult.NO_EFFECT:
|
||||||
|
if (move.id === Moves.SHEER_COLD) {
|
||||||
|
this.scene.queueMessage(i18next.t("battle:hitResultImmune", { pokemonName: getPokemonNameWithAffix(target) }));
|
||||||
|
} else {
|
||||||
|
this.scene.queueMessage(i18next.t("battle:hitResultNoEffect", { pokemonName: getPokemonNameWithAffix(target) }));
|
||||||
|
}
|
||||||
|
case HitCheckResult.PROTECTED:
|
||||||
|
case HitCheckResult.NO_EFFECT_NO_MESSAGE:
|
||||||
|
applyMoveAttrs(NoEffectAttr, user, target, move);
|
||||||
|
break;
|
||||||
|
case HitCheckResult.MISS:
|
||||||
|
this.scene.queueMessage(i18next.t("battle:attackMissed", { pokemonNameWithAffix: this.getFirstTarget() ? getPokemonNameWithAffix(this.getFirstTarget()!) : "" }));
|
||||||
|
applyMoveAttrs(MissEffectAttr, user, target, move);
|
||||||
|
break;
|
||||||
|
case HitCheckResult.PENDING:
|
||||||
|
case HitCheckResult.ERROR:
|
||||||
|
console.log(`Unexpected hit check result ${HitCheckResult[hitCheckResult]}. Aborting phase.`);
|
||||||
|
return this.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.allSettled(applyPromises)
|
||||||
|
.then(() => executeIf(this.lastHit, () => this.triggerMoveEffects(MoveEffectTrigger.POST_TARGET, user, null)))
|
||||||
|
.then(() => {
|
||||||
|
this.updateSubstitutes();
|
||||||
|
this.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected playMoveAnim(user: Pokemon): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const move = this.move.getMove();
|
||||||
|
const firstTargetPokemon = this.getFirstTarget() ?? null;
|
||||||
|
const playOnEmptyField = this.scene.currentBattle?.mysteryEncounter?.hasBattleAnimationsWithoutTargets ?? false;
|
||||||
|
new MoveAnim(move.id, user, firstTargetPokemon!.getBattlerIndex(), playOnEmptyField)
|
||||||
|
.play(this.scene, move.hitsSubstitute(user, firstTargetPokemon), () => resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected applyMoveEffects(target: Pokemon, effectiveness: TypeDamageMultiplier): Promise<void> {
|
||||||
|
const user = this.getUserPokemon();
|
||||||
|
const move = this.move.getMove();
|
||||||
|
|
||||||
|
const firstTarget = target === this.getTargets().find((_, i) => this.hitChecks[i][1] > 0);
|
||||||
|
|
||||||
|
if (isNullOrUndefined(user)) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent field-targeted moves from activating multiple times
|
||||||
|
if (move.isFieldTarget() && target !== this.getTargets()[this.targets.length - 1]) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target).then(() => {
|
||||||
const hitResult = this.applyMove(target, effectiveness);
|
const hitResult = this.applyMove(target, effectiveness);
|
||||||
|
|
||||||
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||||
@ -255,19 +265,20 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
HitResult.ONE_HIT_KO
|
HitResult.ONE_HIT_KO
|
||||||
].includes(hitResult);
|
].includes(hitResult);
|
||||||
|
|
||||||
await this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target);
|
return this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget)
|
||||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
.then(() => this.applyHeldItemFlinchCheck(user, target, dealsDamage))
|
||||||
await this.triggerMoveEffects(MoveEffectTrigger.HIT, user, target);
|
.then(() => this.triggerMoveEffects(MoveEffectTrigger.HIT, user, target, firstTarget))
|
||||||
await this.applyOnGetHitAbEffects(user, target, hitResult);
|
.then(() => this.applyOnGetHitAbEffects(user, target, hitResult))
|
||||||
await applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult);
|
.then(() => applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult))
|
||||||
|
.then(() => {
|
||||||
|
if (move instanceof AttackMove) {
|
||||||
|
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
|
||||||
|
}
|
||||||
|
|
||||||
if (move instanceof AttackMove) {
|
if (this.lastHit) {
|
||||||
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
|
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (this.lastHit) {
|
|
||||||
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,18 +318,19 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param triggerType The {@linkcode MoveEffectTrigger} being applied
|
* @param triggerType The {@linkcode MoveEffectTrigger} being applied
|
||||||
* @param user The {@linkcode Pokemon} using the move
|
* @param user The {@linkcode Pokemon} using the move
|
||||||
* @param target The {@linkcode Pokemon} targeted by the move
|
* @param target The {@linkcode Pokemon} targeted by the move
|
||||||
|
* @param firstTarget Whether the target is the first to be hit by the current strike
|
||||||
* @param selfTarget If defined, limits the effects triggered to either self-targeted
|
* @param selfTarget If defined, limits the effects triggered to either self-targeted
|
||||||
* effects (if set to `true`) or targeted effects (if set to `false`).
|
* effects (if set to `true`) or targeted effects (if set to `false`).
|
||||||
* @returns a `Promise` applying the relevant move effects.
|
* @returns a `Promise` applying the relevant move effects.
|
||||||
*/
|
*/
|
||||||
protected triggerMoveEffects(triggerType: MoveEffectTrigger, user: Pokemon, target: Pokemon | null, selfTarget?: boolean): Promise<void> {
|
protected triggerMoveEffects(triggerType: MoveEffectTrigger, user: Pokemon, target: Pokemon | null, firstTarget?: boolean | null, selfTarget?: boolean): Promise<void> {
|
||||||
return applyFilteredMoveAttrs((attr: MoveAttr) =>
|
return applyFilteredMoveAttrs((attr: MoveAttr) =>
|
||||||
attr instanceof MoveEffectAttr
|
attr instanceof MoveEffectAttr
|
||||||
&& attr.trigger === triggerType
|
&& attr.trigger === triggerType
|
||||||
&& (isNullOrUndefined(selfTarget) || (attr.selfTarget === selfTarget))
|
&& (isNullOrUndefined(selfTarget) || (attr.selfTarget === selfTarget))
|
||||||
&& (!attr.firstHitOnly || this.firstHit)
|
&& (!attr.firstHitOnly || this.firstHit)
|
||||||
&& (!attr.lastHitOnly || this.lastHit)
|
&& (!attr.lastHitOnly || this.lastHit)
|
||||||
&& (!attr.firstTargetOnly || this.firstTarget),
|
&& (!attr.firstTargetOnly || (firstTarget ?? true)),
|
||||||
user, target, this.move.getMove());
|
user, target, this.move.getMove());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,7 +577,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alwaysHit || target.getTag(BattlerTagType.TELEKINESIS) && !move.hasAttr(OneHitKOAttr)) {
|
if (alwaysHit || (target.getTag(BattlerTagType.TELEKINESIS) && !move.hasAttr(OneHitKOAttr))) {
|
||||||
return [ HitCheckResult.HIT, effectiveness ];
|
return [ HitCheckResult.HIT, effectiveness ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import { Type } from "#enums/type";
|
|||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Moves } from "#app/enums/moves";
|
import { Moves } from "#app/enums/moves";
|
||||||
import { Species } from "#app/enums/species";
|
import { Species } from "#app/enums/species";
|
||||||
import { HitResult } from "#app/field/pokemon";
|
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
@ -39,13 +38,13 @@ describe("Abilities - Galvanize", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should change Normal-type attacks to Electric type and boost their power", async () => {
|
it("should change Normal-type attacks to Electric type and boost their power", async () => {
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
vi.spyOn(playerPokemon, "getMoveType");
|
vi.spyOn(playerPokemon, "getMoveType");
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
vi.spyOn(enemyPokemon, "apply");
|
vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
const move = allMoves[Moves.TACKLE];
|
const move = allMoves[Moves.TACKLE];
|
||||||
vi.spyOn(move, "calculateBattlePower");
|
vi.spyOn(move, "calculateBattlePower");
|
||||||
@ -55,7 +54,7 @@ describe("Abilities - Galvanize", () => {
|
|||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(Type.ELECTRIC);
|
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(Type.ELECTRIC);
|
||||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.EFFECTIVE);
|
expect(enemyPokemon.getMoveEffectiveness).toHaveReturnedWith(1);
|
||||||
expect(move.calculateBattlePower).toHaveReturnedWith(48);
|
expect(move.calculateBattlePower).toHaveReturnedWith(48);
|
||||||
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
|
||||||
});
|
});
|
||||||
@ -63,13 +62,13 @@ describe("Abilities - Galvanize", () => {
|
|||||||
it("should cause Normal-type attacks to activate Volt Absorb", async () => {
|
it("should cause Normal-type attacks to activate Volt Absorb", async () => {
|
||||||
game.override.enemyAbility(Abilities.VOLT_ABSORB);
|
game.override.enemyAbility(Abilities.VOLT_ABSORB);
|
||||||
|
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
vi.spyOn(playerPokemon, "getMoveType");
|
vi.spyOn(playerPokemon, "getMoveType");
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
vi.spyOn(enemyPokemon, "apply");
|
vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8);
|
enemyPokemon.hp = Math.floor(enemyPokemon.getMaxHp() * 0.8);
|
||||||
|
|
||||||
@ -78,37 +77,37 @@ describe("Abilities - Galvanize", () => {
|
|||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(Type.ELECTRIC);
|
expect(playerPokemon.getMoveType).toHaveLastReturnedWith(Type.ELECTRIC);
|
||||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT);
|
expect(enemyPokemon.getMoveEffectiveness).toHaveReturnedWith(0);
|
||||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not change the type of variable-type moves", async () => {
|
it("should not change the type of variable-type moves", async () => {
|
||||||
game.override.enemySpecies(Species.MIGHTYENA);
|
game.override.enemySpecies(Species.MIGHTYENA);
|
||||||
|
|
||||||
await game.startBattle([ Species.ESPEON ]);
|
await game.classicMode.startBattle([ Species.ESPEON ]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
vi.spyOn(playerPokemon, "getMoveType");
|
vi.spyOn(playerPokemon, "getMoveType");
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
vi.spyOn(enemyPokemon, "apply");
|
vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
game.move.select(Moves.REVELATION_DANCE);
|
game.move.select(Moves.REVELATION_DANCE);
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(Type.ELECTRIC);
|
expect(playerPokemon.getMoveType).not.toHaveLastReturnedWith(Type.ELECTRIC);
|
||||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.NO_EFFECT);
|
expect(enemyPokemon.getMoveEffectiveness).toHaveReturnedWith(0);
|
||||||
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should affect all hits of a Normal-type multi-hit move", async () => {
|
it("should affect all hits of a Normal-type multi-hit move", async () => {
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
vi.spyOn(playerPokemon, "getMoveType");
|
vi.spyOn(playerPokemon, "getMoveType");
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
vi.spyOn(enemyPokemon, "apply");
|
vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
game.move.select(Moves.FURY_SWIPES);
|
game.move.select(Moves.FURY_SWIPES);
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
@ -126,6 +125,6 @@ describe("Abilities - Galvanize", () => {
|
|||||||
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp);
|
expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(enemyPokemon.apply).not.toHaveReturnedWith(HitResult.NO_EFFECT);
|
expect(enemyPokemon.getMoveEffectiveness).not.toHaveReturnedWith(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { HitCheckResult, MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
import { MoveEndPhase } from "#app/phases/move-end-phase";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -28,6 +28,7 @@ describe("Abilities - No Guard", () => {
|
|||||||
.moveset(Moves.ZAP_CANNON)
|
.moveset(Moves.ZAP_CANNON)
|
||||||
.ability(Abilities.NO_GUARD)
|
.ability(Abilities.NO_GUARD)
|
||||||
.enemyLevel(200)
|
.enemyLevel(200)
|
||||||
|
.enemySpecies(Species.SNORLAX)
|
||||||
.enemyAbility(Abilities.BALL_FETCH)
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
.enemyMoveset(Moves.SPLASH);
|
.enemyMoveset(Moves.SPLASH);
|
||||||
});
|
});
|
||||||
@ -50,7 +51,7 @@ describe("Abilities - No Guard", () => {
|
|||||||
|
|
||||||
await game.phaseInterceptor.to(MoveEndPhase);
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
expect(moveEffectPhase.hitCheck).toHaveReturnedWith(true);
|
expect(moveEffectPhase.hitCheck).toHaveReturnedWith([ HitCheckResult.HIT, 1 ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should guarantee double battle with any one LURE", async () => {
|
it("should guarantee double battle with any one LURE", async () => {
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability";
|
import { applyAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr } from "#app/data/ability";
|
||||||
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
|
||||||
import { NumberHolder } from "#app/utils";
|
import { NumberHolder } from "#app/utils";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import { Stat } from "#enums/stat";
|
import { Stat } from "#enums/stat";
|
||||||
|
import { Type } from "#enums/type";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { HitResult } from "#app/field/pokemon";
|
|
||||||
|
|
||||||
describe("Abilities - Sheer Force", () => {
|
describe("Abilities - Sheer Force", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -130,40 +131,25 @@ describe("Abilities - Sheer Force", () => {
|
|||||||
|
|
||||||
it("Sheer Force Disabling Specific Abilities", async () => {
|
it("Sheer Force Disabling Specific Abilities", async () => {
|
||||||
const moveToUse = Moves.CRUSH_CLAW;
|
const moveToUse = Moves.CRUSH_CLAW;
|
||||||
game.override.enemyAbility(Abilities.COLOR_CHANGE);
|
game.override
|
||||||
game.override.startingHeldItems([{ name: "KINGS_ROCK", count: 1 }]);
|
.startingLevel(100)
|
||||||
game.override.ability(Abilities.SHEER_FORCE);
|
.enemyLevel(100)
|
||||||
await game.startBattle([ Species.PIDGEOT ]);
|
.enemySpecies(Species.SHUCKLE)
|
||||||
|
.enemyAbility(Abilities.COLOR_CHANGE)
|
||||||
|
.ability(Abilities.SHEER_FORCE);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([ Species.PIDGEOT ]);
|
||||||
|
|
||||||
game.scene.getEnemyPokemon()!.stats[Stat.DEF] = 10000;
|
const player = game.scene.getPlayerPokemon()!;
|
||||||
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(0);
|
const enemy = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
game.move.select(moveToUse);
|
||||||
|
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
await game.move.forceHit();
|
||||||
|
await game.phaseInterceptor.to("MoveEndPhase", 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, HitResult.EFFECTIVE);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
expect(player.battleData.abilitiesApplied.includes(Abilities.SHEER_FORCE)).toBeTruthy();
|
||||||
|
expect(enemy.getTypes()).toEqual([ Type.BUG, Type.ROCK ]);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
|
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
|
||||||
|
@ -2,7 +2,6 @@ import { BattlerIndex } from "#app/battle";
|
|||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { Moves } from "#app/enums/moves";
|
import { Moves } from "#app/enums/moves";
|
||||||
import { Species } from "#app/enums/species";
|
import { Species } from "#app/enums/species";
|
||||||
import { HitResult } from "#app/field/pokemon";
|
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
@ -98,12 +97,12 @@ describe("Abilities - Tera Shell", () => {
|
|||||||
await game.classicMode.startBattle([ Species.CHARIZARD ]);
|
await game.classicMode.startBattle([ Species.CHARIZARD ]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
vi.spyOn(playerPokemon, "apply");
|
vi.spyOn(playerPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
await game.phaseInterceptor.to("BerryPhase", false);
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.EFFECTIVE);
|
expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(1);
|
||||||
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40);
|
expect(playerPokemon.hp).toBe(playerPokemon.getMaxHp() - 40);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -116,7 +115,9 @@ describe("Abilities - Tera Shell", () => {
|
|||||||
await game.classicMode.startBattle([ Species.SNORLAX ]);
|
await game.classicMode.startBattle([ Species.SNORLAX ]);
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
vi.spyOn(playerPokemon, "apply");
|
vi.spyOn(playerPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
|
|
||||||
game.move.select(Moves.SPLASH);
|
game.move.select(Moves.SPLASH);
|
||||||
|
|
||||||
@ -124,9 +125,9 @@ describe("Abilities - Tera Shell", () => {
|
|||||||
await game.move.forceHit();
|
await game.move.forceHit();
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
expect(playerPokemon.apply).toHaveLastReturnedWith(HitResult.NOT_VERY_EFFECTIVE);
|
expect(playerPokemon.getMoveEffectiveness).toHaveLastReturnedWith(0.5);
|
||||||
}
|
}
|
||||||
expect(playerPokemon.apply).toHaveReturnedTimes(2);
|
expect(enemyPokemon.turnData.hitCount).toBe(2);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -36,8 +36,7 @@ describe("Items - Dire Hit", () => {
|
|||||||
.enemyMoveset(Moves.SPLASH)
|
.enemyMoveset(Moves.SPLASH)
|
||||||
.moveset([ Moves.POUND ])
|
.moveset([ Moves.POUND ])
|
||||||
.startingHeldItems([{ name: "DIRE_HIT" }])
|
.startingHeldItems([{ name: "DIRE_HIT" }])
|
||||||
.battleType("single")
|
.battleType("single");
|
||||||
.disableCrits();
|
|
||||||
|
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ describe("Items - Leek", () => {
|
|||||||
.enemyMoveset([ Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH ])
|
.enemyMoveset([ Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH ])
|
||||||
.startingHeldItems([{ name: "LEEK" }])
|
.startingHeldItems([{ name: "LEEK" }])
|
||||||
.moveset([ Moves.TACKLE ])
|
.moveset([ Moves.TACKLE ])
|
||||||
.disableCrits()
|
|
||||||
.battleType("single");
|
.battleType("single");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,8 +27,7 @@ describe("Items - Scope Lens", () => {
|
|||||||
.enemyMoveset(Moves.SPLASH)
|
.enemyMoveset(Moves.SPLASH)
|
||||||
.moveset([ Moves.POUND ])
|
.moveset([ Moves.POUND ])
|
||||||
.startingHeldItems([{ name: "SCOPE_LENS" }])
|
.startingHeldItems([{ name: "SCOPE_LENS" }])
|
||||||
.battleType("single")
|
.battleType("single");
|
||||||
.disableCrits();
|
|
||||||
|
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import { Stat } from "#enums/stat";
|
|||||||
import { allMoves } from "#app/data/move";
|
import { allMoves } from "#app/data/move";
|
||||||
import { Type } from "#enums/type";
|
import { Type } from "#enums/type";
|
||||||
import { Abilities } from "#app/enums/abilities";
|
import { Abilities } from "#app/enums/abilities";
|
||||||
import { HitResult } from "#app/field/pokemon";
|
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
import { Species } from "#enums/species";
|
import { Species } from "#enums/species";
|
||||||
import GameManager from "#test/utils/gameManager";
|
import GameManager from "#test/utils/gameManager";
|
||||||
@ -47,21 +46,21 @@ describe("Moves - Tera Blast", () => {
|
|||||||
game.override
|
game.override
|
||||||
.enemySpecies(Species.FURRET)
|
.enemySpecies(Species.FURRET)
|
||||||
.startingHeldItems([{ name: "TERA_SHARD", type: Type.FIGHTING }]);
|
.startingHeldItems([{ name: "TERA_SHARD", type: Type.FIGHTING }]);
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
vi.spyOn(enemyPokemon, "apply");
|
vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||||
|
|
||||||
game.move.select(Moves.TERA_BLAST);
|
game.move.select(Moves.TERA_BLAST);
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE);
|
expect(enemyPokemon.getMoveEffectiveness).toHaveReturnedWith(2);
|
||||||
}, 20000);
|
}, 20000);
|
||||||
|
|
||||||
it("increases power if user is Stellar tera type", async () => {
|
it("increases power if user is Stellar tera type", async () => {
|
||||||
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
|
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
|
||||||
|
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
game.move.select(Moves.TERA_BLAST);
|
game.move.select(Moves.TERA_BLAST);
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
@ -73,23 +72,23 @@ describe("Moves - Tera Blast", () => {
|
|||||||
it("is super effective against terastallized targets if user is Stellar tera type", async () => {
|
it("is super effective against terastallized targets if user is Stellar tera type", async () => {
|
||||||
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
|
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
|
||||||
|
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||||
vi.spyOn(enemyPokemon, "apply");
|
vi.spyOn(enemyPokemon, "getMoveEffectiveness");
|
||||||
vi.spyOn(enemyPokemon, "isTerastallized").mockReturnValue(true);
|
vi.spyOn(enemyPokemon, "isTerastallized").mockReturnValue(true);
|
||||||
|
|
||||||
game.move.select(Moves.TERA_BLAST);
|
game.move.select(Moves.TERA_BLAST);
|
||||||
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
|
||||||
await game.phaseInterceptor.to("MoveEffectPhase");
|
await game.phaseInterceptor.to("MoveEffectPhase");
|
||||||
|
|
||||||
expect(enemyPokemon.apply).toHaveReturnedWith(HitResult.SUPER_EFFECTIVE);
|
expect(enemyPokemon.getMoveEffectiveness).toHaveReturnedWith(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Currently abilities are bugged and can't see when a move's category is changed
|
// Currently abilities are bugged and can't see when a move's category is changed
|
||||||
it.skip("uses the higher stat of the user's Atk and SpAtk for damage calculation", async () => {
|
it.skip("uses the higher stat of the user's Atk and SpAtk for damage calculation", async () => {
|
||||||
game.override.enemyAbility(Abilities.TOXIC_DEBRIS);
|
game.override.enemyAbility(Abilities.TOXIC_DEBRIS);
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
playerPokemon.stats[Stat.ATK] = 100;
|
playerPokemon.stats[Stat.ATK] = 100;
|
||||||
@ -102,7 +101,7 @@ describe("Moves - Tera Blast", () => {
|
|||||||
|
|
||||||
it("causes stat drops if user is Stellar tera type", async () => {
|
it("causes stat drops if user is Stellar tera type", async () => {
|
||||||
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
|
game.override.startingHeldItems([{ name: "TERA_SHARD", type: Type.STELLAR }]);
|
||||||
await game.startBattle();
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
@ -14,26 +14,24 @@ import { vi } from "vitest";
|
|||||||
*/
|
*/
|
||||||
export class MoveHelper extends GameManagerHelper {
|
export class MoveHelper extends GameManagerHelper {
|
||||||
/**
|
/**
|
||||||
* Intercepts {@linkcode MoveEffectPhase} and mocks the
|
* Intercepts {@linkcode MoveEffectPhase} and mocks the phase's move's
|
||||||
* {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `true`.
|
* accuracy to -1, guaranteeing a hit.
|
||||||
* Used to force a move to hit.
|
|
||||||
*/
|
*/
|
||||||
public async forceHit(): Promise<void> {
|
public async forceHit(): Promise<void> {
|
||||||
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
|
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase;
|
const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||||
vi.spyOn(moveEffectPhase.move.getMove(), "accuracy", "get").mockReturnValue(-1);
|
vi.spyOn(moveEffectPhase.move.getMove(), "calculateBattleAccuracy").mockReturnValue(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intercepts {@linkcode MoveEffectPhase} and mocks the
|
* Intercepts {@linkcode MoveEffectPhase} and mocks the phase's move's accuracy
|
||||||
* {@linkcode MoveEffectPhase.hitCheck | hitCheck}'s return value to `false`.
|
* to 0, guaranteeing a miss.
|
||||||
* Used to force a move to miss.
|
|
||||||
* @param firstTargetOnly - Whether the move should force miss on the first target only, in the case of multi-target moves.
|
* @param firstTargetOnly - Whether the move should force miss on the first target only, in the case of multi-target moves.
|
||||||
*/
|
*/
|
||||||
public async forceMiss(firstTargetOnly: boolean = false): Promise<void> {
|
public async forceMiss(firstTargetOnly: boolean = false): Promise<void> {
|
||||||
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
|
await this.game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||||
const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase;
|
const moveEffectPhase = this.game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||||
const accuracy = vi.spyOn(moveEffectPhase.move.getMove(), "accuracy", "get");
|
const accuracy = vi.spyOn(moveEffectPhase.move.getMove(), "calculateBattleAccuracy");
|
||||||
|
|
||||||
if (firstTargetOnly) {
|
if (firstTargetOnly) {
|
||||||
accuracy.mockReturnValueOnce(0);
|
accuracy.mockReturnValueOnce(0);
|
||||||
|
Loading…
Reference in New Issue
Block a user