From 9efb2b834f209ef14cfb32d583ec6cba4d44ebac Mon Sep 17 00:00:00 2001 From: YounesM Date: Tue, 7 May 2024 10:53:31 +0200 Subject: [PATCH 1/3] implemented Dancer Ability --- src/data/ability.ts | 34 ++++++++++++++++++++++++++++++++-- src/phases.ts | 7 +++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 07aecbdcc..68f4b06c6 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2,7 +2,7 @@ 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 } from "../messages"; import { Weather, WeatherType } from "./weather"; import { BattlerTag } from "./battler-tags"; @@ -22,6 +22,7 @@ import i18next, { Localizable } from "#app/plugins/i18n.js"; import { Command } from "../ui/command-ui-handler"; import Battle from "#app/battle.js"; import { ability } from "#app/locales/en/ability.js"; +import {BattlerIndex} from "#app/battle"; export class Ability implements Localizable { public id: Abilities; @@ -2126,6 +2127,30 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { } } +export class PostMoveUsedAbAttr extends AbAttr { + applyPostMoveUsed(pokemon: Pokemon, move: PokemonMove, source: Pokemon, args: any[]): boolean | Promise { + return false; + } +} + +export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { + applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, args: any[]): boolean | Promise { + const selfDance: Moves[] = [ Moves.DRAGON_DANCE, Moves.SWORDS_DANCE, + Moves.TEETER_DANCE, Moves.VICTORY_DANCE, Moves.QUIVER_DANCE, Moves.CLANGOROUS_SOUL, Moves.LUNAR_DANCE]; + const attackingDance: Moves[] = [ Moves.AQUA_STEP, Moves.PETAL_DANCE, Moves.FIERY_DANCE, Moves.REVELATION_DANCE, Moves.FEATHER_DANCE]; + if (source.getBattlerIndex() !== dancer.getBattlerIndex()) { + if (attackingDance.includes(move.getMove().id)) { + const target= source.getBattlerIndex() === BattlerIndex.PLAYER || BattlerIndex.PLAYER_2 ? + dancer.scene.getEnemyPokemon().getBattlerIndex() : BattlerIndex.ATTACKER; + dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [target], move)); + } else if (selfDance.includes(move.getMove().id)) { + dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move)); + } + } + return true; + } +} + export class StatChangeMultiplierAbAttr extends AbAttr { private multiplier: integer; @@ -2575,6 +2600,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, ...args: any[]): Promise { + return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, 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); @@ -3318,7 +3348,7 @@ export function initAbilities() { new Ability(Abilities.INNARDS_OUT, 7) .unimplemented(), 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 fc6af354a..5f22a5a14 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, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, 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, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, 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"; @@ -2292,7 +2292,10 @@ export class MovePhase extends BattlePhase { if (!cancelled.value) this.showFailedText(failedText); } - + this.scene.getPlayerField().forEach(pokemon => { + applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon); + }) + this.end(); }; From c5a27281e2358544a03ef8757d5133bdf3e026e8 Mon Sep 17 00:00:00 2001 From: YounesM Date: Tue, 7 May 2024 12:39:20 +0200 Subject: [PATCH 2/3] corrected target selection for double battles, improved ability detection --- src/data/ability.ts | 30 ++++++++++++++++-------------- src/phases.ts | 13 +++++++++---- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 68f4b06c6..39789ab95 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -9,7 +9,7 @@ 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, RecoilAttr, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, StrengthSapHealAttr, allMoves, StatusMove } from "./move"; +import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, RecoilAttr, StatusMoveTypeImmunityAttr, FlinchAttr, OneHitKOAttr, HitHealAttr, StrengthSapHealAttr, allMoves, StatusMove, SelfStatusMove} from "./move"; import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; import { ArenaTagType } from "./enums/arena-tag-type"; import { Stat } from "./pokemon-stat"; @@ -2128,27 +2128,29 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { } export class PostMoveUsedAbAttr extends AbAttr { - applyPostMoveUsed(pokemon: Pokemon, move: PokemonMove, source: Pokemon, args: any[]): boolean | Promise { + applyPostMoveUsed(pokemon: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], args: any[]): boolean | Promise { return false; } } export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { - applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, args: any[]): boolean | Promise { - const selfDance: Moves[] = [ Moves.DRAGON_DANCE, Moves.SWORDS_DANCE, - Moves.TEETER_DANCE, Moves.VICTORY_DANCE, Moves.QUIVER_DANCE, Moves.CLANGOROUS_SOUL, Moves.LUNAR_DANCE]; - const attackingDance: Moves[] = [ Moves.AQUA_STEP, Moves.PETAL_DANCE, Moves.FIERY_DANCE, Moves.REVELATION_DANCE, Moves.FEATHER_DANCE]; + applyPostMoveUsed(dancer: Pokemon, move: PokemonMove, source: Pokemon, targets: BattlerIndex[], args: any[]): boolean | Promise { if (source.getBattlerIndex() !== dancer.getBattlerIndex()) { - if (attackingDance.includes(move.getMove().id)) { - const target= source.getBattlerIndex() === BattlerIndex.PLAYER || BattlerIndex.PLAYER_2 ? - dancer.scene.getEnemyPokemon().getBattlerIndex() : BattlerIndex.ATTACKER; - dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [target], move)); - } else if (selfDance.includes(move.getMove().id)) { - dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move)); + if (move.getMove() instanceof AttackMove || move 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) { + dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true)); } } return true; } + + 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 { @@ -2601,8 +2603,8 @@ export function applyPostDefendAbAttrs(attrType: { new(...args: any[]): PostDefe } export function applyPostMoveUsedAbAttrs(attrType: { new(...args: any[]): PostMoveUsedAbAttr }, - pokemon: Pokemon, move: PokemonMove, source: Pokemon, ...args: any[]): Promise { - return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPostMoveUsed(pokemon, move, source, args), args); + 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 }, diff --git a/src/phases.ts b/src/phases.ts index 5f22a5a14..e8011d4b6 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2185,7 +2185,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(); } @@ -2292,9 +2292,14 @@ export class MovePhase extends BattlePhase { if (!cancelled.value) this.showFailedText(failedText); } - this.scene.getPlayerField().forEach(pokemon => { - applyPostMoveUsedAbAttrs(PostMoveUsedAbAttr, pokemon, this.move, this.pokemon); - }) + if (this.move.getMove().hasFlag(MoveFlags.DANCE_MOVE) && !this.followUp) { + 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(); }; From 5c1f99d9e5b22ec92730a185425cfa39d80b8ab5 Mon Sep 17 00:00:00 2001 From: YounesM Date: Mon, 13 May 2024 00:05:53 +0200 Subject: [PATCH 3/3] Added TSDoc --- src/data/ability.ts | 19 ++++++++++++++++++- src/phases.ts | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index cb17d404d..0adfafb25 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2140,25 +2140,42 @@ export class PostBiomeChangeTerrainChangeAbAttr extends PostBiomeChangeAbAttr { } } +/** + * Triggers just after a move is used either by the opponent or the player + */ 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 + */ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr { 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 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 + else if (move.getMove() instanceof SelfStatusMove) { dancer.scene.unshiftPhase(new MovePhase(dancer.scene, dancer, [dancer.getBattlerIndex()], move, true)); } } return true; } + /** + * Get the correct targets of Dancer ability + * + * @param dancer Pokemon with Dancer ability + * @param source Source of the dancing move + * @param targets Targets of the dancing move + */ getTarget(dancer: Pokemon, source: Pokemon, targets: BattlerIndex[]) : BattlerIndex[] { if (dancer.isPlayer()) return source.isPlayer() ? targets : [source.getBattlerIndex()]; diff --git a/src/phases.ts b/src/phases.ts index 9d8e636d3..5fefa8a6d 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2292,7 +2292,9 @@ export class MovePhase extends BattlePhase { if (!cancelled.value) 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); })