Implement Triple Kick/Triple Axel/Population Bomb (#1341)

* Implement Triple Kick/Triple Axel/Population Bomb

* Add maximum allowed base power increments

* Add documentation

* Fix Population Bomb, Account for Skill Link

* Change multi-hit power increment behavior

* Fix Typo
This commit is contained in:
Amani H 2024-05-28 19:07:35 -04:00 committed by GitHub
parent 4d15269eec
commit 47d39388ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 50 deletions

View File

@ -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)

View File

@ -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)) {