[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
*/
export class ForewarnAbAttr extends PostSummonAbAttr {
@ -4338,46 +4343,77 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
}
override apply({ simulated, pokemon }: AbAttrBaseParams): void {
if (!simulated) {
if (simulated) {
return;
}
let maxPowerSeen = 0;
let maxMove = "";
let movePower = 0;
for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) {
if (move?.getMove().is("StatusMove")) {
movePower = 1;
} else if (move?.getMove().hasAttr("OneHitKOAttr")) {
movePower = 150;
} else if (
move?.getMove().id === MoveId.COUNTER
|| 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;
const movesAtMaxPower: string[] = [];
// Record all moves in all opponents' movesets seen at our max power threshold, clearing it if a new "highest power" is found
// TODO: Change to `pokemon.getOpponents().flatMap(p => p.getMoveset())` if or when we upgrade to ES2025
for (const opp of pokemon.getOpponents()) {
for (const oppMove of opp.getMoveset()) {
const move = oppMove.getMove();
const movePower = getForewarnPower(move);
if (movePower < maxPowerSeen) {
continue;
}
if (movePower > maxPowerSeen) {
maxPowerSeen = movePower;
maxMove = move?.getName() ?? "";
// Another move at current max found; add to tiebreaker array
if (movePower === maxPowerSeen) {
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(
i18next.t("abilityTriggers:forewarn", {
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.
* @sealed