[Bug] [Ability] Fix berserk multi proc (#6402)

* Fix berserk activating multiple times with multi strike

* Apply kev's suggestions from code review

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Update doc comments

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Sirz Benjie 2025-08-24 19:47:52 -05:00 committed by GitHub
parent 6fac1a5052
commit 4b8c064335
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 37 additions and 21 deletions

View File

@ -970,6 +970,8 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr {
export interface PostMoveInteractionAbAttrParams extends AugmentMoveInteractionAbAttrParams { export interface PostMoveInteractionAbAttrParams extends AugmentMoveInteractionAbAttrParams {
/** Stores the hit result of the move used in the interaction */ /** Stores the hit result of the move used in the interaction */
readonly hitResult: HitResult; readonly hitResult: HitResult;
/** The amount of damage dealt in the interaction */
readonly damage: number;
} }
export class PostDefendAbAttr extends AbAttr { export class PostDefendAbAttr extends AbAttr {
@ -1079,20 +1081,16 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
} }
override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean { override canApply({ pokemon, opponent: attacker, move, damage }: PostMoveInteractionAbAttrParams): boolean {
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate); const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; return this.condition(pokemon, attacker, move) && pokemon.hp <= hpGateFlat && pokemon.hp + damage > hpGateFlat;
const damageReceived = lastAttackReceived?.damage || 0;
return (
this.condition(pokemon, attacker, move) && pokemon.hp <= hpGateFlat && pokemon.hp + damageReceived > hpGateFlat
);
} }
override apply({ simulated, pokemon, opponent: attacker }: PostMoveInteractionAbAttrParams): void { override apply({ simulated, pokemon, opponent }: PostMoveInteractionAbAttrParams): void {
if (!simulated) { if (!simulated) {
globalScene.phaseManager.unshiftNew( globalScene.phaseManager.unshiftNew(
"StatStageChangePhase", "StatStageChangePhase",
(this.selfTarget ? pokemon : attacker).getBattlerIndex(), (this.selfTarget ? pokemon : opponent).getBattlerIndex(),
true, true,
this.stats, this.stats,
this.stages, this.stages,

View File

@ -400,10 +400,17 @@ 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 damage - The amount of damage dealt to the target in the interaction
* @param wasCritical - `true` if the move was a critical hit * @param wasCritical - `true` if the move was a critical hit
*/ */
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, wasCritical = false): void { protected applyOnGetHitAbEffects(
const params = { pokemon: target, opponent: user, move: this.move, hitResult }; user: Pokemon,
target: Pokemon,
hitResult: HitResult,
damage: number,
wasCritical = false,
): void {
const params = { pokemon: target, opponent: user, move: this.move, hitResult, damage };
applyAbAttrs("PostDefendAbAttr", params); applyAbAttrs("PostDefendAbAttr", params);
if (wasCritical) { if (wasCritical) {
@ -763,12 +770,12 @@ export class MoveEffectPhase extends PokemonPhase {
this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target); this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
const [hitResult, wasCritical] = this.applyMove(user, target, effectiveness); const [hitResult, wasCritical, dmg] = 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, wasCritical); this.applyOnTargetEffects(user, target, hitResult, firstTarget, dmg, wasCritical);
} }
if (this.lastHit) { if (this.lastHit) {
globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger); globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
@ -788,9 +795,13 @@ 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 * @returns The {@linkcode HitResult} of the move against the target, a boolean indicating whether the target was crit, and the amount of damage dealt
*/ */
protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] { protected applyMoveDamage(
user: Pokemon,
target: Pokemon,
effectiveness: TypeDamageMultiplier,
): [result: HitResult, critical: boolean, damage: number] {
const isCritical = target.getCriticalHitResult(user, this.move); const isCritical = target.getCriticalHitResult(user, this.move);
/* /*
@ -821,7 +832,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, false]; return [result, false, 0];
} }
target.lapseTags(BattlerTagLapseType.HIT); target.lapseTags(BattlerTagLapseType.HIT);
@ -850,7 +861,7 @@ export class MoveEffectPhase extends PokemonPhase {
} }
if (damage <= 0) { if (damage <= 0) {
return [result, isCritical]; return [result, isCritical, damage];
} }
if (user.isPlayer()) { if (user.isPlayer()) {
@ -879,7 +890,7 @@ export class MoveEffectPhase extends PokemonPhase {
globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage)); globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage));
} }
return [result, isCritical]; return [result, isCritical, damage];
} }
/** /**
@ -932,12 +943,17 @@ 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} 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
* @returns The {@linkcode HitResult} of the move against the target, a boolean indicating whether the target was crit, and the amount of damage dealt
*/ */
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] { protected applyMove(
user: Pokemon,
target: Pokemon,
effectiveness: TypeDamageMultiplier,
): [HitResult, critical: boolean, damage: number] {
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, false]; return [HitResult.STATUS, false, 0];
} }
const result = this.applyMoveDamage(user, target, effectiveness); const result = this.applyMoveDamage(user, target, effectiveness);
@ -960,6 +976,7 @@ 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 damage - The amount of damage dealt to the target in the interaction
* @param wasCritical - `true` if the move was a critical hit * @param wasCritical - `true` if the move was a critical hit
*/ */
protected applyOnTargetEffects( protected applyOnTargetEffects(
@ -967,6 +984,7 @@ export class MoveEffectPhase extends PokemonPhase {
target: Pokemon, target: Pokemon,
hitResult: HitResult, hitResult: HitResult,
firstTarget: boolean, firstTarget: boolean,
damage: number,
wasCritical = false, wasCritical = false,
): void { ): void {
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */ /** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
@ -979,8 +997,8 @@ 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, wasCritical); this.applyOnGetHitAbEffects(user, target, hitResult, damage, wasCritical);
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult }); applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult, damage: damage });
// 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
if (!user.isPlayer() && this.move.is("AttackMove")) { if (!user.isPlayer() && this.move.is("AttackMove")) {