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 stat: BattleStat;
|
||||||
private stages: number;
|
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 {
|
export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
|
||||||
@ -6417,7 +6420,7 @@ const AbilityAttrs = Object.freeze({
|
|||||||
PostDefendContactApplyStatusEffectAbAttr,
|
PostDefendContactApplyStatusEffectAbAttr,
|
||||||
EffectSporeAbAttr,
|
EffectSporeAbAttr,
|
||||||
PostDefendContactApplyTagChanceAbAttr,
|
PostDefendContactApplyTagChanceAbAttr,
|
||||||
PostDefendCritStatStageChangeAbAttr,
|
PostReceiveCritStatStageChangeAbAttr,
|
||||||
PostDefendContactDamageAbAttr,
|
PostDefendContactDamageAbAttr,
|
||||||
PostDefendPerishSongAbAttr,
|
PostDefendPerishSongAbAttr,
|
||||||
PostDefendWeatherChangeAbAttr,
|
PostDefendWeatherChangeAbAttr,
|
||||||
@ -6886,7 +6889,7 @@ export function initAbilities() {
|
|||||||
new Ability(AbilityId.GLUTTONY, 4)
|
new Ability(AbilityId.GLUTTONY, 4)
|
||||||
.attr(ReduceBerryUseThresholdAbAttr),
|
.attr(ReduceBerryUseThresholdAbAttr),
|
||||||
new Ability(AbilityId.ANGER_POINT, 4)
|
new Ability(AbilityId.ANGER_POINT, 4)
|
||||||
.attr(PostDefendCritStatStageChangeAbAttr, Stat.ATK, 6),
|
.attr(PostReceiveCritStatStageChangeAbAttr, Stat.ATK, 12),
|
||||||
new Ability(AbilityId.UNBURDEN, 4)
|
new Ability(AbilityId.UNBURDEN, 4)
|
||||||
.attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN)
|
.attr(PostItemLostApplyBattlerTagAbAttr, BattlerTagType.UNBURDEN)
|
||||||
.bypassFaint() // Allows reviver seed to activate 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 user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - {@linkcode Pokemon} the current target of 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 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 {
|
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, wasCritical = false): void {
|
||||||
applyAbAttrs("PostDefendAbAttr", { pokemon: target, opponent: user, move: this.move, hitResult });
|
const params = { pokemon: target, opponent: user, move: this.move, hitResult };
|
||||||
|
applyAbAttrs("PostDefendAbAttr", params);
|
||||||
|
|
||||||
|
if (wasCritical) {
|
||||||
|
applyAbAttrs("PostReceiveCritStatStageChangeAbAttr", params);
|
||||||
|
}
|
||||||
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -788,12 +794,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
|
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).
|
// Apply effects to the user (always) and the target (if not blocked by substitute).
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
||||||
if (!this.move.hitsSubstitute(user, target)) {
|
if (!this.move.hitsSubstitute(user, target)) {
|
||||||
this.applyOnTargetEffects(user, target, hitResult, firstTarget);
|
this.applyOnTargetEffects(user, target, hitResult, firstTarget, wasCritical);
|
||||||
}
|
}
|
||||||
if (this.lastHit) {
|
if (this.lastHit) {
|
||||||
globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
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 user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||||
* @param effectiveness - The effectiveness of the move against the target
|
* @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);
|
const isCritical = target.getCriticalHitResult(user, this.move);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -845,7 +852,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
const isOneHitKo = result === HitResult.ONE_HIT_KO;
|
const isOneHitKo = result === HitResult.ONE_HIT_KO;
|
||||||
|
|
||||||
if (!dmg) {
|
if (!dmg) {
|
||||||
return result;
|
return [result, false];
|
||||||
}
|
}
|
||||||
|
|
||||||
target.lapseTags(BattlerTagLapseType.HIT);
|
target.lapseTags(BattlerTagLapseType.HIT);
|
||||||
@ -873,7 +880,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (damage <= 0) {
|
if (damage <= 0) {
|
||||||
return result;
|
return [result, isCritical];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isPlayer()) {
|
if (user.isPlayer()) {
|
||||||
@ -902,7 +909,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage));
|
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 target - The {@linkcode Pokemon} struck by the move
|
||||||
* @param effectiveness - The effectiveness of the move against the target
|
* @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);
|
const moveCategory = user.getMoveCategory(target, this.move);
|
||||||
|
|
||||||
if (moveCategory === MoveCategory.STATUS) {
|
if (moveCategory === MoveCategory.STATUS) {
|
||||||
return HitResult.STATUS;
|
return [HitResult.STATUS, false];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = this.applyMoveDamage(user, target, effectiveness);
|
const result = this.applyMoveDamage(user, target, effectiveness);
|
||||||
|
|
||||||
if (user.turnData.hitsLeft === 1 || target.isFainted()) {
|
if (user.turnData.hitsLeft === 1 || target.isFainted()) {
|
||||||
this.queueHitResultMessage(result);
|
this.queueHitResultMessage(result[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.isFainted()) {
|
if (target.isFainted()) {
|
||||||
@ -983,8 +990,15 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||||
* @param hitResult - The {@linkcode HitResult} obtained from applying 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 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? */
|
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||||
const dealsDamage = [
|
const dealsDamage = [
|
||||||
HitResult.EFFECTIVE,
|
HitResult.EFFECTIVE,
|
||||||
@ -995,7 +1009,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
||||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
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 });
|
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult });
|
||||||
|
|
||||||
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
// 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