diff --git a/src/data/move.ts b/src/data/move.ts index 459ee1adce4..78f23effa81 100755 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -86,6 +86,10 @@ export enum MoveFlags { WIND_MOVE = 1 << 14, TRIAGE_MOVE = 1 << 15, IGNORE_ABILITIES = 1 << 16, + /** + * Enables all hits of a multi-hit move to be accuracy checked individually + */ + CHECK_ALL_HITS = 1 << 17, } type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; @@ -346,6 +350,11 @@ export default class Move implements Localizable { return this; } + checkAllHits(checkAllHits?: boolean): this { + this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits); + return this; + } + checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon): boolean { switch (flag) { case MoveFlags.MAKES_CONTACT: @@ -955,8 +964,7 @@ export enum MultiHitType { _2, _2_TO_5, _3, - _3_INCR, - _1_TO_10, + _10, BEAT_UP, } @@ -1287,37 +1295,8 @@ export class MultiHitAttr extends MoveAttr { case MultiHitType._3: hitTimes = 3; break; - case MultiHitType._3_INCR: - hitTimes = 3; - // TODO: Add power increase for every hit - break; - case MultiHitType._1_TO_10: - { - const rand = user.randSeedInt(90); - const hitValue = new Utils.IntegerHolder(rand); - applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue); - if (hitValue.value >= 81) { - hitTimes = 1; - } else if (hitValue.value >= 73) { - hitTimes = 2; - } else if (hitValue.value >= 66) { - hitTimes = 3; - } else if (hitValue.value >= 60) { - hitTimes = 4; - } else if (hitValue.value >= 54) { - hitTimes = 5; - } else if (hitValue.value >= 49) { - hitTimes = 6; - } else if (hitValue.value >= 44) { - hitTimes = 7; - } else if (hitValue.value >= 40) { - hitTimes = 8; - } else if (hitValue.value >= 36) { - hitTimes = 9; - } else { - hitTimes = 10; - } - } + case MultiHitType._10: + hitTimes = 10; break; case MultiHitType.BEAT_UP: const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); @@ -2751,6 +2730,43 @@ export class WaterShurikenPowerAttr extends VariablePowerAttr { } } +/** + * Attribute used for multi-hit moves that increase power in increments of the + * move's base power for each hit, namely Triple Kick and Triple Axel. + * @extends VariablePowerAttr + * @see {@linkcode apply} + */ +export class MultiHitPowerIncrementAttr extends VariablePowerAttr { + /** The max number of base power increments allowed for this move */ + private maxHits: integer; + + constructor(maxHits: integer) { + super(); + + this.maxHits = maxHits; + } + + /** + * Increases power of move in increments of the base power for the amount of times + * the move hit. In the case that the move is extended, it will circle back to the + * original base power of the move after incrementing past the maximum amount of + * hits. + * @param user {@linkcode Pokemon} that used the move + * @param target {@linkcode Pokemon} that the move was used on + * @param move {@linkcode Move} with this attribute + * @param args [0] {@linkcode Utils.NumberHolder} for final calculated power of move + * @returns true if attribute application succeeds + */ + apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0); + const power = args[0] as Utils.NumberHolder; + + power.value = move.power * (1 + hitsTotal % this.maxHits); + + return true; + } +} + export class VariableAtkAttr extends MoveAttr { constructor() { super(); @@ -5411,12 +5427,9 @@ export function initMoves() { .attr(SketchAttr) .ignoresVirtual(), new AttackMove(Moves.TRIPLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, 0, 2) - .attr(MultiHitAttr, MultiHitType._3_INCR) - .attr(MissEffectAttr, (user: Pokemon, move: Move) => { - user.turnData.hitsLeft = 1; - return true; - }) - .partial(), + .attr(MultiHitAttr, MultiHitType._3) + .attr(MultiHitPowerIncrementAttr, 3) + .checkAllHits(), new AttackMove(Moves.THIEF, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2) .attr(StealHeldItemChanceAttr, 0.3), new StatusMove(Moves.SPIDER_WEB, Type.BUG, -1, 10, -1, 0, 2) @@ -7268,12 +7281,9 @@ export function initMoves() { new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8) .attr(ForceSwitchOutAttr, true, false), new AttackMove(Moves.TRIPLE_AXEL, Type.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8) - .attr(MultiHitAttr, MultiHitType._3_INCR) - .attr(MissEffectAttr, (user: Pokemon, move: Move) => { - user.turnData.hitsLeft = 1; - return true; - }) - .partial(), + .attr(MultiHitAttr, MultiHitType._3) + .attr(MultiHitPowerIncrementAttr, 3) + .checkAllHits(), new AttackMove(Moves.DUAL_WINGBEAT, Type.FLYING, MoveCategory.PHYSICAL, 40, 90, 10, -1, 0, 8) .attr(MultiHitAttr, MultiHitType._2), new AttackMove(Moves.SCORCHING_SANDS, Type.GROUND, MoveCategory.SPECIAL, 70, 100, 10, 30, 0, 8) @@ -7516,9 +7526,9 @@ export function initMoves() { new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, 100, 0, 9) .attr(StatChangeAttr, BattleStat.SPD, -2, true), new AttackMove(Moves.POPULATION_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9) - .attr(MultiHitAttr, MultiHitType._1_TO_10) + .attr(MultiHitAttr, MultiHitType._10) .slicingMove() - .partial(), + .checkAllHits(), new AttackMove(Moves.ICE_SPINNER, Type.ICE, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9) .attr(ClearTerrainAttr), new AttackMove(Moves.GLAIVE_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 9) diff --git a/src/phases.ts b/src/phases.ts index 45fd7c0b346..055538423be 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, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, HealFromBerryUseAbAttr } 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, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -2833,9 +2833,13 @@ export class MoveEffectPhase extends PokemonPhase { const user = this.getUserPokemon(); - // Hit check only calculated on first hit for multi-hit moves + // Hit check only calculated on first hit for multi-hit moves unless flag is set to check all hits. + // However, if an ability with the MaxMultiHitAbAttr, namely Skill Link, is present, act as a normal + // multi-hit move and proceed with all hits if (user.turnData.hitsLeft < user.turnData.hitCount) { - return true; + if (!this.move.getMove().hasFlag(MoveFlags.CHECK_ALL_HITS) || user.hasAbilityWithAttr(MaxMultiHitAbAttr)) { + return true; + } } if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {