Struggle no longer gets STAB

This commit is contained in:
Sirz Benjie 2025-04-10 09:54:05 -05:00
parent 787feceb14
commit e2ceb88081
No known key found for this signature in database
GPG Key ID: 4A524B4D196C759E
2 changed files with 143 additions and 58 deletions

View File

@ -3961,6 +3961,66 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return baseDamage;
}
/** Determine the STAB multiplier for a move used against this pokemon.
*
* @param source - The attacking {@linkcode Pokemon}
* @param move - The {@linkcode Move} used in the attack
* @param ignoreSourceAbility - If `true`, ignores the attacking Pokemon's ability effects
* @param simulated - If `true`, suppresses changes to game state during the calculation
*
* @returns The STAB multiplier for the move used against this Pokemon
*/
calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number {
// If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle)
if (move.hasAttr(TypelessAttr)) {
return 1;
}
const sourceTypes = source.getTypes();
const sourceTeraType = source.getTeraType();
const moveType = source.getMoveType(move);
const matchesSourceType = sourceTypes.includes(source.getMoveType(move));
const stabMultiplier = new Utils.NumberHolder(1);
if (matchesSourceType && moveType !== PokemonType.STELLAR) {
stabMultiplier.value += 0.5;
}
if (!ignoreSourceAbility) {
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
}
applyMoveAttrs(
CombinedPledgeStabBoostAttr,
source,
this,
move,
stabMultiplier,
);
if (
source.isTerastallized &&
sourceTeraType === moveType &&
moveType !== PokemonType.STELLAR
) {
stabMultiplier.value += 0.5;
}
if (
source.isTerastallized &&
source.getTeraType() === PokemonType.STELLAR &&
(!source.stellarTypesBoosted.includes(moveType) ||
source.hasSpecies(Species.TERAPAGOS))
) {
if (matchesSourceType) {
stabMultiplier.value += 0.5;
} else {
stabMultiplier.value += 0.2;
}
}
return Math.min(stabMultiplier.value, 2.25);
}
/**
* Calculates the damage of an attack made by another Pokemon against this Pokemon
* @param source {@linkcode Pokemon} the attacking Pokemon
@ -4143,70 +4203,30 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
? 1
: this.randSeedIntRange(85, 100) / 100;
const sourceTypes = source.getTypes();
const sourceTeraType = source.getTeraType();
const matchesSourceType = sourceTypes.includes(moveType);
/** A damage multiplier for when the attack is of the attacker's type and/or Tera type. */
const stabMultiplier = new Utils.NumberHolder(1);
if (matchesSourceType && moveType !== PokemonType.STELLAR) {
stabMultiplier.value += 0.5;
}
if (!ignoreSourceAbility) {
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
}
applyMoveAttrs(
CombinedPledgeStabBoostAttr,
source,
this,
move,
stabMultiplier,
);
if (
source.isTerastallized &&
sourceTeraType === moveType &&
moveType !== PokemonType.STELLAR
) {
stabMultiplier.value += 0.5;
}
if (
source.isTerastallized &&
source.getTeraType() === PokemonType.STELLAR &&
(!source.stellarTypesBoosted.includes(moveType) ||
source.hasSpecies(Species.TERAPAGOS))
) {
if (matchesSourceType) {
stabMultiplier.value += 0.5;
} else {
stabMultiplier.value += 0.2;
}
}
stabMultiplier.value = Math.min(stabMultiplier.value, 2.25);
const stabMultiplier = this.calculateStabMultiplier(source, move, ignoreSourceAbility, simulated);
/** Halves damage if the attacker is using a physical attack while burned */
const burnMultiplier = new Utils.NumberHolder(1);
let burnMultiplier = 1;
if (
isPhysical &&
source.status &&
source.status.effect === StatusEffect.BURN
source.status.effect === StatusEffect.BURN &&
!move.hasAttr(BypassBurnDamageReductionAttr)
) {
if (!move.hasAttr(BypassBurnDamageReductionAttr)) {
const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
if (!ignoreSourceAbility) {
applyAbAttrs(
BypassBurnDamageReductionAbAttr,
source,
burnDamageReductionCancelled,
simulated,
);
}
if (!burnDamageReductionCancelled.value) {
burnMultiplier.value = 0.5;
}
const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
if (!ignoreSourceAbility) {
applyAbAttrs(
BypassBurnDamageReductionAbAttr,
source,
burnDamageReductionCancelled,
simulated,
);
}
if (!burnDamageReductionCancelled.value) {
burnMultiplier = 0.5;
}
}
@ -4257,9 +4277,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
glaiveRushMultiplier.value *
criticalMultiplier.value *
randomMultiplier *
stabMultiplier.value *
stabMultiplier *
typeMultiplier *
burnMultiplier.value *
burnMultiplier *
screenMultiplier.value *
hitsTagMultiplier.value *
mistyTerrainMultiplier,

View File

@ -0,0 +1,65 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Moves - Struggle", () => {
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
.moveset([Moves.SPLASH])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should not have its power boosted by adaptability or stab", async () => {
game.override.moveset([Moves.STRUGGLE]).ability(Abilities.ADAPTABILITY);
await game.classicMode.startBattle([Species.RATTATA]);
const enemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.STRUGGLE);
const stabSpy = vi.spyOn(enemy, "calculateStabMultiplier");
await game.phaseInterceptor.to("BerryPhase");
expect(stabSpy).toHaveReturnedWith(1);
stabSpy.mockRestore();
});
it("should ignore type effectiveness", async () => {
game.override.moveset([Moves.STRUGGLE]);
await game.classicMode.startBattle([Species.GASTLY]);
const enemy = game.scene.getEnemyPokemon()!;
game.move.select(Moves.STRUGGLE);
const moveEffectivenessSpy = vi.spyOn(enemy, "getMoveEffectiveness");
await game.phaseInterceptor.to("BerryPhase");
expect(moveEffectivenessSpy).toHaveReturnedWith(1);
moveEffectivenessSpy.mockRestore();
});
});