diff --git a/src/data/ability.ts b/src/data/ability.ts index 7250ee2d920..b3dcf90cd48 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -50,10 +50,10 @@ export class Ability { const attr = new AttrType(...args); attr.addCondition(condition); this.attrs.push(attr); - + return this; } - + hasAttr(attrType: { new(...args: any[]): AbAttr }): boolean { return !!this.getAttrs(attrType).length; } @@ -579,10 +579,41 @@ export class PreAttackAbAttr extends AbAttr { export class VariableMovePowerAbAttr extends PreAttackAbAttr { applyPreAttack(pokemon: Pokemon, defender: Pokemon, move: PokemonMove, args: any[]): boolean { //const power = args[0] as Utils.NumberHolder; + return false; + } +} + +export class VariableMoveTypeAbAttr extends AbAttr { + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + //const power = args[0] as Utils.IntegerHolder; return false; } } +export class MoveTypeChangePowerMultiplierAbAttr extends VariableMoveTypeAbAttr { + private matchType: Type; + private newType: Type; + private powerMultiplier: number; + + constructor(matchType: Type, newType: Type, powerMultiplier: number){ + super(true); + this.matchType = matchType; + this.newType = newType; + this.powerMultiplier = powerMultiplier; + } + + apply(pokemon: Pokemon, cancelled: Utils.BooleanHolder, args: any[]): boolean { + const type = (args[0] as Utils.IntegerHolder); + if (type.value == this.matchType) { + type.value = this.newType; + (args[1] as Utils.NumberHolder).value *= this.powerMultiplier; + return true; + } + + return false; + } +} + export class MovePowerBoostAbAttr extends VariableMovePowerAbAttr { private condition: PokemonAttackCondition; private powerMultiplier: number; @@ -2517,7 +2548,8 @@ export function initAbilities() { new Ability(Abilities.COMPETITIVE, "Competitive (N)", "Boosts the Sp. Atk stat sharply when a stat is lowered.", 6), new Ability(Abilities.STRONG_JAW, "Strong Jaw", "The Pokémon's strong jaw boosts the power of its biting moves.", 6) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.BITING_MOVE), 1.5), - new Ability(Abilities.REFRIGERATE, "Refrigerate (N)", "Normal-type moves become Ice-type moves. The power of those moves is boosted a little.", 6), + new Ability(Abilities.REFRIGERATE, "Refrigerate", "Normal-type moves become Ice-type moves. The power of those moves is boosted a little.", 6) + .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ICE, 1.2), new Ability(Abilities.SWEET_VEIL, "Sweet Veil (N)", "Prevents itself and ally Pokémon from falling asleep.", 6) .ignorable(), new Ability(Abilities.STANCE_CHANGE, "Stance Change", "The Pokémon changes its form to Blade Forme when it uses an attack move and changes to Shield Forme when it uses King's Shield.", 6) @@ -2530,10 +2562,12 @@ export function initAbilities() { new Ability(Abilities.SYMBIOSIS, "Symbiosis (N)", "The Pokémon passes its item to an ally that has used up an item.", 6), new Ability(Abilities.TOUGH_CLAWS, "Tough Claws", "Powers up moves that make direct contact.", 6) .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 1.3), - new Ability(Abilities.PIXILATE, "Pixilate (N)", "Normal-type moves become Fairy-type moves. The power of those moves is boosted a little.", 6), + new Ability(Abilities.PIXILATE, "Pixilate", "Normal-type moves become Fairy-type moves. The power of those moves is boosted a little.", 6) + .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FAIRY, 1.2), new Ability(Abilities.GOOEY, "Gooey", "Contact with the Pokémon lowers the attacker's Speed stat.", 6) .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), BattleStat.SPD, -1, false), - new Ability(Abilities.AERILATE, "Aerilate (N)", "Normal-type moves become Flying-type moves. The power of those moves is boosted a little.", 6), + new Ability(Abilities.AERILATE, "Aerilate", "Normal-type moves become Flying-type moves. The power of those moves is boosted a little.", 6) + .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.FLYING, 1.2), new Ability(Abilities.PARENTAL_BOND, "Parental Bond (N)", "Parent and child each attacks.", 6), new Ability(Abilities.DARK_AURA, "Dark Aura", "Powers up each Pokémon's Dark-type moves.", 6) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => getPokemonMessage(pokemon, ' is radiating a Dark Aura!')) @@ -2574,7 +2608,8 @@ export function initAbilities() { .attr(IgnoreContactAbAttr), new Ability(Abilities.LIQUID_VOICE, "Liquid Voice (N)", "All sound-based moves become Water-type moves.", 7), new Ability(Abilities.TRIAGE, "Triage (N)", "Gives priority to a healing move.", 7), - new Ability(Abilities.GALVANIZE, "Galvanize (N)", "Normal-type moves become Electric-type moves. The power of those moves is boosted a little.", 7), + new Ability(Abilities.GALVANIZE, "Galvanize", "Normal-type moves become Electric-type moves. The power of those moves is boosted a little.", 7) + .attr(MoveTypeChangePowerMultiplierAbAttr, Type.NORMAL, Type.ELECTRIC, 1.2), new Ability(Abilities.SURGE_SURFER, "Surge Surfer", "Doubles the Pokémon's Speed stat on Electric Terrain.", 7) .conditionalAttr(getTerrainCondition(TerrainType.ELECTRIC), BattleStatMultiplierAbAttr, BattleStat.SPD, 2), new Ability(Abilities.SCHOOLING, "Schooling", "When it has a lot of HP, the Pokémon forms a powerful school. It stops schooling when its HP is low.", 7) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 4cbaa8afaa2..3e9f7d93343 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2,7 +2,7 @@ import Phaser from 'phaser'; import BattleScene, { ABILITY_OVERRIDE, AnySound, MOVE_OVERRIDE, OPP_ABILITY_OVERRIDE, OPP_MOVE_OVERRIDE } from '../battle-scene'; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from '../ui/battle-info'; import { Moves } from "../data/enums/moves"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveTypeAbAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from '../data/pokemon-species'; import * as Utils from '../utils'; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from '../data/type'; @@ -25,7 +25,7 @@ import { TempBattleStat } from '../data/temp-battle-stat'; import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from '../data/arena-tag'; import { ArenaTagType } from "../data/enums/arena-tag-type"; import { Biome } from "../data/enums/biome"; -import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, BypassBurnDamageReductionAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from '../data/ability'; +import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, BypassBurnDamageReductionAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, VariableMoveTypeAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from '../data/ability'; import PokemonData from '../system/pokemon-data'; import { BattlerIndex } from '../battle'; import { BattleSpec } from "../enums/battle-spec"; @@ -41,6 +41,7 @@ import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMov import { TerrainType } from '../data/terrain'; import { TrainerSlot } from '../data/trainer-config'; + export enum FieldPosition { CENTER, LEFT, @@ -751,7 +752,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (moveType === Type.STELLAR) return this.isTerastallized() ? 2 : 1; const types = this.getTypes(true, true); - let multiplier = getTypeDamageMultiplier(moveType, types[0]) * (types.length > 1 ? getTypeDamageMultiplier(moveType, types[1]) : 1) as TypeDamageMultiplier; + let multiplier = getTypeDamageMultiplier(moveType, types[0]) * (types.length > 1 ? getTypeDamageMultiplier(moveType, types[1]) : 1) as TypeDamageMultiplier; // Handle strong winds lowering effectiveness of types super effective against pure flying if (this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && multiplier >= 2 && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) multiplier /= 2; @@ -1085,20 +1086,23 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const moveCategory = move.category; let damage = new Utils.NumberHolder(0); + const variableType = new Utils.IntegerHolder(move.type); + const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); + // 2nd argument is for MoveTypeChangePowerMultiplierAbAttr + applyAbAttrs(VariableMoveTypeAbAttr, source, null, variableType, typeChangeMovePowerMultiplier); + const type = variableType.value as Type; + const cancelled = new Utils.BooleanHolder(false); const typeless = !!move.getAttrs(TypelessAttr).length; const types = this.getTypes(true, true); - const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => (attr as StatusMoveTypeImmunityAttr).immuneType === move.type)) - ? getTypeDamageMultiplier(move.type, types[0]) * (types.length > 1 ? getTypeDamageMultiplier(move.type, types[1]) : 1) + const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => (attr as StatusMoveTypeImmunityAttr).immuneType === type)) + ? getTypeDamageMultiplier(type, types[0]) * (types.length > 1 ? getTypeDamageMultiplier(type, types[1]) : 1) : 1); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); if (typeless) typeMultiplier.value = 1; if (this.getTypes(true, true).find(t => move.isTypeImmune(t))) typeMultiplier.value = 0; - // Handle strong winds lowering effectiveness of types super effective against pure flying - if (this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && typeMultiplier.value >= 2 && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(move.type, Type.FLYING) === 2) - typeMultiplier.value /= 2; switch (moveCategory) { case MoveCategory.PHYSICAL: @@ -1106,13 +1110,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const isPhysical = moveCategory === MoveCategory.PHYSICAL; const power = new Utils.NumberHolder(move.power); const sourceTeraType = source.getTeraType(); - if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.getAttrs(MultiHitAttr).length && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type && power.value < 60 && move.priority <= 0 && !move.getAttrs(MultiHitAttr).length && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) power.value = 60; applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, this, battlerMove, power); this.scene.getField(true).map(p => applyPreAttackAbAttrs(FieldVariableMovePowerAbAttr, this, source, battlerMove, power)); applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, battlerMove, cancelled, power); + power.value *= typeChangeMovePowerMultiplier.value; + if (!typeless) applyPreDefendAbAttrs(TypeImmunityAbAttr, this, source, battlerMove, cancelled, typeMultiplier); if (!cancelled.value) @@ -1121,16 +1127,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (cancelled.value) result = HitResult.NO_EFFECT; else { - if (source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === move.type)) + if (source.findTag(t => t instanceof TypeBoostTag && (t as TypeBoostTag).boostedType === type)) power.value *= 1.5; - const arenaAttackTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(move.type, this.isGrounded()); - if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && this.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) + const arenaAttackTypeMultiplier = this.scene.arena.getAttackTypeMultiplier(type, this.isGrounded()); + if (this.scene.arena.terrain?.terrainType === TerrainType.GRASSY && this.isGrounded() && type === Type.GROUND && move.moveTarget === MoveTarget.ALL_NEAR_OTHERS) power.value /= 2; applyMoveAttrs(VariablePowerAttr, source, this, move, power); this.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power); if (!typeless) { - this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); - this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, move.type, power); + this.scene.arena.applyTags(WeakenMoveTypeTag, type, power); + this.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, type, power); } if (source.getTag(HelpingHandTag)) power.value *= 1.5; @@ -1163,11 +1169,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier) === 0; const sourceTypes = source.getTypes(); - const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); + const matchesSourceType = sourceTypes[0] === type || (sourceTypes.length > 1 && sourceTypes[1] === type); let stabMultiplier = new Utils.NumberHolder(1); if (sourceTeraType === Type.UNKNOWN && matchesSourceType) stabMultiplier.value += 0.5; - else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) + else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === type) stabMultiplier.value += 0.5; applyAbAttrs(StabBoostAbAttr, source, null, stabMultiplier); @@ -1192,7 +1198,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }); } - if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && move.type === Type.DRAGON) + if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && this.isGrounded() && type === Type.DRAGON) damage.value = Math.floor(damage.value / 2); applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage); @@ -2433,8 +2439,12 @@ export class EnemyPokemon extends Pokemon { for (let m in movePool) { const pokemonMove = movePool[m]; const move = pokemonMove.getMove(); - let moveScore = moveScores[m]; + const variableType = new Utils.IntegerHolder(move.type); + applyAbAttrs(VariableMoveTypeAbAttr, this, null, variableType); + const moveType = variableType.value as Type; + + let moveScore = moveScores[m]; let targetScores: integer[] = []; for (let mt of moveTargets[move.id]) { @@ -2444,11 +2454,11 @@ export class EnemyPokemon extends Pokemon { const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove); if (target.isPlayer() !== this.isPlayer()) { targetScore *= effectiveness; - if (this.isOfType(move.type)) + if (this.isOfType(moveType)) targetScore *= 1.5; } else { targetScore /= effectiveness; - if (this.isOfType(move.type)) + if (this.isOfType(moveType)) targetScore /= 1.5; } }