diff --git a/src/data/ability.ts b/src/data/ability.ts index 2ac7d6be1ac..5c68a797881 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2301,6 +2301,8 @@ export class NoFusionAbilityAbAttr extends AbAttr { } } +export class MagicBounceAbAttr extends AbAttr { } + function applyAbAttrsInternal(attrType: { new(...args: any[]): TAttr }, pokemon: Pokemon, applyFunc: AbAttrApplyFunc, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise { return new Promise(resolve => { @@ -2925,7 +2927,7 @@ export function initAbilities() { .partial(), new Ability(Abilities.MAGIC_BOUNCE, 5) .ignorable() - .unimplemented(), + .attr(MagicBounceAbAttr), new Ability(Abilities.SAP_SIPPER, 5) .attr(TypeImmunityStatChangeAbAttr, Type.GRASS, BattleStat.ATK, 1) .ignorable(), diff --git a/src/phases.ts b/src/phases.ts index a120e5224d2..1ea3b30ee68 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1,63 +1,231 @@ -import BattleScene, { bypassLogin, startingWave } from "./battle-scene"; -import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; +import BattleScene, {bypassLogin, startingWave} from "./battle-scene"; +import { + DamageResult, + default as Pokemon, + EnemyPokemon, + FieldPosition, + HitResult, + MoveResult, + PlayerPokemon, + PokemonMove, + TurnMove +} from "./field/pokemon"; import * as Utils from './utils'; -import { Moves } from "./data/enums/moves"; -import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, OneHitKOAttr, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, DelayedAttackAttr, RechargeAttr, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, FixedDamageAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr } from "./data/move"; -import { Mode } from './ui/ui'; -import { Command } from "./ui/command-ui-handler"; -import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, PokemonFriendshipBoosterModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, PokemonMoveAccuracyBoosterModifier } from "./modifier/modifier"; -import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; -import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; -import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims"; -import { StatusEffect, getStatusEffectActivationText, getStatusEffectCatchRateMultiplier, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./data/status-effect"; -import { SummaryUiMode } from "./ui/summary-ui-handler"; +import {Moves} from "./data/enums/moves"; +import { + allMoves, + applyFilteredMoveAttrs, + applyMoveAttrs, + AttackMove, + BypassSleepAttr, + ChargeAttr, + CopyMoveAttr, + FixedDamageAttr, + ForceSwitchOutAttr, + getMoveTargets, + HealStatusEffectAttr, + HitsTagAttr, + IgnoreOpponentStatChangesAttr, + MissEffectAttr, + MoveAttr, + MoveEffectAttr, + MoveEffectTrigger, + MoveFlags, + MoveTarget, + MoveTargetSet, + MultiHitAttr, + NoEffectAttr, + OneHitKOAccuracyAttr, + OverrideMoveEffectAttr, + PreMoveMessageAttr, + SelfStatusMove, + VariableAccuracyAttr +} from "./data/move"; +import {Mode} from './ui/ui'; +import {Command} from "./ui/command-ui-handler"; +import {Stat} from "./data/pokemon-stat"; +import { + BerryModifier, + ContactHeldItemTransferChanceModifier, + EnemyAttackStatusEffectChanceModifier, + EnemyPersistentModifier, + EnemyStatusEffectHealChanceModifier, + EnemyTurnHealModifier, + ExpBalanceModifier, + ExpBoosterModifier, + ExpShareModifier, + ExtraModifierModifier, + FlinchChanceModifier, + HealingBoosterModifier, + HitHealModifier, + IvScannerModifier, + LapsingPersistentModifier, + LapsingPokemonHeldItemModifier, + MapModifier, + Modifier, + MoneyInterestModifier, + MoneyMultiplierModifier, + MultipleParticipantExpBonusModifier, + PersistentModifier, + PokemonExpBoosterModifier, + PokemonFriendshipBoosterModifier, + PokemonHeldItemModifier, + PokemonInstantReviveModifier, + PokemonMoveAccuracyBoosterModifier, + PokemonMultiHitModifier, + SwitchEffectTransferModifier, + TempBattleStatBoosterModifier, + TurnHealModifier, + TurnHeldItemTransferModifier +} from "./modifier/modifier"; +import PartyUiHandler, {PartyOption, PartyUiMode} from "./ui/party-ui-handler"; +import { + doPokeballBounceAnim, + getPokeballAtlasKey, + getPokeballCatchMultiplier, + getPokeballTintColor, + PokeballType +} from "./data/pokeball"; +import {CommonAnim, CommonBattleAnim, initMoveAnim, loadMoveAnimAssets, MoveAnim} from "./data/battle-anims"; +import { + getStatusEffectActivationText, + getStatusEffectCatchRateMultiplier, + getStatusEffectHealText, + getStatusEffectObtainText, + getStatusEffectOverlapText, + StatusEffect +} from "./data/status-effect"; +import {SummaryUiMode} from "./ui/summary-ui-handler"; import EvolutionSceneHandler from "./ui/evolution-scene-handler"; -import { EvolutionPhase } from "./evolution-phase"; -import { Phase } from "./phase"; -import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat"; -import { biomeLinks, getBiomeName } from "./data/biomes"; -import { Biome } from "./data/enums/biome"; -import { ModifierTier } from "./modifier/modifier-tier"; -import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type"; +import {EvolutionPhase} from "./evolution-phase"; +import {Phase} from "./phase"; +import {BattleStat, getBattleStatLevelChangeDescription, getBattleStatName} from "./data/battle-stat"; +import {biomeLinks, getBiomeName} from "./data/biomes"; +import {Biome} from "./data/enums/biome"; +import {ModifierTier} from "./modifier/modifier-tier"; +import { + FusePokemonModifierType, + getDailyRunStarterModifiers, + getEnemyBuffModifierForWave, + getModifierType, + getPlayerModifierTypeOptions, + getPlayerShopModifierTypeOptionsForWave, + ModifierPoolType, + ModifierType, + ModifierTypeFunc, + ModifierTypeOption, + modifierTypes, + PokemonModifierType, + PokemonMoveModifierType, + regenerateModifierPoolThresholds, + RememberMoveModifierType, + TmModifierType +} from "./modifier/modifier-type"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; -import { BattlerTagLapseType, BounceTag, EncoreTag, HideSpriteTag as HiddenTag, ProtectedTag, TrappedTag } from "./data/battler-tags"; -import { BattlerTagType } from "./data/enums/battler-tag-type"; -import { getPokemonMessage } from "./messages"; -import { Starter } from "./ui/starter-select-ui-handler"; -import { Gender } from "./data/gender"; -import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; -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 } from "./data/ability"; -import { Unlockables, getUnlockableName } from "./system/unlockables"; -import { getBiomeKey } from "./field/arena"; -import { BattleType, BattlerIndex, TurnCommand } from "./battle"; -import { BattleSpec } from "./enums/battle-spec"; -import { Species } from "./data/enums/species"; -import { HealAchv, LevelAchv, achvs } from "./system/achv"; -import { TrainerSlot, trainerConfigs } from "./data/trainer-config"; -import { TrainerType } from "./data/enums/trainer-type"; -import { EggHatchPhase } from "./egg-hatch-phase"; -import { Egg } from "./data/egg"; -import { vouchers } from "./system/voucher"; -import { loggedInUser, updateUserInfo } from "./account"; -import { PlayerGender, SessionSaveData } from "./system/game-data"; -import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; -import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; -import { battleSpecDialogue, getCharVariantFromDialogue } from "./data/dialogue"; -import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "./ui/modifier-select-ui-handler"; -import { Setting } from "./system/settings"; -import { Tutorial, handleTutorial } from "./tutorial"; -import { TerrainType } from "./data/terrain"; -import { OptionSelectConfig, OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; -import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; -import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; -import { GameModes, gameModes } from "./game-mode"; -import { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; +import { + BattlerTag, + BattlerTagLapseType, + BounceTag, + EncoreTag, + HideSpriteTag as HiddenTag, + ProtectedTag, + TrappedTag +} from "./data/battler-tags"; +import {BattlerTagType} from "./data/enums/battler-tag-type"; +import {getPokemonMessage} from "./messages"; +import {Starter} from "./ui/starter-select-ui-handler"; +import {Gender} from "./data/gender"; +import { + getRandomWeatherType, + getTerrainBlockMessage, + getWeatherDamageMessage, + getWeatherLapseMessage, + Weather, + WeatherType +} from "./data/weather"; +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 { + AlwaysHitAbAttr, + applyAbAttrs, + applyBattleStatMultiplierAbAttrs, + applyCheckTrappedAbAttrs, + applyPostAttackAbAttrs, + applyPostBattleAbAttrs, + applyPostBattleInitAbAttrs, + applyPostDefendAbAttrs, + applyPostFaintAbAttrs, + applyPostKnockOutAbAttrs, + applyPostStatChangeAbAttrs, + applyPostSummonAbAttrs, + applyPostTurnAbAttrs, + applyPostVictoryAbAttrs, + applyPostWeatherLapseAbAttrs, + applyPreStatChangeAbAttrs, + applyPreSwitchOutAbAttrs, + applyPreWeatherEffectAbAttrs, + BattleStatMultiplierAbAttr, + BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, MagicBounceAbAttr, + CheckTrappedAbAttr, + IgnoreOpponentStatChangesAbAttr, + IncreasePpAbAttr, + IncrementMovePriorityAbAttr, + PostAttackAbAttr, + PostBattleAbAttr, + PostBattleInitAbAttr, + PostBiomeChangeAbAttr, + PostDefendAbAttr, + PostFaintAbAttr, + PostKnockOutAbAttr, + PostStatChangeAbAttr, + PostSummonAbAttr, + PostTurnAbAttr, + PostVictoryAbAttr, + PostWeatherLapseAbAttr, + PreSwitchOutAbAttr, + PreventBerryUseAbAttr, + PreWeatherDamageAbAttr, + ProtectStatAbAttr, + RedirectMoveAbAttr, + RunSuccessAbAttr, + StatChangeMultiplierAbAttr, + SuppressWeatherEffectAbAttr, + SyncEncounterNatureAbAttr +} from "./data/ability"; +import {getUnlockableName, Unlockables} from "./system/unlockables"; +import {getBiomeKey} from "./field/arena"; +import {BattlerIndex, BattleType, TurnCommand} from "./battle"; +import {BattleSpec} from "./enums/battle-spec"; +import {Species} from "./data/enums/species"; +import {achvs, HealAchv, LevelAchv} from "./system/achv"; +import {trainerConfigs, TrainerSlot} from "./data/trainer-config"; +import {TrainerType} from "./data/enums/trainer-type"; +import {EggHatchPhase} from "./egg-hatch-phase"; +import {Egg} from "./data/egg"; +import {vouchers} from "./system/voucher"; +import {loggedInUser, updateUserInfo} from "./account"; +import {PlayerGender, SessionSaveData} from "./system/game-data"; +import {addPokeballCaptureStars, addPokeballOpenParticles} from "./field/anims"; +import { + SpeciesFormChangeActiveTrigger, + SpeciesFormChangeManualTrigger, + SpeciesFormChangeMoveLearnedTrigger, + SpeciesFormChangePostMoveTrigger, + SpeciesFormChangePreMoveTrigger +} from "./data/pokemon-forms"; +import {battleSpecDialogue, getCharVariantFromDialogue} from "./data/dialogue"; +import ModifierSelectUiHandler, {SHOP_OPTIONS_ROW_LIMIT} from "./ui/modifier-select-ui-handler"; +import {Setting} from "./system/settings"; +import {handleTutorial, Tutorial} from "./tutorial"; +import {TerrainType} from "./data/terrain"; +import {OptionSelectConfig, OptionSelectItem} from "./ui/abstact-option-select-ui-handler"; +import {SaveSlotUiMode} from "./ui/save-slot-select-ui-handler"; +import {fetchDailyRunSeed, getDailyRunStarters} from "./data/daily-run"; +import {GameModes, gameModes} from "./game-mode"; +import {getPokemonSpecies, speciesStarters} from "./data/pokemon-species"; import i18next from './plugins/i18n'; -import { STARTER_FORM_OVERRIDE, STARTER_SPECIES_OVERRIDE } from './overrides'; +import {STARTER_FORM_OVERRIDE, STARTER_SPECIES_OVERRIDE} from './overrides'; export class LoginPhase extends Phase { private showText: boolean; @@ -2262,16 +2430,11 @@ export class MovePhase extends BattlePhase { // Assume conditions affecting targets only apply to moves with a single target let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove()); let failedText = null; - const isBounced = this.move.getMove().hasFlag(MoveFlags.MAGIC_COAT_MOVE) && targets[0].findTags(t => t instanceof BounceTag).find(t => targets[0].lapseTag(t.tagType)); if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove())) success = false; else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.move.getMove())) { success = false; failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain.terrainType); - } else if (success && isBounced) { - this.showFailedText(this.pokemon.getOpponentDescriptor() + "\nbounced the move back!"); - this.targets = [this.pokemon.getBattlerIndex()]; - this.pokemon = targets[0]; } if (success) this.scene.unshiftPhase(this.getEffectPhase()); @@ -2369,8 +2532,8 @@ export class MoveEffectPhase extends PokemonPhase { start() { super.start(); - const user = this.getUserPokemon(); - const targets = this.getTargets(); + let user = this.getUserPokemon(); + let targets = this.getTargets(); if (!user?.isOnField()) return super.end(); @@ -2413,12 +2576,24 @@ export class MoveEffectPhase extends PokemonPhase { return this.end(); } + let isBounced: BattlerTag | boolean = false; + for (let opponent of targets) { + isBounced = this.move.getMove().hasFlag(MoveFlags.MAGIC_COAT_MOVE) && (targets[0].findTags(t => t instanceof BounceTag).find(t => targets[0].lapseTag(t.tagType)) || opponent.hasAbilityWithAttr(MagicBounceAbAttr)); + if (isBounced) { + this.scene.queueMessage(getPokemonMessage(targets[0], '\nbounced the move back!')); + const tempTargets = targets; + targets = [user]; + user = tempTargets[0]; + break; + } + } + const applyAttrs: Promise[] = []; // Move animation only needs one target new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => { for (let target of targets) { - if (!targetHitChecks[target.getBattlerIndex()]) { + if (!targetHitChecks[target.getBattlerIndex()] && !isBounced) { user.turnData.hitCount = 1; user.turnData.hitsLeft = 1; this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));