diff --git a/src/data/ability.ts b/src/data/ability.ts index 2e67b30c537..0ad69407e2c 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2,14 +2,14 @@ import Pokemon, { HitResult, PokemonMove } from "../field/pokemon"; import { Type } from "./type"; import * as Utils from "../utils"; import { BattleStat, getBattleStatName } from "./battle-stat"; -import { PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; +import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; import { getPokemonMessage, getPokemonPrefix } from "../messages"; import { Weather, WeatherType } from "./weather"; import { BattlerTag } from "./battler-tags"; import { BattlerTagType } from "./enums/battler-tag-type"; import { StatusEffect, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect"; import { Gender } from "./gender"; -import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, StrengthSapHealAttr, allMoves, StatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move"; +import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, StrengthSapHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; import { Stat } from "./pokemon-stat"; @@ -22,6 +22,7 @@ import i18next, { Localizable } from "#app/plugins/i18n.js"; import { Command } from "../ui/command-ui-handler"; import { getPokeballName } from "./pokeball"; import { BerryModifierType } from "#app/modifier/modifier-type"; +import {BattlerIndex} from "#app/battle"; export class Ability implements Localizable { public id: Abilities; @@ -2484,6 +2485,62 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { } } +/** + * Triggers just after a move is used either by the opponent or the player + * @extends AbAttr + */ +export class PostMoveUsedAbAttr extends AbAttr { + applyPostMoveUsed(pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], args: any[]): boolean | Promise { + return false; + } +} + +/** + * Triggers after a dance move is used either by the opponent or the player + * @extends PostMoveUsedAbAttr + */ +export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { + /** + * Resolves the Dancer ability by replicating the move used by the source of the dance + * either on the source itself or on the target of the dance + * @param dancer {@linkcode Pokemon} with Dancer ability + * @param move {@linkcode PokemonMove} Dancing move used by the source + * @param source {@linkcode Pokemon} that used the dancing move + * @param targets {@linkcode BattlerIndex}Targets of the dancing move + * @param args N/A + * + * @return true if the Dancer ability was resolved + */ + applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], args: any[]): boolean | Promise { + // The move to replicate cannot come from the Dancer + if (source.getBattlerIndex() !== dancer.getBattlerIndex()) { + // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance + if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { + const target = this.getTarget(dancer, source, targets); + dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, target, move, true)); + } else if (move.getMove() instanceof SelfStatusMove) { + // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself + dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true)); + } + } + return true; + } + + /** + * Get the correct targets of Dancer ability + * + * @param dancer {@linkcode Pokemon} Pokemon with Dancer ability + * @param source {@linkcode Pokemon} Source of the dancing move + * @param targets {@linkcode BattlerIndex} Targets of the dancing move + */ + getTarget(dancer: Pokemon, source: Pokemon, targets: BattlerIndex[]) : BattlerIndex[] { + if (dancer.isPlayer()) { + return source.isPlayer() ? targets : [source.getBattlerIndex()]; + } + return source.isPlayer() ? [source.getBattlerIndex()] : targets; + } +} + export class StatChangeMultiplierAbAttr extends AbAttr { private multiplier: integer; @@ -3013,6 +3070,11 @@ export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefe return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostDefend(pokemon, passive, attacker, move, hitResult, args), args); } +export function applyPostMoveUsedAbAttrs(attrType: { new(...args: any[]): PostMoveUsedAbAttr }, + pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, targets, args), args); +} + export function applyBattleStatMultiplierAbAttrs(attrType: { new(...args: any[]): BattleStatMultiplierAbAttr }, pokemon: Pokemon, battleStat: BattleStat, statValue: Utils.NumberHolder, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyBattleStat(pokemon, passive, battleStat, statValue, args), args); @@ -3770,7 +3832,7 @@ export function initAbilities() { .attr(PostFaintHPDamageAbAttr) .bypassFaint(), new Ability(Abilities.DANCER, 7) - .unimplemented(), + .attr(PostDancingMoveAbAttr), new Ability(Abilities.BATTERY, 7) .unimplemented(), new Ability(Abilities.FLUFFY, 7) diff --git a/src/phases.ts b/src/phases.ts index b05dfba05ca..371b086057c 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -30,7 +30,7 @@ import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, get import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; import { ArenaTagType } from "./data/enums/arena-tag-type"; -import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr } from "./data/ability"; +import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, applyPostBattleInitAbAttrs, PostBattleInitAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -2404,7 +2404,7 @@ export class MovePhase extends BattlePhase { console.log(Moves[this.move.moveId]); if (!this.canMove()) { - if (this.move.moveId && this.pokemon.summonData.disabledMove === this.move.moveId) { + if (this.move.moveId && this.pokemon.summonData?.disabledMove === this.move.moveId) { this.scene.queueMessage(`${this.move.getName()} is disabled!`); } return this.end(); @@ -2543,7 +2543,16 @@ export class MovePhase extends BattlePhase { this.showFailedText(failedText); } } - + // Checks if Dancer ability is triggered + if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { + // Pokemon with Dancer can be on either side of the battle so we check in both cases + this.scene.getPlayerField().forEach(pokemon => { + applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); + }); + this.scene.getEnemyParty().forEach(pokemon => { + applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon, this.targets); + }); + } this.end(); };