mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-12-15 06:15:20 +01:00
[Bug] Fix Parental Bond reducing damage of spread moves on 2nd pokemon
https://github.com/pagefaultgames/pokerogue/pull/6743 * Fix Pollen Puff interaction with Parental Bond --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
c7b563e498
commit
e1aded9504
@ -1833,13 +1833,13 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for abilities that modify the hit count and damage of a move
|
||||
* Parameters for abilities that modify the hit count of a move.
|
||||
*/
|
||||
export interface AddSecondStrikeAbAttrParams extends Omit<AugmentMoveInteractionAbAttrParams, "opponent"> {
|
||||
/** Holder for the number of hits. May be modified by ability application */
|
||||
hitCount?: NumberHolder;
|
||||
/** Holder for the damage multiplier _of the current hit_ */
|
||||
multiplier?: NumberHolder;
|
||||
/** Holder for the number of hits. Modified by ability application */
|
||||
hitCount: NumberHolder;
|
||||
/** The Pokemon on the other side of this interaction */
|
||||
opponent: Pokemon | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1847,35 +1847,12 @@ export interface AddSecondStrikeAbAttrParams extends Omit<AugmentMoveInteraction
|
||||
* Used by {@linkcode MoveId.PARENTAL_BOND | Parental Bond}.
|
||||
*/
|
||||
export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
|
||||
/** The damage multiplier for the second strike, relative to the first */
|
||||
private readonly damageMultiplier: number;
|
||||
/**
|
||||
* @param damageMultiplier - The damage multiplier for the second strike, relative to the first
|
||||
*/
|
||||
constructor(damageMultiplier: number) {
|
||||
super(false);
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
override canApply({ pokemon, opponent, move }: AddSecondStrikeAbAttrParams): boolean {
|
||||
return move.canBeMultiStrikeEnhanced(pokemon, true, opponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the move can be multi-strike enhanced.
|
||||
*/
|
||||
override canApply({ pokemon, move }: AddSecondStrikeAbAttrParams): boolean {
|
||||
return move.canBeMultiStrikeEnhanced(pokemon, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one to the move's hit count, and, if the pokemon has only one hit left, sets the damage multiplier
|
||||
* to the damage multiplier of this ability.
|
||||
*/
|
||||
override apply({ hitCount, multiplier, pokemon }: AddSecondStrikeAbAttrParams): void {
|
||||
if (hitCount?.value) {
|
||||
hitCount.value += 1;
|
||||
}
|
||||
|
||||
if (multiplier?.value && pokemon.turnData.hitsLeft === 1) {
|
||||
multiplier.value = this.damageMultiplier;
|
||||
}
|
||||
override apply({ hitCount }: AddSecondStrikeAbAttrParams): void {
|
||||
hitCount.value += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1895,10 +1872,12 @@ export interface PreAttackModifyDamageAbAttrParams extends AugmentMoveInteractio
|
||||
* @param damageMultiplier the amount to multiply the damage by
|
||||
* @param condition the condition for this ability to be applied
|
||||
*/
|
||||
export class DamageBoostAbAttr extends PreAttackAbAttr {
|
||||
export class MoveDamageBoostAbAttr extends PreAttackAbAttr {
|
||||
private readonly damageMultiplier: number;
|
||||
private readonly condition: PokemonAttackCondition;
|
||||
|
||||
// TODO: This should not take a `PokemonAttackCondition` (with nullish parameters)
|
||||
// as it's effectively offloading nullishness checks to its child attributes
|
||||
constructor(damageMultiplier: number, condition: PokemonAttackCondition) {
|
||||
super(false);
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
@ -6657,7 +6636,7 @@ const AbilityAttrs = Object.freeze({
|
||||
MoveTypeChangeAbAttr,
|
||||
PokemonTypeChangeAbAttr,
|
||||
AddSecondStrikeAbAttr,
|
||||
DamageBoostAbAttr,
|
||||
MoveDamageBoostAbAttr,
|
||||
MovePowerBoostAbAttr,
|
||||
MoveTypePowerBoostAbAttr,
|
||||
LowHpMoveTypePowerBoostAbAttr,
|
||||
@ -7298,7 +7277,7 @@ export function initAbilities() {
|
||||
.ignorable()
|
||||
.build(),
|
||||
new AbBuilder(AbilityId.TINTED_LENS, 4)
|
||||
.attr(DamageBoostAbAttr, 2, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) <= 0.5)
|
||||
.attr(MoveDamageBoostAbAttr, 2, (user, target, move) => (target?.getMoveEffectiveness(user!, move) ?? 1) <= 0.5)
|
||||
.build(),
|
||||
new AbBuilder(AbilityId.FILTER, 4)
|
||||
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => target.getMoveEffectiveness(user, move) >= 2, 0.75)
|
||||
@ -7636,7 +7615,15 @@ export function initAbilities() {
|
||||
.attr(MoveTypeChangeAbAttr, PokemonType.FLYING, 1.2, (_user, _target, move) => move.type === PokemonType.NORMAL)
|
||||
.build(),
|
||||
new AbBuilder(AbilityId.PARENTAL_BOND, 6)
|
||||
.attr(AddSecondStrikeAbAttr, 0.25)
|
||||
.attr(AddSecondStrikeAbAttr)
|
||||
// Only multiply damage on the last strike of multi-strike moves
|
||||
.attr(MoveDamageBoostAbAttr, 0.25, (user, target, move) => (
|
||||
!!user
|
||||
&& user.turnData.hitCount > 1 // move was originally multi hit
|
||||
&& user.turnData.hitsLeft === 1 // move is on its final strike
|
||||
&& move.canBeMultiStrikeEnhanced(user, true, target)
|
||||
)
|
||||
)
|
||||
.build(),
|
||||
new AbBuilder(AbilityId.DARK_AURA, 6)
|
||||
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonDarkAura", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
||||
|
||||
@ -100,7 +100,6 @@ import i18next from "i18next";
|
||||
import { MovePhaseTimingModifier } from "#enums/move-phase-timing-modifier";
|
||||
import { inSpeedOrder } from "#utils/speed-order-generator";
|
||||
import { canSpeciesTera, willTerastallize } from "#utils/pokemon-utils";
|
||||
import type { ReadonlyGenericUint8Array } from "#types/typed-arrays";
|
||||
import { MovePriorityInBracket } from "#enums/move-priority-in-bracket";
|
||||
|
||||
/**
|
||||
@ -1117,20 +1116,34 @@ export abstract class Move implements Localizable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if this move can be given additional strikes
|
||||
* by enhancing effects.
|
||||
* Check whether this Move can be given additional strikes from enhancing effects.
|
||||
* Currently used for {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(Ability) | Parental Bond}
|
||||
* and {@linkcode PokemonMultiHitModifier | Multi-Lens}.
|
||||
* @param user The {@linkcode Pokemon} using the move
|
||||
* @param restrictSpread `true` if the enhancing effect
|
||||
* should not affect multi-target moves (default `false`)
|
||||
* and {@linkcode PokemonMultiHitModifier | Multi Lens}.
|
||||
* @param user - The {@linkcode Pokemon} using the move
|
||||
* @param restrictSpread - Whether the enhancing effect should ignore multi-target moves; default `false`
|
||||
* @returns Whether this Move can be given additional strikes.
|
||||
*/
|
||||
canBeMultiStrikeEnhanced(user: Pokemon, restrictSpread: boolean = false): boolean {
|
||||
// TODO: Remove target parameter used solely to circumvent Pollen Puff shenanigans - the entire move needs to be fixed anyhow
|
||||
public canBeMultiStrikeEnhanced(user: Pokemon, restrictSpread: boolean = false, target?: Pokemon | null): boolean {
|
||||
// Multi-strike enhancers...
|
||||
|
||||
// ...cannot enhance moves that hit multiple targets
|
||||
// ...cannot enhance charging or 2-turn moves
|
||||
if (this.isChargingMove()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...cannot enhance moves hitting multiple targets unless specified
|
||||
const { targets, multiple } = getMoveTargets(user, this.id);
|
||||
const isMultiTarget = multiple && targets.length > 1;
|
||||
if (restrictSpread && multiple && targets.length > 1) {
|
||||
return false;
|
||||
};
|
||||
|
||||
// ...cannot enhance status moves, including ally-targeting Pollen Puff
|
||||
if (
|
||||
this.category === MoveCategory.STATUS
|
||||
|| (target != null && user.getMoveCategory(target, this) === MoveCategory.STATUS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...cannot enhance multi-hit or sacrificial moves
|
||||
const exceptAttrs: MoveAttrString[] = [
|
||||
@ -1138,6 +1151,9 @@ export abstract class Move implements Localizable {
|
||||
"SacrificialAttr",
|
||||
"SacrificialAttrOnHit"
|
||||
];
|
||||
if (exceptAttrs.some(attr => this.hasAttr(attr))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...and cannot enhance these specific moves
|
||||
const exceptMoves: MoveId[] = [
|
||||
@ -1147,17 +1163,11 @@ export abstract class Move implements Localizable {
|
||||
MoveId.ICE_BALL,
|
||||
MoveId.ENDEAVOR
|
||||
];
|
||||
if (exceptMoves.includes(this.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...and cannot enhance Pollen Puff when targeting an ally.
|
||||
const ally = user.getAlly();
|
||||
const exceptPollenPuffAlly: boolean = this.id === MoveId.POLLEN_PUFF && ally != null && targets.includes(ally.getBattlerIndex())
|
||||
|
||||
return (!restrictSpread || !isMultiTarget)
|
||||
&& !this.isChargingMove()
|
||||
&& !exceptAttrs.some(attr => this.hasAttr(attr))
|
||||
&& !exceptMoves.some(id => this.id === id)
|
||||
&& !exceptPollenPuffAlly
|
||||
&& this.category !== MoveCategory.STATUS;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3672,15 +3672,6 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
multiStrikeEnhancementMultiplier,
|
||||
);
|
||||
|
||||
if (!ignoreSourceAbility) {
|
||||
applyAbAttrs("AddSecondStrikeAbAttr", {
|
||||
pokemon: source,
|
||||
move,
|
||||
simulated,
|
||||
multiplier: multiStrikeEnhancementMultiplier,
|
||||
});
|
||||
}
|
||||
|
||||
/** Doubles damage if this Pokemon's last move was Glaive Rush */
|
||||
const glaiveRushMultiplier = new NumberHolder(1);
|
||||
if (this.getTag(BattlerTagType.RECEIVE_DOUBLE_DAMAGE)) {
|
||||
@ -3769,9 +3760,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* mistyTerrainMultiplier,
|
||||
);
|
||||
|
||||
/** Doubles damage if the attacker has Tinted Lens and is using a resisted move */
|
||||
if (!ignoreSourceAbility) {
|
||||
applyAbAttrs("DamageBoostAbAttr", {
|
||||
applyAbAttrs("MoveDamageBoostAbAttr", {
|
||||
pokemon: source,
|
||||
opponent: this,
|
||||
move,
|
||||
|
||||
@ -276,7 +276,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
||||
// Assume single target for multi hit
|
||||
applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
|
||||
// If Parental Bond is applicable, add another hit
|
||||
applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount });
|
||||
applyAbAttrs("AddSecondStrikeAbAttr", { pokemon: user, move, hitCount, opponent: this.getFirstTarget() });
|
||||
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
|
||||
globalScene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, move.id, hitCount);
|
||||
// Set the user's relevant turnData fields to reflect the final hit count
|
||||
|
||||
@ -384,4 +384,24 @@ describe("Abilities - Parental Bond", () => {
|
||||
// TODO: Update hit count to 1 once Future Sight is fixed to not activate abilities if user is off the field
|
||||
expect(enemyPokemon.damageAndUpdate).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should not reduce damage against the remaining target if the first one faints", async () => {
|
||||
game.override.battleStyle("double").enemySpecies(SpeciesId.MAGIKARP);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
|
||||
const feebas = game.field.getPlayerPokemon();
|
||||
const [karp1, karp2] = game.scene.getEnemyField();
|
||||
|
||||
// Mock base damage for both mons for consistent results
|
||||
vi.spyOn(karp1, "getBaseDamage").mockReturnValue(100);
|
||||
vi.spyOn(karp2, "getBaseDamage").mockReturnValue(100);
|
||||
karp1.hp = 1;
|
||||
|
||||
game.move.use(MoveId.HYPER_VOICE);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(karp1).toHaveFainted();
|
||||
expect(feebas).not.toHaveAbilityApplied(AbilityId.PARENTAL_BOND);
|
||||
expect(karp2).toHaveTakenDamage(100);
|
||||
});
|
||||
});
|
||||
|
||||
@ -26,6 +26,7 @@ describe("Items - Multi Lens", () => {
|
||||
game.override
|
||||
.moveset([MoveId.TACKLE, MoveId.TRAILBLAZE, MoveId.TACHYON_CUTTER, MoveId.FUTURE_SIGHT])
|
||||
.ability(AbilityId.BALL_FETCH)
|
||||
.passiveAbility(AbilityId.NO_GUARD)
|
||||
.startingHeldItems([{ name: "MULTI_LENS" }])
|
||||
.battleStyle("single")
|
||||
.criticalHits(false)
|
||||
@ -135,61 +136,36 @@ describe("Items - Multi Lens", () => {
|
||||
expect(damageResults[1]).toBe(Math.floor(playerPokemon.level * 0.25));
|
||||
});
|
||||
|
||||
it("should result in correct damage for hp% attacks with 1 lens", async () => {
|
||||
it.each([1, 2])("should result in original damage for HP-cutting attacks with %d lenses", async lensCount => {
|
||||
game.override
|
||||
.startingHeldItems([{ name: "MULTI_LENS", count: 1 }])
|
||||
.moveset(MoveId.SUPER_FANG)
|
||||
.ability(AbilityId.COMPOUND_EYES)
|
||||
.startingHeldItems([{ name: "MULTI_LENS", count: lensCount }])
|
||||
.enemyLevel(1000)
|
||||
.enemySpecies(SpeciesId.BLISSEY); // allows for unrealistically high levels of accuracy
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
const blissey = game.field.getEnemyPokemon();
|
||||
|
||||
const enemyPokemon = game.field.getEnemyPokemon();
|
||||
game.move.use(MoveId.SUPER_FANG);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
game.move.select(MoveId.SUPER_FANG);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5);
|
||||
expect(blissey.getHpRatio()).toBeCloseTo(0.5, 5);
|
||||
});
|
||||
|
||||
it("should result in correct damage for hp% attacks with 2 lenses", async () => {
|
||||
it("should result in original damage for HP-cutting attacks with 2 lenses + Parental Bond", async () => {
|
||||
game.override
|
||||
.startingHeldItems([{ name: "MULTI_LENS", count: 2 }])
|
||||
.moveset(MoveId.SUPER_FANG)
|
||||
.ability(AbilityId.COMPOUND_EYES)
|
||||
.enemyMoveset(MoveId.SPLASH)
|
||||
.enemyLevel(1000)
|
||||
.enemySpecies(SpeciesId.BLISSEY); // allows for unrealistically high levels of accuracy
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
|
||||
const enemyPokemon = game.field.getEnemyPokemon();
|
||||
|
||||
game.move.select(MoveId.SUPER_FANG);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.5, 5);
|
||||
});
|
||||
|
||||
it("should result in correct damage for hp% attacks with 2 lenses + Parental Bond", async () => {
|
||||
game.override
|
||||
.startingHeldItems([{ name: "MULTI_LENS", count: 2 }])
|
||||
.moveset(MoveId.SUPER_FANG)
|
||||
.ability(AbilityId.PARENTAL_BOND)
|
||||
.passiveAbility(AbilityId.COMPOUND_EYES)
|
||||
.enemyMoveset(MoveId.SPLASH)
|
||||
.enemyLevel(1000)
|
||||
.enemySpecies(SpeciesId.BLISSEY); // allows for unrealistically high levels of accuracy
|
||||
|
||||
await game.classicMode.startBattle([SpeciesId.MAGIKARP]);
|
||||
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||
|
||||
const enemyPokemon = game.field.getEnemyPokemon();
|
||||
const blissey = game.field.getEnemyPokemon();
|
||||
|
||||
game.move.select(MoveId.SUPER_FANG);
|
||||
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.ENEMY]);
|
||||
await game.phaseInterceptor.to("MoveEndPhase");
|
||||
expect(enemyPokemon.getHpRatio()).toBeCloseTo(0.25, 5);
|
||||
game.move.use(MoveId.SUPER_FANG);
|
||||
await game.toEndOfTurn();
|
||||
|
||||
expect(blissey.getHpRatio()).toBeCloseTo(0.25, 5);
|
||||
});
|
||||
|
||||
it("should not allow Future Sight to hit infinitely many times if the user switches out", async () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user