From 2c8b2de660b1ebd35a0732d36da79d73629537b4 Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 15 Apr 2025 16:54:44 -0500 Subject: [PATCH] Make type boost items like silk scarf affect the move after its type change --- src/constants.ts | 3 +++ src/data/abilities/ability.ts | 41 +++++++++++++++++++++++++++------ src/data/moves/invalid-moves.ts | 15 ++++++++++++ src/data/moves/move.ts | 16 +++++++++---- src/field/pokemon.ts | 9 +++----- src/modifier/modifier-type.ts | 5 ++-- src/modifier/modifier.ts | 3 ++- 7 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index dc901e4a766..d3594c389b6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,3 +14,6 @@ export const MAX_INT_ATTR_VALUE = 0x80000000; export const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const; /** The min and max waves for mystery encounters to spawn in challenge mode */ export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const; + +/** The raw percentage power boost for type boost items*/ +export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 9511fc87dc9..78b65d6998a 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -72,6 +72,7 @@ import type { BattlerIndex } from "#app/battle"; import type Move from "#app/data/moves/move"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import { SelectBiomePhase } from "#app/phases/select-biome-phase"; +import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; export class BlockRecoilDamageAttr extends AbAttr { constructor() { @@ -1239,12 +1240,40 @@ export class MoveTypeChangeAbAttr extends PreAttackAbAttr { super(false); } - override canApplyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon | null, move: Move, args: any[]): boolean { - return (this.condition && this.condition(pokemon, defender, move)) ?? false; + /** + * Determine if the move type change attribute can be applied + * + * Can be applied if: + * - The ability's condition is met, e.g. pixilate only boosts normal moves, + * - The move is not forbidden from having its type changed by an ability, e.g. {@linkcode Moves.MULTI_ATTACK} + * - The user is not terastallized and using tera blast + * - The user is not a terastallized terapagos with tera stellar using tera starstorm + * @param pokemon - The pokemon that has the move type changing ability and is using the attacking move + * @param _passive - Unused + * @param _simulated - Unused + * @param _defender - The pokemon being attacked (unused) + * @param move - The move being used + * @param _args - args[0] holds the type that the move is changed to, args[1] holds the multiplier + * @returns whether the move type change attribute can be applied + */ + override canApplyPreAttack(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _defender: Pokemon | null, move: Move, _args: [NumberHolder?, NumberHolder?, ...any]): boolean { + return (this.condition && this.condition(pokemon, _defender, move) && + !noAbilityTypeOverrideMoves.has(move.id) && + (!pokemon.isTerastallized || + (move.id !== Moves.TERA_BLAST && + (move.id !== Moves.TERA_STARSTORM || pokemon.getTeraType() !== PokemonType.STELLAR || !pokemon.hasSpecies(Species.TERAPAGOS))))) + ?? false; } - // TODO: Decouple this into two attributes (type change / power boost) - override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: any[]): void { + /** + * @param pokemon - The pokemon that has the move type changing ability and is using the attacking move + * @param passive - Unused + * @param simulated - Unused + * @param defender - The pokemon being attacked (unused) + * @param move - The move being used + * @param args - args[0] holds the type that the move is changed to, args[1] holds the multiplier + */ + override applyPreAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, defender: Pokemon, move: Move, args: [NumberHolder?, NumberHolder?, ...any]): void { if (args[0] && args[0] instanceof NumberHolder) { args[0].value = this.newType; } @@ -6628,9 +6657,7 @@ export function initAbilities() { .conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, StatMultiplierAbAttr, Stat.SPD, 2) .conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), StatMultiplierAbAttr, Stat.SPD, 1.5), new Ability(Abilities.NORMALIZE, 4) - .attr(MoveTypeChangeAbAttr, PokemonType.NORMAL, 1.2, (user, target, move) => { - return ![ Moves.MULTI_ATTACK, Moves.REVELATION_DANCE, Moves.TERRAIN_PULSE, Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST ].includes(move.id); - }), + .attr(MoveTypeChangeAbAttr, PokemonType.NORMAL, 1.2), new Ability(Abilities.SNIPER, 4) .attr(MultCritAbAttr, 1.5), new Ability(Abilities.MAGIC_GUARD, 4) diff --git a/src/data/moves/invalid-moves.ts b/src/data/moves/invalid-moves.ts index 5cd45de7939..025c0383f43 100644 --- a/src/data/moves/invalid-moves.ts +++ b/src/data/moves/invalid-moves.ts @@ -240,3 +240,18 @@ export const invalidMirrorMoveMoves: ReadonlySet = new Set([ Moves.WATER_SPORT, Moves.WIDE_GUARD, ]); + +/** Set of moves that can never have their type overridden by an ability like Pixilate or Normalize + * + * Excludes tera blast and tera starstorm, as these are only conditionally forbidden + */ +export const noAbilityTypeOverrideMoves: ReadonlySet = new Set([ + Moves.WEATHER_BALL, + Moves.JUDGMENT, + Moves.REVELATION_DANCE, + Moves.MULTI_ATTACK, + Moves.TERRAIN_PULSE, + Moves.NATURAL_GIFT, + Moves.TECHNO_BLAST, + Moves.HIDDEN_POWER, +]); diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 85f3007dd88..0cdc7f2738e 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -810,8 +810,14 @@ export default class Move implements Localizable { const power = new NumberHolder(this.power); const typeChangeMovePowerMultiplier = new NumberHolder(1); + const typeChangeHolder = new NumberHolder(this.type); - applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier); + // apply move type changing ability attributes + applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); + if (typeChangeHolder.value !== this.type) { + console.log("=========================================="); + console.log("Move type change to " + PokemonType[typeChangeHolder.value]); + } const sourceTeraType = source.getTeraType(); if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { @@ -841,7 +847,7 @@ export default class Move implements Localizable { power.value *= typeChangeMovePowerMultiplier.value; - const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === this.type) as TypeBoostTag; + const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === typeChangeHolder.value) as TypeBoostTag; if (typeBoost) { power.value *= typeBoost.boostValue; } @@ -849,8 +855,10 @@ export default class Move implements Localizable { applyMoveAttrs(VariablePowerAttr, source, target, this, power); if (!this.hasAttr(TypelessAttr)) { - globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power); - globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power); + globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power); + console.log("Before applying attack type boosters, power is " + power.value); + globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power); + console.log("After applying attack type boosters, power is " + power.value); } if (source.getTag(HelpingHandTag)) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 90f0c4275dd..b4424585ad2 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2583,22 +2583,19 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ public getMoveType(move: Move, simulated = true): PokemonType { const moveTypeHolder = new NumberHolder(move.type); - // If the user is terastallized and the move is tera blast, then the move type is the tera type - // Abilities that change the move type go first + applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyPreAttackAbAttrs( MoveTypeChangeAbAttr, this, null, move, simulated, - moveTypeHolder, + moveTypeHolder ); - // And then moves that change the move type go - applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, - // then bypass the check for ion deluge + // then bypass the check for ion deluge and electrify if (this.isTerastallized && (move.id === Moves.TERA_BLAST || move.id === Moves.TERA_STARSTORM && moveTypeHolder.value === PokemonType.STELLAR)) { return moveTypeHolder.value as PokemonType; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 219a6b6344b..110c19dfec0 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -128,6 +128,7 @@ import { getStatKey, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { timedEventManager } from "#app/global-event-manager"; +import { TYPE_BOOST_ITEM_BOOST_PERCENT } from "#app/constants"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -1329,7 +1330,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { constructor() { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && pregenArgs.length === 1 && pregenArgs[0] in PokemonType) { - return new AttackTypeBoosterModifierType(pregenArgs[0] as PokemonType, 20); + return new AttackTypeBoosterModifierType(pregenArgs[0] as PokemonType, TYPE_BOOST_ITEM_BOOST_PERCENT); } const attackMoveTypes = party.flatMap(p => @@ -1377,7 +1378,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator { weight += typeWeight; } - return new AttackTypeBoosterModifierType(type!, 20); + return new AttackTypeBoosterModifierType(type!, TYPE_BOOST_ITEM_BOOST_PERCENT); }); } } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 3eaf4e3c510..7dfbcf6ca0b 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1479,7 +1479,8 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { return ( super.shouldApply(pokemon, moveType, movePower) && typeof moveType === "number" && - movePower instanceof NumberHolder + movePower instanceof NumberHolder && + this.moveType === moveType ); }