[Ability] Serene Grace, Shield Dust, Sheer Force (P) (#246)
Co-authored-by: okimin <danielgaston6@gmail.com>
This commit is contained in:
parent
3b3b9e39af
commit
c52e0ac5b6
|
@ -1045,6 +1045,56 @@ export class PreAttackAbAttr extends AbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies moves additional effects with multipliers, ie. Sheer Force, Serene Grace.
|
||||
* @extends AbAttr
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class MoveEffectChanceMultiplierAbAttr extends AbAttr {
|
||||
private chanceMultiplier: number;
|
||||
|
||||
constructor(chanceMultiplier?: number) {
|
||||
super(true);
|
||||
this.chanceMultiplier = chanceMultiplier;
|
||||
}
|
||||
/**
|
||||
* @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance. Has to be higher than or equal to 0.
|
||||
* [1]: {@linkcode Moves } Move used by the ability user.
|
||||
*/
|
||||
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
|
||||
if ((args[0] as Utils.NumberHolder).value <= 0 || (args[1] as Move).id === Moves.ORDER_UP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(args[0] as Utils.NumberHolder).value *= this.chanceMultiplier;
|
||||
(args[0] as Utils.NumberHolder).value = Math.min((args[0] as Utils.NumberHolder).value, 100);
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets incoming moves additional effect chance to zero, ignoring all effects from moves. ie. Shield Dust.
|
||||
* @extends PreDefendAbAttr
|
||||
* @see {@linkcode applyPreDefend}
|
||||
*/
|
||||
export class IgnoreMoveEffectsAbAttr extends PreDefendAbAttr {
|
||||
/**
|
||||
* @param args [0]: {@linkcode Utils.NumberHolder} Move additional effect chance.
|
||||
*/
|
||||
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
|
||||
if ((args[0] as Utils.NumberHolder).value <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(args[0] as Utils.NumberHolder).value = 0;
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class VariableMovePowerAbAttr extends PreAttackAbAttr {
|
||||
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
|
||||
//const power = args[0] as Utils.NumberHolder;
|
||||
|
@ -1418,7 +1468,8 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
|
|||
}
|
||||
|
||||
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
|
||||
/**Status inflicted by abilities post attacking are also considered additional effects.*/
|
||||
if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance && !pokemon.status) {
|
||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
||||
return attacker.trySetStatus(effect, true, pokemon);
|
||||
}
|
||||
|
@ -1448,10 +1499,9 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
|
|||
}
|
||||
|
||||
applyPostAttack(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||
if (pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
|
||||
/**Battler tags inflicted by abilities post attacking are also considered additional effects.*/
|
||||
if (!attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker && (!this.contactRequired || move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) && pokemon.randSeedInt(100) < this.chance(attacker, pokemon, move) && !pokemon.status) {
|
||||
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
|
||||
|
||||
|
||||
return attacker.addTag(effect);
|
||||
}
|
||||
|
||||
|
@ -2402,6 +2452,35 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Condition function to applied to abilities related to Sheer Force.
|
||||
* Checks if last move used against target was affected by a Sheer Force user and:
|
||||
* Disables: Color Change, Pickpocket, Wimp Out, Emergency Exit, Berserk, Anger Shell
|
||||
* @returns {AbAttrCondition} If false disables the ability which the condition is applied to.
|
||||
*/
|
||||
function getSheerForceHitDisableAbCondition(): AbAttrCondition {
|
||||
return (pokemon: Pokemon) => {
|
||||
if (!pokemon.turnData) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastReceivedAttack = pokemon.turnData.attacksReceived[0];
|
||||
if (!lastReceivedAttack) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastAttacker = pokemon.getOpponents().find(p => p.id === lastReceivedAttack.sourceId);
|
||||
if (!lastAttacker) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**if the last move chance is greater than or equal to cero, and the last attacker's ability is sheer force*/
|
||||
const SheerForceAffected = allMoves[lastReceivedAttack.move].chance >= 0 && lastAttacker.hasAbility(Abilities.SHEER_FORCE);
|
||||
|
||||
return !SheerForceAffected;
|
||||
};
|
||||
}
|
||||
|
||||
function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
|
||||
return (pokemon: Pokemon) => {
|
||||
if (pokemon.scene.arena.weather?.isEffectSuppressed(pokemon.scene)) {
|
||||
|
@ -3932,7 +4011,8 @@ export function initAbilities() {
|
|||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.COLOR_CHANGE, 3)
|
||||
.attr(PostDefendTypeChangeAbAttr),
|
||||
.attr(PostDefendTypeChangeAbAttr)
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
new Ability(Abilities.IMMUNITY, 3)
|
||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
|
||||
.ignorable(),
|
||||
|
@ -3940,8 +4020,8 @@ export function initAbilities() {
|
|||
.attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1, (pokemon: Pokemon) => !pokemon.status || pokemon.status.effect !== StatusEffect.FREEZE)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.SHIELD_DUST, 3)
|
||||
.ignorable()
|
||||
.unimplemented(),
|
||||
.attr(IgnoreMoveEffectsAbAttr)
|
||||
.partial(),
|
||||
new Ability(Abilities.OWN_TEMPO, 3)
|
||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
||||
.attr(IntimidateImmunityAbAttr)
|
||||
|
@ -3984,7 +4064,8 @@ export function initAbilities() {
|
|||
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.SERENE_GRACE, 3)
|
||||
.unimplemented(),
|
||||
.attr(MoveEffectChanceMultiplierAbAttr, 2)
|
||||
.partial(),
|
||||
new Ability(Abilities.SWIFT_SWIM, 3)
|
||||
.attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
|
||||
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
|
||||
|
@ -4260,9 +4341,12 @@ export function initAbilities() {
|
|||
new Ability(Abilities.BAD_DREAMS, 4)
|
||||
.attr(PostTurnHurtIfSleepingAbAttr),
|
||||
new Ability(Abilities.PICKPOCKET, 5)
|
||||
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT)),
|
||||
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT))
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
new Ability(Abilities.SHEER_FORCE, 5)
|
||||
.unimplemented(),
|
||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461/4096)
|
||||
.attr(MoveEffectChanceMultiplierAbAttr, 0)
|
||||
.partial(),
|
||||
new Ability(Abilities.CONTRARY, 5)
|
||||
.attr(StatChangeMultiplierAbAttr, -1)
|
||||
.ignorable(),
|
||||
|
@ -4471,8 +4555,10 @@ export function initAbilities() {
|
|||
new Ability(Abilities.STAMINA, 7)
|
||||
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattleStat.DEF, 1),
|
||||
new Ability(Abilities.WIMP_OUT, 7)
|
||||
.condition(getSheerForceHitDisableAbCondition())
|
||||
.unimplemented(),
|
||||
new Ability(Abilities.EMERGENCY_EXIT, 7)
|
||||
.condition(getSheerForceHitDisableAbCondition())
|
||||
.unimplemented(),
|
||||
new Ability(Abilities.WATER_COMPACTION, 7)
|
||||
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2),
|
||||
|
@ -4498,7 +4584,8 @@ export function initAbilities() {
|
|||
new Ability(Abilities.STEELWORKER, 7)
|
||||
.attr(MoveTypePowerBoostAbAttr, Type.STEEL),
|
||||
new Ability(Abilities.BERSERK, 7)
|
||||
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [BattleStat.SPATK], 1),
|
||||
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [BattleStat.SPATK], 1)
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
new Ability(Abilities.SLUSH_RUSH, 7)
|
||||
.attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
|
||||
.condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW)),
|
||||
|
@ -4757,7 +4844,8 @@ export function initAbilities() {
|
|||
.ignorable(),
|
||||
new Ability(Abilities.ANGER_SHELL, 9)
|
||||
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 1)
|
||||
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.DEF, BattleStat.SPDEF ], -1),
|
||||
.attr(PostDefendHpGatedStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, 0.5, [ BattleStat.DEF, BattleStat.SPDEF ], -1)
|
||||
.condition(getSheerForceHitDisableAbCondition()),
|
||||
new Ability(Abilities.PURIFYING_SALT, 9)
|
||||
.attr(StatusEffectImmunityAbAttr)
|
||||
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.GHOST, 0.5)
|
||||
|
|
162
src/data/move.ts
162
src/data/move.ts
|
@ -9,7 +9,7 @@ import { Type } from "./type";
|
|||
import * as Utils from "../utils";
|
||||
import { WeatherType } from "./weather";
|
||||
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
|
||||
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr } from "./ability";
|
||||
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr } from "./ability";
|
||||
import { allAbilities } from "./ability";
|
||||
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier";
|
||||
import { BattlerIndex } from "../battle";
|
||||
|
@ -829,6 +829,22 @@ export class MoveEffectAttr extends MoveAttr {
|
|||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||
return this.canApply(user, target, move, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the used move's additional effect chance.
|
||||
* If user's ability has MoveEffectChanceMultiplierAbAttr or IgnoreMoveEffectsAbAttr modifies the base chance.
|
||||
* @param user {@linkcode Pokemon} using this move
|
||||
* @param target {@linkcode Pokemon} target of this move
|
||||
* @param move {@linkcode Move} being used
|
||||
* @param selfEffect {@linkcode Boolean} if move targets user.
|
||||
* @returns Move chance value.
|
||||
*/
|
||||
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean): integer {
|
||||
const moveChance = new Utils.NumberHolder(move.chance);
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, moveChance, move, target, selfEffect);
|
||||
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr,target,user,null,null, moveChance);
|
||||
return moveChance.value;
|
||||
}
|
||||
}
|
||||
|
||||
export class PreMoveMessageAttr extends MoveAttr {
|
||||
|
@ -1673,7 +1689,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||
}
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
const statusCheck = move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance;
|
||||
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
|
||||
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
|
||||
if (statusCheck) {
|
||||
const pokemon = this.selfTarget ? user : target;
|
||||
if (pokemon.status) {
|
||||
|
@ -1683,7 +1700,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if ((!pokemon.status || (pokemon.status.effect === this.effect && move.chance < 0))
|
||||
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
|
||||
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
|
||||
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect);
|
||||
return true;
|
||||
|
@ -1693,7 +1710,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
|
|||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
|
||||
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
|
||||
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1712,7 +1730,8 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
|
|||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
|
||||
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
|
||||
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2315,7 +2334,8 @@ export class StatChangeAttr extends MoveEffectAttr {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) {
|
||||
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
|
||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||
const levels = this.getLevels(user);
|
||||
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage));
|
||||
return true;
|
||||
|
@ -3892,18 +3912,14 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||
return false;
|
||||
}
|
||||
|
||||
const chance = this.getTagChance(user, target, move);
|
||||
if (chance < 0 || chance === 100 || user.randSeedInt(100) < chance) {
|
||||
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
|
||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getTagChance(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
return move.chance;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return this.failOnOverlap
|
||||
? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType)
|
||||
|
@ -3953,11 +3969,11 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
|||
}
|
||||
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||
let chance = this.getTagChance(user, target, move);
|
||||
if (chance < 0) {
|
||||
chance = 100;
|
||||
let moveChance = this.getMoveChance(user,target,move,this.selfTarget);
|
||||
if (moveChance < 0) {
|
||||
moveChance = 100;
|
||||
}
|
||||
return Math.floor(this.getTagTargetBenefitScore(user, target, move) * (chance / 100));
|
||||
return Math.floor(this.getTagTargetBenefitScore(user, target, move) * (moveChance / 100));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4221,10 +4237,14 @@ export class RemoveArenaTagsAttr extends MoveEffectAttr {
|
|||
export class AddArenaTrapTagAttr extends AddArenaTagAttr {
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user, target, move) => {
|
||||
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
|
||||
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
||||
if (move.category !== MoveCategory.STATUS || !user.scene.arena.getTagOnSide(this.tagType, side)) {
|
||||
return true;
|
||||
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
||||
if (move.category !== MoveCategory.STATUS || !user.scene.arena.getTagOnSide(this.tagType, side)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
||||
return tag.layers < tag.maxLayers;
|
||||
};
|
||||
|
@ -5617,7 +5637,7 @@ export function initMoves() {
|
|||
.attr(ChargeAttr, ChargeAnim.FLY_CHARGING, "flew\nup high!", BattlerTagType.FLYING)
|
||||
.condition(failOnGravityCondition)
|
||||
.ignoresVirtual(),
|
||||
new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, 100, 0, 1)
|
||||
new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1)
|
||||
.attr(TrapAttr, BattlerTagType.BIND),
|
||||
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
|
||||
new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1),
|
||||
|
@ -5650,7 +5670,7 @@ export function initMoves() {
|
|||
.attr(MinimizeAccuracyAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, 100, 0, 1)
|
||||
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1)
|
||||
.attr(TrapAttr, BattlerTagType.WRAP),
|
||||
new AttackMove(Moves.TAKE_DOWN, Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, 0, 1)
|
||||
.attr(RecoilAttr)
|
||||
|
@ -5675,7 +5695,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.PIN_MISSILE, Type.BUG, MoveCategory.PHYSICAL, 25, 95, 20, -1, 0, 1)
|
||||
.attr(MultiHitAttr)
|
||||
.makesContact(false),
|
||||
new StatusMove(Moves.LEER, Type.NORMAL, 100, 30, 100, 0, 1)
|
||||
new StatusMove(Moves.LEER, Type.NORMAL, 100, 30, -1, 0, 1)
|
||||
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.BITE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 1)
|
||||
|
@ -5784,7 +5804,7 @@ export function initMoves() {
|
|||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.DRAGON_RAGE, Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1)
|
||||
.attr(FixedDamageAttr, 40),
|
||||
new AttackMove(Moves.FIRE_SPIN, Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, 100, 0, 1)
|
||||
new AttackMove(Moves.FIRE_SPIN, Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 1)
|
||||
.attr(TrapAttr, BattlerTagType.FIRE_SPIN),
|
||||
new AttackMove(Moves.THUNDER_SHOCK, Type.ELECTRIC, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||
|
@ -5900,11 +5920,11 @@ export function initMoves() {
|
|||
.attr(StatusEffectAttr, StatusEffect.BURN),
|
||||
new AttackMove(Moves.WATERFALL, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 1)
|
||||
.attr(FlinchAttr),
|
||||
new AttackMove(Moves.CLAMP, Type.WATER, MoveCategory.PHYSICAL, 35, 85, 15, 100, 0, 1)
|
||||
new AttackMove(Moves.CLAMP, Type.WATER, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 1)
|
||||
.attr(TrapAttr, BattlerTagType.CLAMP),
|
||||
new AttackMove(Moves.SWIFT, Type.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, 100, 0, 1)
|
||||
new AttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1)
|
||||
.attr(ChargeAttr, ChargeAnim.SKULL_BASH_CHARGING, "lowered\nits head!", null, true)
|
||||
.attr(StatChangeAttr, BattleStat.DEF, 1, true)
|
||||
.ignoresVirtual(),
|
||||
|
@ -6253,7 +6273,7 @@ export function initMoves() {
|
|||
.attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, "foresaw\nan attack!"),
|
||||
new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2)
|
||||
.attr(StatChangeAttr, BattleStat.DEF, -1),
|
||||
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, 100, 0, 2)
|
||||
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
|
||||
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
|
||||
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
|
||||
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
|
||||
|
@ -6329,7 +6349,7 @@ export function initMoves() {
|
|||
.ignoresVirtual(),
|
||||
new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true),
|
||||
new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 3)
|
||||
new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1, true),
|
||||
new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3)
|
||||
.unimplemented(),
|
||||
|
@ -6431,7 +6451,7 @@ export function initMoves() {
|
|||
.slicingMove()
|
||||
.windMove()
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.OVERHEAT, Type.FIRE, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 3)
|
||||
new AttackMove(Moves.OVERHEAT, Type.FIRE, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 3)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -2, true)
|
||||
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE),
|
||||
new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3)
|
||||
|
@ -6464,7 +6484,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING)
|
||||
.punchingMove(),
|
||||
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, 100, 0, 3)
|
||||
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3)
|
||||
.attr(TrapAttr, BattlerTagType.SAND_TOMB)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.SHEER_COLD, Type.ICE, MoveCategory.SPECIAL, 200, 20, 5, -1, 0, 3)
|
||||
|
@ -6534,7 +6554,7 @@ export function initMoves() {
|
|||
.pulseMove(),
|
||||
new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3)
|
||||
.attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, "chose\nDoom Desire as its destiny!"),
|
||||
new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, 100, 0, 3)
|
||||
new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
||||
new SelfStatusMove(Moves.ROOST, Type.FLYING, -1, 5, -1, 0, 4)
|
||||
.attr(HealAttr, 0.5)
|
||||
|
@ -6548,7 +6568,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.WAKE_UP_SLAP, Type.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => targetSleptOrComatoseCondition(user, target, move) ? 2 : 1)
|
||||
.attr(HealStatusEffectAttr, false, StatusEffect.SLEEP),
|
||||
new AttackMove(Moves.HAMMER_ARM, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 4)
|
||||
new AttackMove(Moves.HAMMER_ARM, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 4)
|
||||
.attr(StatChangeAttr, BattleStat.SPD, -1, true)
|
||||
.punchingMove(),
|
||||
new AttackMove(Moves.GYRO_BALL, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
|
||||
|
@ -6582,7 +6602,7 @@ export function initMoves() {
|
|||
.target(MoveTarget.ATTACKER),
|
||||
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
|
||||
.attr(ForceSwitchOutAttr, true, false),
|
||||
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 4)
|
||||
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
|
||||
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
|
||||
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.BALL ? 2 : 1),
|
||||
|
@ -6626,9 +6646,9 @@ export function initMoves() {
|
|||
new SelfStatusMove(Moves.COPYCAT, Type.NORMAL, -1, 20, -1, 0, 4)
|
||||
.attr(CopyMoveAttr)
|
||||
.ignoresVirtual(),
|
||||
new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 4)
|
||||
new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
|
||||
.unimplemented(),
|
||||
new StatusMove(Moves.GUARD_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 4)
|
||||
new StatusMove(Moves.GUARD_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
|
||||
.unimplemented(),
|
||||
new AttackMove(Moves.PUNISHMENT, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
|
||||
.makesContact(true)
|
||||
|
@ -6756,7 +6776,7 @@ export function initMoves() {
|
|||
.attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5)
|
||||
.ignoresProtect()
|
||||
.target(MoveTarget.BOTH_SIDES),
|
||||
new AttackMove(Moves.DRACO_METEOR, Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 4)
|
||||
new AttackMove(Moves.DRACO_METEOR, Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
||||
new AttackMove(Moves.DISCHARGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
||||
|
@ -6764,7 +6784,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.LAVA_PLUME, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
|
||||
.attr(StatusEffectAttr, StatusEffect.BURN)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.LEAF_STORM, Type.GRASS, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 4)
|
||||
new AttackMove(Moves.LEAF_STORM, Type.GRASS, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
||||
new AttackMove(Moves.POWER_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 4),
|
||||
new AttackMove(Moves.ROCK_WRECKER, Type.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4)
|
||||
|
@ -6834,7 +6854,7 @@ export function initMoves() {
|
|||
.unimplemented(),
|
||||
new AttackMove(Moves.CRUSH_GRIP, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
|
||||
.attr(OpponentHighHpPowerAttr),
|
||||
new AttackMove(Moves.MAGMA_STORM, Type.FIRE, MoveCategory.SPECIAL, 100, 75, 5, 100, 0, 4)
|
||||
new AttackMove(Moves.MAGMA_STORM, Type.FIRE, MoveCategory.SPECIAL, 100, 75, 5, -1, 0, 4)
|
||||
.attr(TrapAttr, BattlerTagType.MAGMA_STORM),
|
||||
new StatusMove(Moves.DARK_VOID, Type.DARK, 50, 10, -1, 0, 4)
|
||||
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
||||
|
@ -7094,7 +7114,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.ICICLE_CRASH, Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, 30, 0, 5)
|
||||
.attr(FlinchAttr)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.V_CREATE, Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, 100, 0, 5)
|
||||
new AttackMove(Moves.V_CREATE, Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, 0, 5)
|
||||
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF, BattleStat.SPD ], -1, true),
|
||||
new AttackMove(Moves.FUSION_FLARE, Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 5)
|
||||
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
||||
|
@ -7113,7 +7133,7 @@ export function initMoves() {
|
|||
.condition(new FirstMoveCondition()),
|
||||
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
|
||||
.condition((user, target, move) => user.battleData.berriesEaten.length > 0),
|
||||
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, 100, 0, 6)
|
||||
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
|
||||
.target(MoveTarget.ALL)
|
||||
.condition((user,target,move) => {
|
||||
// If any fielded pokémon is grass-type and grounded.
|
||||
|
@ -7132,7 +7152,7 @@ export function initMoves() {
|
|||
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
|
||||
.attr(AddTypeAttr, Type.GHOST)
|
||||
.partial(),
|
||||
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, 100, 0, 6)
|
||||
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1)
|
||||
.soundBased(),
|
||||
new StatusMove(Moves.ION_DELUGE, Type.ELECTRIC, -1, 25, -1, 1, 6)
|
||||
|
@ -7155,7 +7175,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
|
||||
.soundBased()
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, 100, 0, 6)
|
||||
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
|
||||
.attr(ForceSwitchOutAttr, true, false)
|
||||
.soundBased(),
|
||||
|
@ -7168,7 +7188,7 @@ export function initMoves() {
|
|||
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
|
||||
.target(MoveTarget.USER_SIDE)
|
||||
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true),
|
||||
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, 100, 0, 6)
|
||||
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
||||
.target(MoveTarget.ALL)
|
||||
.unimplemented(),
|
||||
new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6)
|
||||
|
@ -7193,9 +7213,9 @@ export function initMoves() {
|
|||
.unimplemented(),
|
||||
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
|
||||
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD),
|
||||
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, 100, 0, 6)
|
||||
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
||||
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, 100, 0, 6)
|
||||
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -1)
|
||||
.soundBased(),
|
||||
new AttackMove(Moves.DIAMOND_STORM, Type.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6)
|
||||
|
@ -7221,7 +7241,7 @@ export function initMoves() {
|
|||
.target(MoveTarget.NEAR_ALLY),
|
||||
new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -2),
|
||||
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, 100, 0, 6)
|
||||
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, -1, 0, 6)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], -1, false, (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
||||
|
@ -7252,7 +7272,7 @@ export function initMoves() {
|
|||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||
new AttackMove(Moves.HOLD_BACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6)
|
||||
.attr(SurviveDamageAttr),
|
||||
new AttackMove(Moves.INFESTATION, Type.BUG, MoveCategory.SPECIAL, 20, 100, 20, 100, 0, 6)
|
||||
new AttackMove(Moves.INFESTATION, Type.BUG, MoveCategory.SPECIAL, 20, 100, 20, -1, 0, 6)
|
||||
.makesContact()
|
||||
.attr(TrapAttr, BattlerTagType.INFESTATION),
|
||||
new AttackMove(Moves.POWER_UP_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 20, 100, 0, 6)
|
||||
|
@ -7261,7 +7281,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.OBLIVION_WING, Type.FLYING, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
|
||||
.attr(HitHealAttr, 0.75)
|
||||
.triageMove(),
|
||||
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 6)
|
||||
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
||||
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
|
||||
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
||||
|
@ -7284,9 +7304,9 @@ export function initMoves() {
|
|||
new AttackMove(Moves.PRECIPICE_BLADES, Type.GROUND, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 6)
|
||||
.makesContact(false)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new AttackMove(Moves.DRAGON_ASCENT, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 6)
|
||||
new AttackMove(Moves.DRAGON_ASCENT, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 6)
|
||||
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
|
||||
new AttackMove(Moves.HYPERSPACE_FURY, Type.DARK, MoveCategory.PHYSICAL, 100, -1, 5, 100, 0, 6)
|
||||
new AttackMove(Moves.HYPERSPACE_FURY, Type.DARK, MoveCategory.PHYSICAL, 100, -1, 5, -1, 0, 6)
|
||||
.attr(StatChangeAttr, BattleStat.DEF, -1, true)
|
||||
.makesContact(false)
|
||||
.ignoresProtect(),
|
||||
|
@ -7410,23 +7430,23 @@ export function initMoves() {
|
|||
.condition(new FirstMoveCondition()),
|
||||
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
|
||||
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER),
|
||||
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 7)
|
||||
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
|
||||
.attr(IgnoreOpponentStatChangesAttr),
|
||||
new AttackMove(Moves.SPARKLING_ARIA, Type.WATER, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 7)
|
||||
new AttackMove(Moves.SPARKLING_ARIA, Type.WATER, MoveCategory.SPECIAL, 90, 100, 10, 100, 0, 7)
|
||||
.attr(HealStatusEffectAttr, false, StatusEffect.BURN)
|
||||
.soundBased()
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS),
|
||||
new AttackMove(Moves.ICE_HAMMER, Type.ICE, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 7)
|
||||
new AttackMove(Moves.ICE_HAMMER, Type.ICE, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7)
|
||||
.attr(StatChangeAttr, BattleStat.SPD, -1, true)
|
||||
.punchingMove(),
|
||||
new StatusMove(Moves.FLORAL_HEALING, Type.FAIRY, -1, 10, -1, 0, 7)
|
||||
.attr(BoostHealAttr, 0.5, 2/3, true, false, (user, target, move) => user.scene.arena.terrain?.terrainType === TerrainType.GRASSY)
|
||||
.triageMove(),
|
||||
new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7),
|
||||
new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, 100, 0, 7)
|
||||
new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, -1, 0, 7)
|
||||
.attr(HitHealAttr, null, Stat.ATK)
|
||||
.attr(StatChangeAttr, BattleStat.ATK, -1)
|
||||
.condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6)
|
||||
|
@ -7439,7 +7459,7 @@ export function initMoves() {
|
|||
.makesContact(false),
|
||||
new StatusMove(Moves.SPOTLIGHT, Type.NORMAL, -1, 15, -1, 3, 7)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, false),
|
||||
new StatusMove(Moves.TOXIC_THREAD, Type.POISON, 100, 20, 100, 0, 7)
|
||||
new StatusMove(Moves.TOXIC_THREAD, Type.POISON, 100, 20, -1, 0, 7)
|
||||
.attr(StatusEffectAttr, StatusEffect.POISON)
|
||||
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
||||
new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7)
|
||||
|
@ -7454,7 +7474,7 @@ export function initMoves() {
|
|||
.attr(StatusCategoryOnAllyAttr)
|
||||
.attr(HealOnAllyAttr, 0.5, true, false)
|
||||
.ballBombMove(),
|
||||
new AttackMove(Moves.ANCHOR_SHOT, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, -1, 0, 7)
|
||||
new AttackMove(Moves.ANCHOR_SHOT, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1),
|
||||
new StatusMove(Moves.PSYCHIC_TERRAIN, Type.PSYCHIC, -1, 10, -1, 0, 7)
|
||||
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
|
||||
|
@ -7498,7 +7518,7 @@ export function initMoves() {
|
|||
.ballBombMove()
|
||||
.makesContact(false)
|
||||
.partial(),
|
||||
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, 100, 0, 7)
|
||||
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7)
|
||||
.attr(StatChangeAttr, BattleStat.DEF, -1, true)
|
||||
.soundBased()
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
|
@ -7535,14 +7555,14 @@ export function initMoves() {
|
|||
new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, 100, 0, 7)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true)
|
||||
.ignoresVirtual(),
|
||||
new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, -1, 0, 7)
|
||||
new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
|
||||
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
|
||||
.ignoresVirtual(),
|
||||
/* End Unused */
|
||||
new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
||||
.partial(),
|
||||
new AttackMove(Moves.FLEUR_CANNON, Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, 100, 0, 7)
|
||||
new AttackMove(Moves.FLEUR_CANNON, Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7)
|
||||
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
||||
new AttackMove(Moves.PSYCHIC_FANGS, Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
|
||||
.bitingMove()
|
||||
|
@ -7565,7 +7585,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
|
||||
.ignoresAbilities()
|
||||
.partial(),
|
||||
new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, 100, 0, 7)
|
||||
new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1),
|
||||
new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7)
|
||||
.attr(FlinchAttr),
|
||||
|
@ -7675,7 +7695,7 @@ export function initMoves() {
|
|||
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, 100, 0, 8)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, true, 1),
|
||||
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, 100, 0, 8)
|
||||
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8)
|
||||
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
||||
.partial(),
|
||||
new StatusMove(Moves.MAGIC_POWDER, Type.PSYCHIC, 100, 20, -1, 0, 8)
|
||||
|
@ -7777,12 +7797,12 @@ export function initMoves() {
|
|||
.danceMove(),
|
||||
new AttackMove(Moves.BODY_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8)
|
||||
.attr(DefAtkAttr),
|
||||
new StatusMove(Moves.DECORATE, Type.FAIRY, -1, 15, 100, 0, 8)
|
||||
new StatusMove(Moves.DECORATE, Type.FAIRY, -1, 15, -1, 0, 8)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 2),
|
||||
new AttackMove(Moves.DRUM_BEATING, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8)
|
||||
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.SNAP_TRAP, Type.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, 100, 0, 8)
|
||||
new AttackMove(Moves.SNAP_TRAP, Type.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, -1, 0, 8)
|
||||
.attr(TrapAttr, BattlerTagType.SNAP_TRAP),
|
||||
new AttackMove(Moves.PYRO_BALL, Type.FIRE, MoveCategory.PHYSICAL, 120, 90, 5, 10, 0, 8)
|
||||
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
||||
|
@ -7834,7 +7854,7 @@ export function initMoves() {
|
|||
new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8)
|
||||
.attr(ClearTerrainAttr)
|
||||
.condition((user, target, move) => !!user.scene.arena.terrain),
|
||||
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, 100, 0, 8)
|
||||
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8)
|
||||
//.attr(StatChangeAttr, BattleStat.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit
|
||||
//.attr(StatChangeAttr, BattleStat.DEF, -1, true)
|
||||
.attr(MultiHitAttr)
|
||||
|
@ -7875,7 +7895,7 @@ export function initMoves() {
|
|||
new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8)
|
||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||
.unimplemented(),
|
||||
new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, 100, 0, 8)
|
||||
new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, -1, 0, 8)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], 1)
|
||||
.target(MoveTarget.NEAR_ALLY),
|
||||
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8)
|
||||
|
@ -7901,7 +7921,7 @@ export function initMoves() {
|
|||
.attr(MultiHitAttr, MultiHitType._3)
|
||||
.attr(CritOnlyAttr)
|
||||
.punchingMove(),
|
||||
new AttackMove(Moves.THUNDER_CAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 90, 15, 100, 0, 8)
|
||||
new AttackMove(Moves.THUNDER_CAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 90, 15, -1, 0, 8)
|
||||
.attr(TrapAttr, BattlerTagType.THUNDER_CAGE),
|
||||
new AttackMove(Moves.DRAGON_ENERGY, Type.DRAGON, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 8)
|
||||
.attr(HpPowerAttr)
|
||||
|
@ -7950,7 +7970,7 @@ export function initMoves() {
|
|||
new SelfStatusMove(Moves.VICTORY_DANCE, Type.FIGHTING, -1, 10, 100, 0, 8)
|
||||
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPD ], 1, true)
|
||||
.danceMove(),
|
||||
new AttackMove(Moves.HEADLONG_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 120, 100, 5, 100, 0, 8)
|
||||
new AttackMove(Moves.HEADLONG_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8)
|
||||
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true)
|
||||
.punchingMove(),
|
||||
new AttackMove(Moves.BARB_BARRAGE, Type.POISON, MoveCategory.PHYSICAL, 60, 100, 10, 50, 0, 8)
|
||||
|
@ -8113,15 +8133,15 @@ export function initMoves() {
|
|||
.makesContact(false),
|
||||
new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
||||
.attr(StatChangeAttr, BattleStat.SPDEF, -2),
|
||||
new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 9)
|
||||
new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
|
||||
.makesContact(false)
|
||||
.partial(),
|
||||
new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
|
||||
.punchingMove(),
|
||||
new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, 100, 0, 9)
|
||||
new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, -1, 0, 9)
|
||||
.attr(StatChangeAttr, BattleStat.ATK, 2)
|
||||
.attr(StatChangeAttr, BattleStat.DEF, -2),
|
||||
new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, 100, 0, 9)
|
||||
new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 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._10)
|
||||
|
@ -8135,7 +8155,7 @@ export function initMoves() {
|
|||
.triageMove()
|
||||
.attr(RevivalBlessingAttr)
|
||||
.target(MoveTarget.USER),
|
||||
new AttackMove(Moves.SALT_CURE, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 15, -1, 0, 9)
|
||||
new AttackMove(Moves.SALT_CURE, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 15, 100, 0, 9)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.SALT_CURED)
|
||||
.makesContact(false),
|
||||
new AttackMove(Moves.TRIPLE_DIVE, Type.WATER, MoveCategory.PHYSICAL, 30, 95, 10, -1, 0, 9)
|
||||
|
@ -8209,7 +8229,7 @@ export function initMoves() {
|
|||
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
||||
new AttackMove(Moves.TRAILBLAZE, Type.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9)
|
||||
.attr(StatChangeAttr, BattleStat.SPD, 1, true),
|
||||
new AttackMove(Moves.CHILLING_WATER, Type.WATER, MoveCategory.SPECIAL, 50, 100, 20, -1, 0, 9)
|
||||
new AttackMove(Moves.CHILLING_WATER, Type.WATER, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 9)
|
||||
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
||||
new AttackMove(Moves.HYPER_DRILL, Type.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
|
||||
.ignoresProtect(),
|
||||
|
@ -8302,7 +8322,7 @@ export function initMoves() {
|
|||
.slicingMove(),
|
||||
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
|
||||
.attr(OpponentHighHpPowerAttr),
|
||||
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, 100, 0, 9)
|
||||
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, -1, 0, 9)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true)
|
||||
.target(MoveTarget.NEAR_ALLY)
|
||||
.partial(),
|
||||
|
|
|
@ -24,6 +24,9 @@ import * as Overrides from "../overrides";
|
|||
import { ModifierType, modifierTypes } from "./modifier-type";
|
||||
import { Command } from "#app/ui/command-ui-handler.js";
|
||||
|
||||
import { allMoves } from "#app/data/move.js";
|
||||
import { Abilities } from "#app/enums/abilities.js";
|
||||
|
||||
export type ModifierPredicate = (modifier: Modifier) => boolean;
|
||||
|
||||
const iconOverflowIndex = 24;
|
||||
|
@ -520,6 +523,18 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
|
|||
return 1;
|
||||
}
|
||||
|
||||
//Applies to items with chance of activating secondary effects ie Kings Rock
|
||||
getSecondaryChanceMultiplier(pokemon: Pokemon): integer {
|
||||
const sheerForceAffected = allMoves[pokemon.getLastXMoves(0)[0].move].chance >= 0 && pokemon.hasAbility(Abilities.SHEER_FORCE);
|
||||
|
||||
if (sheerForceAffected) {
|
||||
return 0;
|
||||
} else if (pokemon.hasAbility(Abilities.SERENE_GRACE)) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer {
|
||||
const pokemon = this.getPokemon(scene);
|
||||
if (!pokemon) {
|
||||
|
@ -831,7 +846,7 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier {
|
|||
const pokemon = args[0] as Pokemon;
|
||||
const flinched = args[1] as Utils.BooleanHolder;
|
||||
|
||||
if (!flinched.value && pokemon.randSeedInt(10) < this.getStackCount()) {
|
||||
if (!flinched.value && pokemon.randSeedInt(10) < (this.getStackCount() * this.getSecondaryChanceMultiplier(pokemon))) {
|
||||
flinched.value = true;
|
||||
return true;
|
||||
}
|
||||
|
|
106
src/phases.ts
106
src/phases.ts
|
@ -1,7 +1,7 @@
|
|||
import BattleScene, { bypassLogin } from "./battle-scene";
|
||||
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
|
||||
import * as Utils from "./utils";
|
||||
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr } from "./data/move";
|
||||
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr } from "./data/move";
|
||||
import { Mode } from "./ui/ui";
|
||||
import { Command } from "./ui/command-ui-handler";
|
||||
import { Stat } from "./data/pokemon-stat";
|
||||
|
@ -26,7 +26,7 @@ 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 { 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, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs } 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, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr } from "./data/ability";
|
||||
import { Unlockables, getUnlockableName } from "./system/unlockables";
|
||||
import { getBiomeKey } from "./field/arena";
|
||||
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
|
||||
|
@ -646,7 +646,7 @@ export class BattlePhase extends Phase {
|
|||
const tintSprites = this.scene.currentBattle.trainer.getTintSprites();
|
||||
for (let i = 0; i < sprites.length; i++) {
|
||||
const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2;
|
||||
[ sprites[i], tintSprites[i] ].map(sprite => {
|
||||
[sprites[i], tintSprites[i]].map(sprite => {
|
||||
if (visible) {
|
||||
sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16;
|
||||
}
|
||||
|
@ -943,7 +943,7 @@ export class EncounterPhase extends BattlePhase {
|
|||
|
||||
const enemyField = this.scene.getEnemyField();
|
||||
this.scene.tweens.add({
|
||||
targets: [ this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer ].flat(),
|
||||
targets: [this.scene.arenaEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.arenaPlayer, this.scene.trainer].flat(),
|
||||
x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300,
|
||||
duration: 2000,
|
||||
onComplete: () => {
|
||||
|
@ -958,21 +958,21 @@ export class EncounterPhase extends BattlePhase {
|
|||
const enemyField = this.scene.getEnemyField();
|
||||
|
||||
if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) {
|
||||
return i18next.t("battle:bossAppeared", {bossName: enemyField[0].name});
|
||||
return i18next.t("battle:bossAppeared", { bossName: enemyField[0].name });
|
||||
}
|
||||
|
||||
if (this.scene.currentBattle.battleType === BattleType.TRAINER) {
|
||||
if (this.scene.currentBattle.double) {
|
||||
return i18next.t("battle:trainerAppearedDouble", {trainerName: this.scene.currentBattle.trainer.getName(TrainerSlot.NONE, true)});
|
||||
return i18next.t("battle:trainerAppearedDouble", { trainerName: this.scene.currentBattle.trainer.getName(TrainerSlot.NONE, true) });
|
||||
|
||||
} else {
|
||||
return i18next.t("battle:trainerAppeared", {trainerName: this.scene.currentBattle.trainer.getName(TrainerSlot.NONE, true)});
|
||||
return i18next.t("battle:trainerAppeared", { trainerName: this.scene.currentBattle.trainer.getName(TrainerSlot.NONE, true) });
|
||||
}
|
||||
}
|
||||
|
||||
return enemyField.length === 1
|
||||
? i18next.t("battle:singleWildAppeared", {pokemonName: enemyField[0].name})
|
||||
: i18next.t("battle:multiWildAppeared", {pokemonName1: enemyField[0].name, pokemonName2: enemyField[1].name});
|
||||
? i18next.t("battle:singleWildAppeared", { pokemonName: enemyField[0].name })
|
||||
: i18next.t("battle:multiWildAppeared", { pokemonName1: enemyField[0].name, pokemonName2: enemyField[1].name });
|
||||
}
|
||||
|
||||
doEncounterCommon(showEncounterMessage: boolean = true) {
|
||||
|
@ -1002,7 +1002,7 @@ export class EncounterPhase extends BattlePhase {
|
|||
this.scene.currentBattle.started = true;
|
||||
this.scene.playBgm(undefined);
|
||||
this.scene.pbTray.showPbTray(this.scene.getParty());
|
||||
this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty());
|
||||
this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty());
|
||||
const doTrainerSummon = () => {
|
||||
this.hideEnemyTrainer();
|
||||
const availablePartyMembers = this.scene.getEnemyParty().filter(p => !p.isFainted()).length;
|
||||
|
@ -1028,7 +1028,7 @@ export class EncounterPhase extends BattlePhase {
|
|||
this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex);
|
||||
|
||||
const showDialogueAndSummon = () => {
|
||||
this.scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE,true), null, () => {
|
||||
this.scene.ui.showDialogue(message, trainer.getName(TrainerSlot.NONE, true), null, () => {
|
||||
this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon()));
|
||||
});
|
||||
};
|
||||
|
@ -1059,7 +1059,7 @@ export class EncounterPhase extends BattlePhase {
|
|||
// how many player pokemon are on the field ?
|
||||
const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length;
|
||||
// if it's a 2vs1, there will never be a 2nd pokemon on our field even
|
||||
const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2);
|
||||
const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2);
|
||||
// if it's a double, there should be 2, otherwise 1
|
||||
if (this.scene.currentBattle.double) {
|
||||
return pokemonsOnFieldCount === requiredPokemonsOnField;
|
||||
|
@ -1145,7 +1145,7 @@ export class NextEncounterPhase extends EncounterPhase {
|
|||
|
||||
const enemyField = this.scene.getEnemyField();
|
||||
this.scene.tweens.add({
|
||||
targets: [ this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer ].flat(),
|
||||
targets: [this.scene.arenaEnemy, this.scene.arenaNextEnemy, this.scene.currentBattle.trainer, enemyField, this.scene.lastEnemyTrainer].flat(),
|
||||
x: "+=300",
|
||||
duration: 2000,
|
||||
onComplete: () => {
|
||||
|
@ -1188,7 +1188,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
|
|||
|
||||
const enemyField = this.scene.getEnemyField();
|
||||
this.scene.tweens.add({
|
||||
targets: [ this.scene.arenaEnemy, enemyField ].flat(),
|
||||
targets: [this.scene.arenaEnemy, enemyField].flat(),
|
||||
x: "+=300",
|
||||
duration: 2000,
|
||||
onComplete: () => {
|
||||
|
@ -1254,7 +1254,7 @@ export class SelectBiomePhase extends BattlePhase {
|
|||
let biomeChoices: Biome[];
|
||||
this.scene.executeWithSeedOffset(() => {
|
||||
biomeChoices = (!Array.isArray(biomeLinks[currentBiome])
|
||||
? [ biomeLinks[currentBiome] as Biome ]
|
||||
? [biomeLinks[currentBiome] as Biome]
|
||||
: biomeLinks[currentBiome] as (Biome | [Biome, integer])[])
|
||||
.filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
|
||||
.map(b => Array.isArray(b) ? b[0] : b);
|
||||
|
@ -1309,7 +1309,7 @@ export class SwitchBiomePhase extends BattlePhase {
|
|||
}
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: [ this.scene.arenaEnemy, this.scene.lastEnemyTrainer ],
|
||||
targets: [this.scene.arenaEnemy, this.scene.lastEnemyTrainer],
|
||||
x: "+=300",
|
||||
duration: 2000,
|
||||
onComplete: () => {
|
||||
|
@ -1327,7 +1327,7 @@ export class SwitchBiomePhase extends BattlePhase {
|
|||
this.scene.arenaPlayerTransition.setVisible(true);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: [ this.scene.arenaPlayer, this.scene.arenaBgTransition, this.scene.arenaPlayerTransition ],
|
||||
targets: [this.scene.arenaPlayer, this.scene.arenaBgTransition, this.scene.arenaPlayerTransition],
|
||||
duration: 1000,
|
||||
delay: 1000,
|
||||
ease: "Sine.easeInOut",
|
||||
|
@ -1807,7 +1807,7 @@ export class SummonMissingPhase extends SummonPhase {
|
|||
}
|
||||
|
||||
preSummon(): void {
|
||||
this.scene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: this.getPokemon().name}));
|
||||
this.scene.ui.showText(i18next.t("battle:sendOutPokemon", { pokemonName: this.getPokemon().name }));
|
||||
this.scene.time.delayedCall(250, () => this.summon());
|
||||
}
|
||||
}
|
||||
|
@ -1839,7 +1839,7 @@ export class TurnInitPhase extends FieldPhase {
|
|||
this.scene.getPlayerField().forEach(p => {
|
||||
// If this pokemon is in play and evolved into something illegal under the current challenge, force a switch
|
||||
if (p.isOnField() && !p.isAllowedInBattle()) {
|
||||
this.scene.queueMessage(i18next.t("challenges:illegalEvolution", {"pokemon": p.name}), null, true);
|
||||
this.scene.queueMessage(i18next.t("challenges:illegalEvolution", { "pokemon": p.name }), null, true);
|
||||
|
||||
const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle());
|
||||
|
||||
|
@ -1920,7 +1920,7 @@ export class CommandPhase extends FieldPhase {
|
|||
|
||||
while (moveQueue.length && moveQueue[0]
|
||||
&& moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move)
|
||||
|| !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable(playerPokemon, moveQueue[0].ignorePP))) {
|
||||
|| !playerPokemon.getMoveset()[playerPokemon.getMoveset().findIndex(m => m.moveId === moveQueue[0].move)].isUsable(playerPokemon, moveQueue[0].ignorePP))) {
|
||||
moveQueue.shift();
|
||||
}
|
||||
|
||||
|
@ -1950,13 +1950,13 @@ export class CommandPhase extends FieldPhase {
|
|||
case Command.FIGHT:
|
||||
let useStruggle = false;
|
||||
if (cursor === -1 ||
|
||||
playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
|
||||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length)) {
|
||||
playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
|
||||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length)) {
|
||||
const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor].moveId : Moves.NONE : Moves.STRUGGLE;
|
||||
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args };
|
||||
const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2];
|
||||
if (!moveId) {
|
||||
turnCommand.targets = [ this.fieldIndex ];
|
||||
turnCommand.targets = [this.fieldIndex];
|
||||
}
|
||||
console.log(moveTargets, playerPokemon.name);
|
||||
if (moveTargets.targets.length <= 1 || moveTargets.multiple) {
|
||||
|
@ -2177,7 +2177,7 @@ export class EnemyCommandPhase extends FieldPhase {
|
|||
const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores);
|
||||
|
||||
battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
|
||||
{ command: Command.POKEMON, cursor: index, args: [ false ] };
|
||||
{ command: Command.POKEMON, cursor: index, args: [false] };
|
||||
|
||||
battle.enemySwitchCounter++;
|
||||
|
||||
|
@ -2214,7 +2214,7 @@ export class SelectTargetPhase extends PokemonPhase {
|
|||
this.scene.currentBattle.turnCommands[this.fieldIndex] = null;
|
||||
this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex));
|
||||
} else {
|
||||
turnCommand.targets = [ cursor ];
|
||||
turnCommand.targets = [cursor];
|
||||
}
|
||||
if (turnCommand.command === Command.BALL && this.fieldIndex) {
|
||||
this.scene.currentBattle.turnCommands[this.fieldIndex - 1].skip = true;
|
||||
|
@ -2262,10 +2262,10 @@ export class TurnStartPhase extends FieldPhase {
|
|||
const aPriority = new Utils.IntegerHolder(aMove.priority);
|
||||
const bPriority = new Utils.IntegerHolder(bMove.priority);
|
||||
|
||||
applyMoveAttrs(IncrementMovePriorityAttr,this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a),null,aMove,aPriority);
|
||||
applyMoveAttrs(IncrementMovePriorityAttr,this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b),null,bMove,bPriority);
|
||||
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a), null, aMove, aPriority);
|
||||
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b), null, bMove, bPriority);
|
||||
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a), null, aMove, aPriority);
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a), null, aMove, aPriority);
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b), null, bMove, bPriority);
|
||||
|
||||
if (aPriority.value !== bPriority.value) {
|
||||
|
@ -2326,7 +2326,7 @@ export class TurnStartPhase extends FieldPhase {
|
|||
return;
|
||||
}
|
||||
});
|
||||
// if only one pokemon is alive, use that one
|
||||
// if only one pokemon is alive, use that one
|
||||
if (playerActivePokemon.length > 1) {
|
||||
// find which active pokemon has faster speed
|
||||
const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1];
|
||||
|
@ -2619,7 +2619,7 @@ export class MovePhase extends BattlePhase {
|
|||
this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
|
||||
}
|
||||
moveTarget.value = oldTarget;
|
||||
}
|
||||
}
|
||||
this.targets[0] = moveTarget.value;
|
||||
}
|
||||
|
||||
|
@ -2846,7 +2846,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
// of the left Pokemon and gets hit unless this is checked.
|
||||
if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
|
||||
const i = targets.indexOf(battlerIndex);
|
||||
targets.splice(i,i+1);
|
||||
targets.splice(i, i + 1);
|
||||
}
|
||||
this.targets = targets;
|
||||
}
|
||||
|
@ -2886,7 +2886,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
|
||||
user.pushMoveHistory(moveHistoryEntry);
|
||||
|
||||
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
|
||||
const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)]));
|
||||
const activeTargets = targets.map(t => t.isActive(true));
|
||||
if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) {
|
||||
user.turnData.hitCount = 1;
|
||||
|
@ -2935,26 +2935,26 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||
const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), move));
|
||||
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
|
||||
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
|
||||
&& attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move)).then(() => {
|
||||
&& attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move)).then(() => {
|
||||
if (hitResult !== HitResult.NO_EFFECT) {
|
||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
|
||||
&& !attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move).then(() => {
|
||||
if (hitResult < HitResult.NO_EFFECT) {
|
||||
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
|
||||
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => {
|
||||
if (hitResult < HitResult.NO_EFFECT && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) {
|
||||
const flinched = new Utils.BooleanHolder(false);
|
||||
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
|
||||
if (flinched.value) {
|
||||
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
|
||||
}
|
||||
}
|
||||
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
|
||||
user, target, move).then(() => {
|
||||
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, move, hitResult).then(() => {
|
||||
if (!user.isPlayer() && move instanceof AttackMove) {
|
||||
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
|
||||
user, target, this.move.getMove()).then(() => {
|
||||
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
|
||||
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
|
||||
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
|
||||
}
|
||||
})).then(() => {
|
||||
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult).then(() => {
|
||||
if (move instanceof AttackMove) {
|
||||
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
|
||||
if (this.move.getMove() instanceof AttackMove) {
|
||||
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
|
||||
}
|
||||
resolve();
|
||||
|
@ -3151,7 +3151,7 @@ export class MoveAnimTestPhase extends BattlePhase {
|
|||
}
|
||||
|
||||
initMoveAnim(this.scene, moveId).then(() => {
|
||||
loadMoveAnimAssets(this.scene, [ moveId ], true)
|
||||
loadMoveAnimAssets(this.scene, [moveId], true)
|
||||
.then(() => {
|
||||
new MoveAnim(moveId, player ? this.scene.getPlayerPokemon() : this.scene.getEnemyPokemon(), (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).getBattlerIndex()).play(this.scene, () => {
|
||||
if (player) {
|
||||
|
@ -3328,7 +3328,7 @@ export class StatChangePhase extends PokemonPhase {
|
|||
}
|
||||
|
||||
aggregateStatChanges(random: boolean = false): void {
|
||||
const isAccEva = [ BattleStat.ACC, BattleStat.EVA ].some(s => this.stats.includes(s));
|
||||
const isAccEva = [BattleStat.ACC, BattleStat.EVA].some(s => this.stats.includes(s));
|
||||
let existingPhase: StatChangePhase;
|
||||
if (this.stats.length === 1) {
|
||||
while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1
|
||||
|
@ -3348,7 +3348,7 @@ export class StatChangePhase extends PokemonPhase {
|
|||
}
|
||||
}
|
||||
while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget
|
||||
&& ([ BattleStat.ACC, BattleStat.EVA ].some(s => p.stats.includes(s)) === isAccEva)
|
||||
&& ([BattleStat.ACC, BattleStat.EVA].some(s => p.stats.includes(s)) === isAccEva)
|
||||
&& p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) {
|
||||
this.stats.push(...existingPhase.stats);
|
||||
if (!this.scene.tryRemovePhase(p => p === existingPhase)) {
|
||||
|
@ -3512,7 +3512,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
|
|||
break;
|
||||
}
|
||||
if (damage) {
|
||||
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
||||
// Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ...
|
||||
this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage, false, true));
|
||||
pokemon.updateInfo();
|
||||
}
|
||||
|
@ -3962,7 +3962,7 @@ export class TrainerVictoryPhase extends BattlePhase {
|
|||
const trainerType = this.scene.currentBattle.trainer.config.trainerType;
|
||||
if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
|
||||
if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer.config.isBoss) {
|
||||
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [ modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM ][vouchers[TrainerType[trainerType]].voucherType]));
|
||||
this.scene.unshiftPhase(new ModifierRewardPhase(this.scene, [modifierTypes.VOUCHER, modifierTypes.VOUCHER, modifierTypes.VOUCHER_PLUS, modifierTypes.VOUCHER_PREMIUM][vouchers[TrainerType[trainerType]].voucherType]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4571,7 +4571,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
|
|||
if (emptyMoveIndex > -1) {
|
||||
pokemon.setMove(emptyMoveIndex, this.moveId);
|
||||
initMoveAnim(this.scene, this.moveId).then(() => {
|
||||
loadMoveAnimAssets(this.scene, [ this.moveId ], true)
|
||||
loadMoveAnimAssets(this.scene, [this.moveId], true)
|
||||
.then(() => {
|
||||
this.scene.ui.setMode(messageMode).then(() => {
|
||||
this.scene.playSound("level_up_fanfare");
|
||||
|
@ -4944,7 +4944,7 @@ export class AttemptCapturePhase extends PokemonPhase {
|
|||
}
|
||||
});
|
||||
};
|
||||
Promise.all([ pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon) ]).then(() => {
|
||||
Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => {
|
||||
if (this.scene.getParty().length === 6) {
|
||||
const promptRelease = () => {
|
||||
this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => {
|
||||
|
@ -5008,7 +5008,7 @@ export class AttemptRunPhase extends PokemonPhase {
|
|||
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: [ this.scene.arenaEnemy, enemyField ].flat(),
|
||||
targets: [this.scene.arenaEnemy, enemyField].flat(),
|
||||
alpha: 0,
|
||||
duration: 250,
|
||||
ease: "Sine.easeIn",
|
||||
|
@ -5097,7 +5097,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => {
|
||||
if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) {
|
||||
const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
||||
&& (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
|
||||
&& (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
|
||||
const itemModifier = itemModifiers[itemIndex];
|
||||
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
|
||||
} else {
|
||||
|
@ -5223,7 +5223,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||
getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): integer {
|
||||
let baseValue = 0;
|
||||
if (lockRarities) {
|
||||
const tierValues = [ 50, 125, 300, 750, 2000 ];
|
||||
const tierValues = [50, 125, 300, 750, 2000];
|
||||
for (const opt of typeOptions) {
|
||||
baseValue += tierValues[opt.type.tier];
|
||||
}
|
||||
|
@ -5443,7 +5443,7 @@ export class TrainerMessageTestPhase extends BattlePhase {
|
|||
continue;
|
||||
}
|
||||
const config = trainerConfigs[type];
|
||||
[ config.encounterMessages, config.femaleEncounterMessages, config.victoryMessages, config.femaleVictoryMessages, config.defeatMessages, config.femaleDefeatMessages ]
|
||||
[config.encounterMessages, config.femaleEncounterMessages, config.victoryMessages, config.femaleVictoryMessages, config.defeatMessages, config.femaleDefeatMessages]
|
||||
.map(messages => {
|
||||
if (messages?.length) {
|
||||
testMessages.push(...messages);
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import * as overrides from "#app/overrides";
|
||||
import {Abilities} from "#enums/abilities";
|
||||
import {applyAbAttrs ,MoveEffectChanceMultiplierAbAttr} from "#app/data/ability";
|
||||
import {Species} from "#enums/species";
|
||||
import {
|
||||
CommandPhase,
|
||||
MoveEffectPhase,
|
||||
} from "#app/phases";
|
||||
import {Mode} from "#app/ui/ui";
|
||||
import {Stat} from "#app/data/pokemon-stat";
|
||||
import {Moves} from "#enums/moves";
|
||||
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
|
||||
import {Command} from "#app/ui/command-ui-handler";
|
||||
import * as Utils from "#app/utils";
|
||||
|
||||
|
||||
describe("Abilities - Serene Grace", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const movesToUse = [Moves.AIR_SLASH, Moves.TACKLE];
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ONIX);
|
||||
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(movesToUse);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
|
||||
});
|
||||
|
||||
it("Move chance without Serene Grace", async() => {
|
||||
const moveToUse = Moves.AIR_SLASH;
|
||||
await game.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPD] = 1;
|
||||
expect(game.scene.getParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
|
||||
});
|
||||
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||
const movePosition = getMovePosition(game.scene, 0, moveToUse);
|
||||
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
// Check chance of Air Slash without Serene Grace
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
console.log(move.chance + " Their ability is " + phase.getUserPokemon().getAbility().name);
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false);
|
||||
expect(chance.value).toBe(30);
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Move chance with Serene Grace", async() => {
|
||||
const moveToUse = Moves.AIR_SLASH;
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SERENE_GRACE);
|
||||
await game.startBattle([
|
||||
Species.TOGEKISS
|
||||
]);
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPD] = 1;
|
||||
expect(game.scene.getParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
|
||||
});
|
||||
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||
const movePosition = getMovePosition(game.scene, 0, moveToUse);
|
||||
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
// Check chance of Air Slash with Serene Grace
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false);
|
||||
expect(chance.value).toBe(60);
|
||||
|
||||
}, 20000);
|
||||
|
||||
//TODO King's Rock Interaction Unit Test
|
||||
});
|
|
@ -0,0 +1,208 @@
|
|||
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import * as overrides from "#app/overrides";
|
||||
import {Abilities} from "#enums/abilities";
|
||||
import {applyAbAttrs ,applyPreAttackAbAttrs,applyPostDefendAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr} from "#app/data/ability";
|
||||
import {Species} from "#enums/species";
|
||||
import {
|
||||
CommandPhase,
|
||||
MoveEffectPhase,
|
||||
} from "#app/phases";
|
||||
import {Mode} from "#app/ui/ui";
|
||||
import {Stat} from "#app/data/pokemon-stat";
|
||||
import {Moves} from "#enums/moves";
|
||||
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
|
||||
import {Command} from "#app/ui/command-ui-handler";
|
||||
import * as Utils from "#app/utils";
|
||||
|
||||
|
||||
describe("Abilities - Sheer Force", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const movesToUse = [Moves.AIR_SLASH, Moves.BIND, Moves.CRUSH_CLAW, Moves.TACKLE];
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ONIX);
|
||||
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(movesToUse);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
|
||||
});
|
||||
|
||||
it("Sheer Force", async() => {
|
||||
const moveToUse = Moves.AIR_SLASH;
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SHEER_FORCE);
|
||||
await game.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPD] = 1;
|
||||
expect(game.scene.getParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
|
||||
});
|
||||
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||
const movePosition = getMovePosition(game.scene, 0, moveToUse);
|
||||
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
|
||||
//Verify the move is boosted and has no chance of secondary effects
|
||||
const power = new Utils.IntegerHolder(move.power);
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon(), phase.getTarget(), move, power);
|
||||
|
||||
expect(chance.value).toBe(0);
|
||||
expect(power.value).toBe(move.power * 5461/4096);
|
||||
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Sheer Force with exceptions including binding moves", async() => {
|
||||
const moveToUse = Moves.BIND;
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SHEER_FORCE);
|
||||
await game.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.DEF] = 10000;
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPD] = 1;
|
||||
expect(game.scene.getParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
|
||||
});
|
||||
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||
const movePosition = getMovePosition(game.scene, 0, moveToUse);
|
||||
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.BIND);
|
||||
|
||||
//Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1
|
||||
const power = new Utils.IntegerHolder(move.power);
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon(), phase.getTarget(), move, power);
|
||||
|
||||
expect(chance.value).toBe(-1);
|
||||
expect(power.value).toBe(move.power);
|
||||
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Sheer Force with moves with no secondary effect", async() => {
|
||||
const moveToUse = Moves.TACKLE;
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SHEER_FORCE);
|
||||
await game.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.DEF] = 10000;
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPD] = 1;
|
||||
expect(game.scene.getParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
|
||||
});
|
||||
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||
const movePosition = getMovePosition(game.scene, 0, moveToUse);
|
||||
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.TACKLE);
|
||||
|
||||
//Binding moves and other exceptions are not affected by Sheer Force and have a chance.value of -1
|
||||
const power = new Utils.IntegerHolder(move.power);
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, phase.getUserPokemon(), phase.getTarget(), move, power);
|
||||
|
||||
expect(chance.value).toBe(-1);
|
||||
expect(power.value).toBe(move.power);
|
||||
|
||||
|
||||
}, 20000);
|
||||
|
||||
it("Sheer Force Disabling Specific Abilities", async() => {
|
||||
const moveToUse = Moves.CRUSH_CLAW;
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.COLOR_CHANGE);
|
||||
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "KINGS_ROCK", count: 1}]);
|
||||
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SHEER_FORCE);
|
||||
await game.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.DEF] = 10000;
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPD] = 1;
|
||||
expect(game.scene.getParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
|
||||
});
|
||||
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||
const movePosition = getMovePosition(game.scene, 0, moveToUse);
|
||||
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.CRUSH_CLAW);
|
||||
|
||||
//Disable color change due to being hit by Sheer Force
|
||||
const power = new Utils.IntegerHolder(move.power);
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
const user = phase.getUserPokemon();
|
||||
const target = phase.getTarget();
|
||||
const opponentType = target.getTypes()[0];
|
||||
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, chance, move, target, false);
|
||||
applyPreAttackAbAttrs(MovePowerBoostAbAttr, user, target, move, power);
|
||||
applyPostDefendAbAttrs(PostDefendTypeChangeAbAttr, target, user, move, target.apply(user, move));
|
||||
|
||||
expect(chance.value).toBe(0);
|
||||
expect(power.value).toBe(move.power * 5461/4096);
|
||||
expect(target.getTypes().length).toBe(2);
|
||||
expect(target.getTypes()[0]).toBe(opponentType);
|
||||
|
||||
}, 20000);
|
||||
|
||||
//TODO King's Rock Interaction Unit Test
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
|
||||
import Phaser from "phaser";
|
||||
import GameManager from "#app/test/utils/gameManager";
|
||||
import * as overrides from "#app/overrides";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import {applyAbAttrs ,applyPreDefendAbAttrs,IgnoreMoveEffectsAbAttr,MoveEffectChanceMultiplierAbAttr} from "#app/data/ability";
|
||||
import {Species} from "#enums/species";
|
||||
import {
|
||||
CommandPhase,
|
||||
MoveEffectPhase,
|
||||
} from "#app/phases";
|
||||
import {Mode} from "#app/ui/ui";
|
||||
import {Stat} from "#app/data/pokemon-stat";
|
||||
import {Moves} from "#enums/moves";
|
||||
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
|
||||
import {Command} from "#app/ui/command-ui-handler";
|
||||
import * as Utils from "#app/utils";
|
||||
|
||||
|
||||
describe("Abilities - Shield Dust", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
const movesToUse = [Moves.AIR_SLASH];
|
||||
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
|
||||
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.ONIX);
|
||||
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SHIELD_DUST);
|
||||
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
|
||||
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue(movesToUse);
|
||||
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
|
||||
});
|
||||
|
||||
it("Shield Dust", async() => {
|
||||
const moveToUse = Moves.AIR_SLASH;
|
||||
await game.startBattle([
|
||||
Species.PIDGEOT
|
||||
]);
|
||||
|
||||
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPDEF] = 10000;
|
||||
game.scene.getEnemyParty()[0].stats[Stat.SPD] = 1;
|
||||
expect(game.scene.getParty()[0].formIndex).toBe(0);
|
||||
|
||||
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
|
||||
});
|
||||
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
|
||||
const movePosition = getMovePosition(game.scene, 0, moveToUse);
|
||||
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
|
||||
});
|
||||
|
||||
await game.phaseInterceptor.to(MoveEffectPhase, false);
|
||||
|
||||
// Shield Dust negates secondary effect
|
||||
const phase = game.scene.getCurrentPhase() as MoveEffectPhase;
|
||||
const move = phase.move.getMove();
|
||||
expect(move.id).toBe(Moves.AIR_SLASH);
|
||||
|
||||
const chance = new Utils.IntegerHolder(move.chance);
|
||||
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, phase.getUserPokemon(), null, chance, move, phase.getTarget(), false);
|
||||
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, phase.getTarget(),phase.getUserPokemon(),null,null, chance);
|
||||
expect(chance.value).toBe(0);
|
||||
|
||||
}, 20000);
|
||||
|
||||
//TODO King's Rock Interaction Unit Test
|
||||
});
|
Loading…
Reference in New Issue