mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-11 19:02:16 +02:00
[Bug] Fix anger point procing on every hit if first hit in multi hit was a crit
https://github.com/pagefaultgames/pokerogue/pull/6067 * Fix anger point always procing on multi-hit when first strike was a crit * Fix comment spacing Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Rename PostDefendCritStatStageChangeAbAttr --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
e82c788585
commit
115d63d0c5
@ -1194,7 +1194,16 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
/**
|
||||
* Set stat stages when the user gets hit by a critical hit
|
||||
*
|
||||
* @privateremarks
|
||||
* It is the responsibility of the caller to ensure that this ability attribute is only applied
|
||||
* when the user has been hit by a critical hit; such an event is not checked here.
|
||||
*
|
||||
* @sealed
|
||||
*/
|
||||
export class PostReceiveCritStatStageChangeAbAttr extends AbAttr {
|
||||
private stat: BattleStat;
|
||||
private stages: number;
|
||||
|
||||
@ -1216,12 +1225,6 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override getCondition(): AbAttrCondition {
|
||||
return (pokemon: Pokemon) =>
|
||||
pokemon.turnData.attacksReceived.length !== 0 &&
|
||||
pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
||||
@ -6417,7 +6420,7 @@ const AbilityAttrs = Object.freeze({
|
||||
PostDefendContactApplyStatusEffectAbAttr,
|
||||
EffectSporeAbAttr,
|
||||
PostDefendContactApplyTagChanceAbAttr,
|
||||
PostDefendCritStatStageChangeAbAttr,
|
||||
PostReceiveCritStatStageChangeAbAttr,
|
||||
PostDefendContactDamageAbAttr,
|
||||
PostDefendPerishSongAbAttr,
|
||||
PostDefendWeatherChangeAbAttr,
|
||||
@ -6886,7 +6889,7 @@ export function initAbilities() {
|
||||
new Ability(AbilityId.GLUTTONY, 4)
|
||||
.attr(ReduceBerryUseThresholdAbAttr),
|
||||
new Ability(AbilityId.ANGER_POINT, 4)
|
||||
.attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6),
|
||||
.attr(PostReceiveCritStatStageChangeAbAttr, Stat.ATK, 12),
|
||||
new Ability(AbilityId.UNBURDEN, 4)
|
||||
.attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN)
|
||||
.bypassFaint() // Allows reviver seed to activate Unburden
|
||||
|
@ -432,9 +432,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||
* @param wasCritical - `true` if the move was a critical hit
|
||||
*/
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void {
|
||||
applyAbAttrs("PostDefendAbAttr", { pokemon: target, opponent: user, move: this.move, hitResult });
|
||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, wasCritical = false): void {
|
||||
const params = { pokemon: target, opponent: user, move: this.move, hitResult };
|
||||
applyAbAttrs("PostDefendAbAttr", params);
|
||||
|
||||
if (wasCritical) {
|
||||
applyAbAttrs("PostReceiveCritStatStageChangeAbAttr", params);
|
||||
}
|
||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||
}
|
||||
|
||||
@ -788,12 +794,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
|
||||
this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
|
||||
|
||||
const hitResult = this.applyMove(user, target, effectiveness);
|
||||
const [hitResult, wasCritical] = this.applyMove(user, target, effectiveness);
|
||||
|
||||
// Apply effects to the user (always) and the target (if not blocked by substitute).
|
||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
||||
if (!this.move.hitsSubstitute(user, target)) {
|
||||
this.applyOnTargetEffects(user, target, hitResult, firstTarget);
|
||||
this.applyOnTargetEffects(user, target, hitResult, firstTarget, wasCritical);
|
||||
}
|
||||
if (this.lastHit) {
|
||||
globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
||||
@ -813,8 +819,9 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param effectiveness - The effectiveness of the move against the target
|
||||
* @returns The {@linkcode HitResult} of the move against the target and a boolean indicating whether the target was crit
|
||||
*/
|
||||
protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult {
|
||||
protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
||||
const isCritical = target.getCriticalHitResult(user, this.move);
|
||||
|
||||
/*
|
||||
@ -845,7 +852,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
const isOneHitKo = result === HitResult.ONE_HIT_KO;
|
||||
|
||||
if (!dmg) {
|
||||
return result;
|
||||
return [result, false];
|
||||
}
|
||||
|
||||
target.lapseTags(BattlerTagLapseType.HIT);
|
||||
@ -873,7 +880,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
}
|
||||
|
||||
if (damage <= 0) {
|
||||
return result;
|
||||
return [result, isCritical];
|
||||
}
|
||||
|
||||
if (user.isPlayer()) {
|
||||
@ -902,7 +909,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage));
|
||||
}
|
||||
|
||||
return result;
|
||||
return [result, isCritical];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -956,17 +963,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param target - The {@linkcode Pokemon} struck by the move
|
||||
* @param effectiveness - The effectiveness of the move against the target
|
||||
*/
|
||||
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): HitResult {
|
||||
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
||||
const moveCategory = user.getMoveCategory(target, this.move);
|
||||
|
||||
if (moveCategory === MoveCategory.STATUS) {
|
||||
return HitResult.STATUS;
|
||||
return [HitResult.STATUS, false];
|
||||
}
|
||||
|
||||
const result = this.applyMoveDamage(user, target, effectiveness);
|
||||
|
||||
if (user.turnData.hitsLeft === 1 || target.isFainted()) {
|
||||
this.queueHitResultMessage(result);
|
||||
this.queueHitResultMessage(result[0]);
|
||||
}
|
||||
|
||||
if (target.isFainted()) {
|
||||
@ -983,8 +990,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||
* @param hitResult - The {@linkcode HitResult} obtained from applying the move
|
||||
* @param firstTarget - `true` if the target is the first Pokemon hit by the attack
|
||||
* @param wasCritical - `true` if the move was a critical hit
|
||||
*/
|
||||
protected applyOnTargetEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, firstTarget: boolean): void {
|
||||
protected applyOnTargetEffects(
|
||||
user: Pokemon,
|
||||
target: Pokemon,
|
||||
hitResult: HitResult,
|
||||
firstTarget: boolean,
|
||||
wasCritical = false,
|
||||
): void {
|
||||
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||
const dealsDamage = [
|
||||
HitResult.EFFECTIVE,
|
||||
@ -995,7 +1009,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
|
||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
||||
this.applyOnGetHitAbEffects(user, target, hitResult);
|
||||
this.applyOnGetHitAbEffects(user, target, hitResult, wasCritical);
|
||||
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult });
|
||||
|
||||
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
||||
|
78
test/abilities/anger-point.test.ts
Normal file
78
test/abilities/anger-point.test.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { PostReceiveCritStatStageChangeAbAttr } from "#app/data/abilities/ability";
|
||||
import { AbilityId } from "#enums/ability-id";
|
||||
import { MoveId } from "#enums/move-id";
|
||||
import { SpeciesId } from "#enums/species-id";
|
||||
import { Stat } from "#enums/stat";
|
||||
import GameManager from "#test/testUtils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("Ability - Anger Point", () => {
|
||||
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
|
||||
.ability(AbilityId.BALL_FETCH)
|
||||
.battleStyle("single")
|
||||
.criticalHits(false)
|
||||
.enemySpecies(SpeciesId.MAGIKARP)
|
||||
.enemyAbility(AbilityId.BALL_FETCH)
|
||||
.enemyMoveset(MoveId.SPLASH)
|
||||
.enemyLevel(100);
|
||||
});
|
||||
|
||||
it("should set the user's attack stage to +6 when hit by a critical hit", async () => {
|
||||
game.override.enemyAbility(AbilityId.ANGER_POINT).moveset(MoveId.FALSE_SWIPE);
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
|
||||
// minimize the enemy's attack stage to ensure it is always set to +6
|
||||
enemy.setStatStage(Stat.ATK, -6);
|
||||
vi.spyOn(enemy, "getCriticalHitResult").mockReturnValueOnce(true);
|
||||
game.move.select(MoveId.FALSE_SWIPE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(enemy.getStatStage(Stat.ATK)).toBe(6);
|
||||
});
|
||||
|
||||
it("should only proc once when a multi-hit move crits on the first hit", async () => {
|
||||
game.override
|
||||
.moveset(MoveId.BULLET_SEED)
|
||||
.enemyLevel(50)
|
||||
.enemyAbility(AbilityId.ANGER_POINT)
|
||||
.ability(AbilityId.SKILL_LINK);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemy, "getCriticalHitResult").mockReturnValueOnce(true);
|
||||
const angerPointSpy = vi.spyOn(PostReceiveCritStatStageChangeAbAttr.prototype, "apply");
|
||||
game.move.select(MoveId.BULLET_SEED);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(angerPointSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should set a contrary user's attack stage to -6 when hit by a critical hit", async () => {
|
||||
game.override
|
||||
.enemyAbility(AbilityId.ANGER_POINT)
|
||||
.enemyPassiveAbility(AbilityId.CONTRARY)
|
||||
.enemyHasPassiveAbility(true)
|
||||
.moveset(MoveId.FALSE_SWIPE);
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
const enemy = game.scene.getEnemyPokemon()!;
|
||||
vi.spyOn(enemy, "getCriticalHitResult").mockReturnValueOnce(true);
|
||||
enemy.setStatStage(Stat.ATK, 6);
|
||||
game.move.select(MoveId.FALSE_SWIPE);
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
expect(enemy.getStatStage(Stat.ATK)).toBe(-6);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user