[Bug/Ability] Fix Forewarn not triggering + add randomized selection (#6623)

* [Ability] Fix Forewarn not triggering + add randomized selection

* Fix typo

* Comment fix

* Fix name oops

* Fix `slice` instead of `splice`

* Fix missing continue

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

* Apply Biome

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Bertie690 2025-10-07 23:08:57 -04:00 committed by GitHub
parent 54918f6145
commit d32e112194
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -4330,6 +4330,11 @@ function getOncePerBattleCondition(ability: AbilityId): AbAttrCondition {
} }
/** /**
* Ability attribute used by {@linkcode AbilityId.FOREWARN}.
*
* Displays a message on switch-in containing the highest power Move known by the user's opponents,
* picking randomly in the case of a tie.
* @see {@link https://www.smogon.com/dex/sv/abilities/forewarn/}
* @sealed * @sealed
*/ */
export class ForewarnAbAttr extends PostSummonAbAttr { export class ForewarnAbAttr extends PostSummonAbAttr {
@ -4338,46 +4343,77 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
} }
override apply({ simulated, pokemon }: AbAttrBaseParams): void { override apply({ simulated, pokemon }: AbAttrBaseParams): void {
if (!simulated) { if (simulated) {
return; return;
} }
let maxPowerSeen = 0; let maxPowerSeen = 0;
let maxMove = ""; const movesAtMaxPower: string[] = [];
let movePower = 0;
for (const opponent of pokemon.getOpponents()) { // Record all moves in all opponents' movesets seen at our max power threshold, clearing it if a new "highest power" is found
for (const move of opponent.moveset) { // TODO: Change to `pokemon.getOpponents().flatMap(p => p.getMoveset())` if or when we upgrade to ES2025
if (move?.getMove().is("StatusMove")) { for (const opp of pokemon.getOpponents()) {
movePower = 1; for (const oppMove of opp.getMoveset()) {
} else if (move?.getMove().hasAttr("OneHitKOAttr")) { const move = oppMove.getMove();
movePower = 150; const movePower = getForewarnPower(move);
} else if ( if (movePower < maxPowerSeen) {
move?.getMove().id === MoveId.COUNTER continue;
|| move?.getMove().id === MoveId.MIRROR_COAT
|| move?.getMove().id === MoveId.METAL_BURST
) {
movePower = 120;
} else if (move?.getMove().power === -1) {
movePower = 80;
} else {
movePower = move?.getMove().power ?? 0;
} }
if (movePower > maxPowerSeen) { // Another move at current max found; add to tiebreaker array
maxPowerSeen = movePower; if (movePower === maxPowerSeen) {
maxMove = move?.getName() ?? ""; movesAtMaxPower.push(move.name);
continue;
} }
// New max reached; clear prior results and update tracker
maxPowerSeen = movePower;
movesAtMaxPower.splice(0, movesAtMaxPower.length, move.name);
} }
} }
// Pick a random move in our list.
if (movesAtMaxPower.length === 0) {
// NB: The ONLY way this can happen is if both opponents have 0 moves in their moveset
throw new Error("Forewarn ability found 0 valid moves!");
}
const chosenMove = movesAtMaxPower[pokemon.randBattleSeedInt(movesAtMaxPower.length)];
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("abilityTriggers:forewarn", { i18next.t("abilityTriggers:forewarn", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
moveName: maxMove, moveName: chosenMove,
}), }),
); );
} }
} }
/**
* Helper function to return the estimated power used by Forewarn's "highest power" ranking.
* @param move - The `Move` being checked
* @returns The "forewarned" power of the move.
* @see {@link https://www.smogon.com/dex/sv/abilities/forewarn/}
*/
function getForewarnPower(move: Move): number {
if (move.is("StatusMove")) {
return 1;
}
if (move.hasAttr("OneHitKOAttr")) {
return 150;
}
// NB: Mainline doesn't count Comeuppance in its "counter move exceptions" list, which is dumb
if (move.hasAttr("CounterDamageAttr")) {
return 120;
}
// All damaging moves with unlisted powers use 80 as a fallback
if (move.power === -1) {
return 80;
}
return move.power;
}
/** /**
* Ability attribute that reveals the abilities of all opposing Pokémon when the Pokémon with this ability is summoned. * Ability attribute that reveals the abilities of all opposing Pokémon when the Pokémon with this ability is summoned.
* @sealed * @sealed