[Ability] Serene Grace, Shield Dust, Sheer Force (P) (#246)

Co-authored-by: okimin <danielgaston6@gmail.com>
This commit is contained in:
NxKarim 2024-06-17 18:30:42 -06:00 committed by GitHub
parent 3b3b9e39af
commit c52e0ac5b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 657 additions and 137 deletions

112
src/data/ability.ts Executable file → Normal file
View File

@ -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 { export class VariableMovePowerAbAttr extends PreAttackAbAttr {
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean { applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
//const power = args[0] as Utils.NumberHolder; //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 { 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)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.trySetStatus(effect, true, pokemon); 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 { 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)]; const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return attacker.addTag(effect); 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 { function getWeatherCondition(...weatherTypes: WeatherType[]): AbAttrCondition {
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
if (pokemon.scene.arena.weather?.isEffectSuppressed(pokemon.scene)) { if (pokemon.scene.arena.weather?.isEffectSuppressed(pokemon.scene)) {
@ -3932,7 +4011,8 @@ export function initAbilities() {
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY) .attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
.ignorable(), .ignorable(),
new Ability(Abilities.COLOR_CHANGE, 3) new Ability(Abilities.COLOR_CHANGE, 3)
.attr(PostDefendTypeChangeAbAttr), .attr(PostDefendTypeChangeAbAttr)
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.IMMUNITY, 3) new Ability(Abilities.IMMUNITY, 3)
.attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(StatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.ignorable(), .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) .attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1, (pokemon: Pokemon) => !pokemon.status || pokemon.status.effect !== StatusEffect.FREEZE)
.ignorable(), .ignorable(),
new Ability(Abilities.SHIELD_DUST, 3) new Ability(Abilities.SHIELD_DUST, 3)
.ignorable() .attr(IgnoreMoveEffectsAbAttr)
.unimplemented(), .partial(),
new Ability(Abilities.OWN_TEMPO, 3) new Ability(Abilities.OWN_TEMPO, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
.attr(IntimidateImmunityAbAttr) .attr(IntimidateImmunityAbAttr)
@ -3984,7 +4064,8 @@ export function initAbilities() {
.attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1) .attr(TypeImmunityStatChangeAbAttr, Type.ELECTRIC, BattleStat.SPATK, 1)
.ignorable(), .ignorable(),
new Ability(Abilities.SERENE_GRACE, 3) new Ability(Abilities.SERENE_GRACE, 3)
.unimplemented(), .attr(MoveEffectChanceMultiplierAbAttr, 2)
.partial(),
new Ability(Abilities.SWIFT_SWIM, 3) new Ability(Abilities.SWIFT_SWIM, 3)
.attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2) .attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)), .condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
@ -4260,9 +4341,12 @@ export function initAbilities() {
new Ability(Abilities.BAD_DREAMS, 4) new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr), .attr(PostTurnHurtIfSleepingAbAttr),
new Ability(Abilities.PICKPOCKET, 5) 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) 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) new Ability(Abilities.CONTRARY, 5)
.attr(StatChangeMultiplierAbAttr, -1) .attr(StatChangeMultiplierAbAttr, -1)
.ignorable(), .ignorable(),
@ -4471,8 +4555,10 @@ export function initAbilities() {
new Ability(Abilities.STAMINA, 7) new Ability(Abilities.STAMINA, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattleStat.DEF, 1), .attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, BattleStat.DEF, 1),
new Ability(Abilities.WIMP_OUT, 7) new Ability(Abilities.WIMP_OUT, 7)
.condition(getSheerForceHitDisableAbCondition())
.unimplemented(), .unimplemented(),
new Ability(Abilities.EMERGENCY_EXIT, 7) new Ability(Abilities.EMERGENCY_EXIT, 7)
.condition(getSheerForceHitDisableAbCondition())
.unimplemented(), .unimplemented(),
new Ability(Abilities.WATER_COMPACTION, 7) new Ability(Abilities.WATER_COMPACTION, 7)
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.type === Type.WATER && move.category !== MoveCategory.STATUS, BattleStat.DEF, 2), .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) new Ability(Abilities.STEELWORKER, 7)
.attr(MoveTypePowerBoostAbAttr, Type.STEEL), .attr(MoveTypePowerBoostAbAttr, Type.STEEL),
new Ability(Abilities.BERSERK, 7) 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) new Ability(Abilities.SLUSH_RUSH, 7)
.attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2) .attr(BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
.condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW)), .condition(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW)),
@ -4757,7 +4844,8 @@ export function initAbilities() {
.ignorable(), .ignorable(),
new Ability(Abilities.ANGER_SHELL, 9) 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.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) new Ability(Abilities.PURIFYING_SALT, 9)
.attr(StatusEffectImmunityAbAttr) .attr(StatusEffectImmunityAbAttr)
.attr(ReceivedTypeDamageMultiplierAbAttr, Type.GHOST, 0.5) .attr(ReceivedTypeDamageMultiplierAbAttr, Type.GHOST, 0.5)

View File

@ -9,7 +9,7 @@ import { Type } from "./type";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag"; 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 { allAbilities } from "./ability";
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier"; import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier";
import { BattlerIndex } from "../battle"; 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> { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
return this.canApply(user, target, move, args); 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 { export class PreMoveMessageAttr extends MoveAttr {
@ -1673,7 +1689,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { 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) { if (statusCheck) {
const pokemon = this.selfTarget ? user : target; const pokemon = this.selfTarget ? user : target;
if (pokemon.status) { if (pokemon.status) {
@ -1683,7 +1700,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
return false; 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)) { && pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect); applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect);
return true; return true;
@ -1693,7 +1710,8 @@ export class StatusEffectAttr extends MoveEffectAttr {
} }
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { 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 { 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; 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); const levels = this.getLevels(user);
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage)); user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage));
return true; return true;
@ -3892,18 +3912,14 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
return false; return false;
} }
const chance = this.getTagChance(user, target, move); const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
if (chance < 0 || chance === 100 || user.randSeedInt(100) < chance) { 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 (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id);
} }
return false; return false;
} }
getTagChance(user: Pokemon, target: Pokemon, move: Move): integer {
return move.chance;
}
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return this.failOnOverlap return this.failOnOverlap
? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType) ? (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 { getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
let chance = this.getTagChance(user, target, move); let moveChance = this.getMoveChance(user,target,move,this.selfTarget);
if (chance < 0) { if (moveChance < 0) {
chance = 100; 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 { export class AddArenaTrapTagAttr extends AddArenaTagAttr {
getCondition(): MoveConditionFunc { getCondition(): MoveConditionFunc {
return (user, target, move) => { return (user, target, move) => {
const moveChance = this.getMoveChance(user,target,move,this.selfTarget);
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (move.category !== MoveCategory.STATUS || !user.scene.arena.getTagOnSide(this.tagType, side)) { if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
return true; 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; const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
return tag.layers < tag.maxLayers; return tag.layers < tag.maxLayers;
}; };
@ -5617,7 +5637,7 @@ export function initMoves() {
.attr(ChargeAttr, ChargeAnim.FLY_CHARGING, "flew\nup high!", BattlerTagType.FLYING) .attr(ChargeAttr, ChargeAnim.FLY_CHARGING, "flew\nup high!", BattlerTagType.FLYING)
.condition(failOnGravityCondition) .condition(failOnGravityCondition)
.ignoresVirtual(), .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), .attr(TrapAttr, BattlerTagType.BIND),
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1), 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), 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(MinimizeAccuracyAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS), .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), .attr(TrapAttr, BattlerTagType.WRAP),
new AttackMove(Moves.TAKE_DOWN, Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, 0, 1) new AttackMove(Moves.TAKE_DOWN, Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, 0, 1)
.attr(RecoilAttr) .attr(RecoilAttr)
@ -5675,7 +5695,7 @@ export function initMoves() {
new AttackMove(Moves.PIN_MISSILE, Type.BUG, MoveCategory.PHYSICAL, 25, 95, 20, -1, 0, 1) new AttackMove(Moves.PIN_MISSILE, Type.BUG, MoveCategory.PHYSICAL, 25, 95, 20, -1, 0, 1)
.attr(MultiHitAttr) .attr(MultiHitAttr)
.makesContact(false), .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) .attr(StatChangeAttr, BattleStat.DEF, -1)
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BITE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 1) 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), .target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.DRAGON_RAGE, Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1) new AttackMove(Moves.DRAGON_RAGE, Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1)
.attr(FixedDamageAttr, 40), .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), .attr(TrapAttr, BattlerTagType.FIRE_SPIN),
new AttackMove(Moves.THUNDER_SHOCK, Type.ELECTRIC, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1) new AttackMove(Moves.THUNDER_SHOCK, Type.ELECTRIC, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS), .attr(StatusEffectAttr, StatusEffect.PARALYSIS),
@ -5900,11 +5920,11 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.BURN), .attr(StatusEffectAttr, StatusEffect.BURN),
new AttackMove(Moves.WATERFALL, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 1) new AttackMove(Moves.WATERFALL, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 1)
.attr(FlinchAttr), .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), .attr(TrapAttr, BattlerTagType.CLAMP),
new AttackMove(Moves.SWIFT, Type.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1) new AttackMove(Moves.SWIFT, Type.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1)
.target(MoveTarget.ALL_NEAR_ENEMIES), .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(ChargeAttr, ChargeAnim.SKULL_BASH_CHARGING, "lowered\nits head!", null, true)
.attr(StatChangeAttr, BattleStat.DEF, 1, true) .attr(StatChangeAttr, BattleStat.DEF, 1, true)
.ignoresVirtual(), .ignoresVirtual(),
@ -6253,7 +6273,7 @@ export function initMoves() {
.attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, "foresaw\nan attack!"), .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) new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2)
.attr(StatChangeAttr, BattleStat.DEF, -1), .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(TrapAttr, BattlerTagType.WHIRLPOOL)
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true), .attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2) new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
@ -6329,7 +6349,7 @@ export function initMoves() {
.ignoresVirtual(), .ignoresVirtual(),
new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3) new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true), .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), .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1, true),
new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3) new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3)
.unimplemented(), .unimplemented(),
@ -6431,7 +6451,7 @@ export function initMoves() {
.slicingMove() .slicingMove()
.windMove() .windMove()
.target(MoveTarget.ALL_NEAR_ENEMIES), .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(StatChangeAttr, BattleStat.SPATK, -2, true)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE), .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE),
new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3) 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) new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3)
.attr(HitsTagAttr, BattlerTagType.FLYING) .attr(HitsTagAttr, BattlerTagType.FLYING)
.punchingMove(), .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) .attr(TrapAttr, BattlerTagType.SAND_TOMB)
.makesContact(false), .makesContact(false),
new AttackMove(Moves.SHEER_COLD, Type.ICE, MoveCategory.SPECIAL, 200, 20, 5, -1, 0, 3) new AttackMove(Moves.SHEER_COLD, Type.ICE, MoveCategory.SPECIAL, 200, 20, 5, -1, 0, 3)
@ -6534,7 +6554,7 @@ export function initMoves() {
.pulseMove(), .pulseMove(),
new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) 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!"), .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), .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new SelfStatusMove(Moves.ROOST, Type.FLYING, -1, 5, -1, 0, 4) new SelfStatusMove(Moves.ROOST, Type.FLYING, -1, 5, -1, 0, 4)
.attr(HealAttr, 0.5) .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) 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(MovePowerMultiplierAttr, (user, target, move) => targetSleptOrComatoseCondition(user, target, move) ? 2 : 1)
.attr(HealStatusEffectAttr, false, StatusEffect.SLEEP), .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) .attr(StatChangeAttr, BattleStat.SPD, -1, true)
.punchingMove(), .punchingMove(),
new AttackMove(Moves.GYRO_BALL, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4) 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), .target(MoveTarget.ATTACKER),
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4) new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
.attr(ForceSwitchOutAttr, true, false), .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), .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4) 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), .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) new SelfStatusMove(Moves.COPYCAT, Type.NORMAL, -1, 20, -1, 0, 4)
.attr(CopyMoveAttr) .attr(CopyMoveAttr)
.ignoresVirtual(), .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(), .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(), .unimplemented(),
new AttackMove(Moves.PUNISHMENT, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4) new AttackMove(Moves.PUNISHMENT, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
.makesContact(true) .makesContact(true)
@ -6756,7 +6776,7 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5) .attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5)
.ignoresProtect() .ignoresProtect()
.target(MoveTarget.BOTH_SIDES), .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), .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.DISCHARGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4) new AttackMove(Moves.DISCHARGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS) .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) new AttackMove(Moves.LAVA_PLUME, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
.attr(StatusEffectAttr, StatusEffect.BURN) .attr(StatusEffectAttr, StatusEffect.BURN)
.target(MoveTarget.ALL_NEAR_OTHERS), .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), .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.POWER_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 4), 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) new AttackMove(Moves.ROCK_WRECKER, Type.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4)
@ -6834,7 +6854,7 @@ export function initMoves() {
.unimplemented(), .unimplemented(),
new AttackMove(Moves.CRUSH_GRIP, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4) new AttackMove(Moves.CRUSH_GRIP, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
.attr(OpponentHighHpPowerAttr), .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), .attr(TrapAttr, BattlerTagType.MAGMA_STORM),
new StatusMove(Moves.DARK_VOID, Type.DARK, 50, 10, -1, 0, 4) new StatusMove(Moves.DARK_VOID, Type.DARK, 50, 10, -1, 0, 4)
.attr(StatusEffectAttr, StatusEffect.SLEEP) .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) new AttackMove(Moves.ICICLE_CRASH, Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, 30, 0, 5)
.attr(FlinchAttr) .attr(FlinchAttr)
.makesContact(false), .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), .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) new AttackMove(Moves.FUSION_FLARE, Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 5)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
@ -7113,7 +7133,7 @@ export function initMoves() {
.condition(new FirstMoveCondition()), .condition(new FirstMoveCondition()),
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6) new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
.condition((user, target, move) => user.battleData.berriesEaten.length > 0), .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) .target(MoveTarget.ALL)
.condition((user,target,move) => { .condition((user,target,move) => {
// If any fielded pokémon is grass-type and grounded. // 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) new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
.attr(AddTypeAttr, Type.GHOST) .attr(AddTypeAttr, Type.GHOST)
.partial(), .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) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1)
.soundBased(), .soundBased(),
new StatusMove(Moves.ION_DELUGE, Type.ELECTRIC, -1, 25, -1, 1, 6) 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) new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
.soundBased() .soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES), .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(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
.attr(ForceSwitchOutAttr, true, false) .attr(ForceSwitchOutAttr, true, false)
.soundBased(), .soundBased(),
@ -7168,7 +7188,7 @@ export function initMoves() {
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6) new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
.target(MoveTarget.USER_SIDE) .target(MoveTarget.USER_SIDE)
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true), .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) .target(MoveTarget.ALL)
.unimplemented(), .unimplemented(),
new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6) new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6)
@ -7193,9 +7213,9 @@ export function initMoves() {
.unimplemented(), .unimplemented(),
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6) new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD), .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), .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) .attr(StatChangeAttr, BattleStat.SPATK, -1)
.soundBased(), .soundBased(),
new AttackMove(Moves.DIAMOND_STORM, Type.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6) 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), .target(MoveTarget.NEAR_ALLY),
new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6) new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6)
.attr(StatChangeAttr, BattleStat.SPATK, -2), .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) .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), .target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6) new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
@ -7252,7 +7272,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.PARALYSIS), .attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new AttackMove(Moves.HOLD_BACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6) new AttackMove(Moves.HOLD_BACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6)
.attr(SurviveDamageAttr), .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() .makesContact()
.attr(TrapAttr, BattlerTagType.INFESTATION), .attr(TrapAttr, BattlerTagType.INFESTATION),
new AttackMove(Moves.POWER_UP_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 20, 100, 0, 6) 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) new AttackMove(Moves.OBLIVION_WING, Type.FLYING, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
.attr(HitHealAttr, 0.75) .attr(HitHealAttr, 0.75)
.triageMove(), .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(NeutralDamageAgainstFlyingTypeMultiplierAttr)
.attr(HitsTagAttr, BattlerTagType.FLYING, false) .attr(HitsTagAttr, BattlerTagType.FLYING, false)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) .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) new AttackMove(Moves.PRECIPICE_BLADES, Type.GROUND, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 6)
.makesContact(false) .makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES), .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), .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) .attr(StatChangeAttr, BattleStat.DEF, -1, true)
.makesContact(false) .makesContact(false)
.ignoresProtect(), .ignoresProtect(),
@ -7410,23 +7430,23 @@ export function initMoves() {
.condition(new FirstMoveCondition()), .condition(new FirstMoveCondition()),
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7) new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER), .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) .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1)
.makesContact(false), .makesContact(false),
new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7) new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
.attr(IgnoreOpponentStatChangesAttr), .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) .attr(HealStatusEffectAttr, false, StatusEffect.BURN)
.soundBased() .soundBased()
.target(MoveTarget.ALL_NEAR_OTHERS), .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) .attr(StatChangeAttr, BattleStat.SPD, -1, true)
.punchingMove(), .punchingMove(),
new StatusMove(Moves.FLORAL_HEALING, Type.FAIRY, -1, 10, -1, 0, 7) 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) .attr(BoostHealAttr, 0.5, 2/3, true, false, (user, target, move) => user.scene.arena.terrain?.terrainType === TerrainType.GRASSY)
.triageMove(), .triageMove(),
new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7), 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(HitHealAttr, null, Stat.ATK)
.attr(StatChangeAttr, BattleStat.ATK, -1) .attr(StatChangeAttr, BattleStat.ATK, -1)
.condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6) .condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6)
@ -7439,7 +7459,7 @@ export function initMoves() {
.makesContact(false), .makesContact(false),
new StatusMove(Moves.SPOTLIGHT, Type.NORMAL, -1, 15, -1, 3, 7) new StatusMove(Moves.SPOTLIGHT, Type.NORMAL, -1, 15, -1, 3, 7)
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, false), .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(StatusEffectAttr, StatusEffect.POISON)
.attr(StatChangeAttr, BattleStat.SPD, -1), .attr(StatChangeAttr, BattleStat.SPD, -1),
new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7) new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7)
@ -7454,7 +7474,7 @@ export function initMoves() {
.attr(StatusCategoryOnAllyAttr) .attr(StatusCategoryOnAllyAttr)
.attr(HealOnAllyAttr, 0.5, true, false) .attr(HealOnAllyAttr, 0.5, true, false)
.ballBombMove(), .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), .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1),
new StatusMove(Moves.PSYCHIC_TERRAIN, Type.PSYCHIC, -1, 10, -1, 0, 7) new StatusMove(Moves.PSYCHIC_TERRAIN, Type.PSYCHIC, -1, 10, -1, 0, 7)
.attr(TerrainChangeAttr, TerrainType.PSYCHIC) .attr(TerrainChangeAttr, TerrainType.PSYCHIC)
@ -7498,7 +7518,7 @@ export function initMoves() {
.ballBombMove() .ballBombMove()
.makesContact(false) .makesContact(false)
.partial(), .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) .attr(StatChangeAttr, BattleStat.DEF, -1, true)
.soundBased() .soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES), .target(MoveTarget.ALL_NEAR_ENEMIES),
@ -7535,14 +7555,14 @@ export function initMoves() {
new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, 100, 0, 7) 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) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true)
.ignoresVirtual(), .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) .attr(TerrainChangeAttr, TerrainType.PSYCHIC)
.ignoresVirtual(), .ignoresVirtual(),
/* End Unused */ /* End Unused */
new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7) new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7)
.target(MoveTarget.ALL_NEAR_ENEMIES) .target(MoveTarget.ALL_NEAR_ENEMIES)
.partial(), .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), .attr(StatChangeAttr, BattleStat.SPATK, -2, true),
new AttackMove(Moves.PSYCHIC_FANGS, Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7) new AttackMove(Moves.PSYCHIC_FANGS, Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
.bitingMove() .bitingMove()
@ -7565,7 +7585,7 @@ export function initMoves() {
new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
.ignoresAbilities() .ignoresAbilities()
.partial(), .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), .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1),
new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7) new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7)
.attr(FlinchAttr), .attr(FlinchAttr),
@ -7675,7 +7695,7 @@ export function initMoves() {
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, 100, 0, 8) 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(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, true, 1), .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) .attr(StatChangeAttr, BattleStat.SPD, -1)
.partial(), .partial(),
new StatusMove(Moves.MAGIC_POWDER, Type.PSYCHIC, 100, 20, -1, 0, 8) new StatusMove(Moves.MAGIC_POWDER, Type.PSYCHIC, 100, 20, -1, 0, 8)
@ -7777,12 +7797,12 @@ export function initMoves() {
.danceMove(), .danceMove(),
new AttackMove(Moves.BODY_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8) new AttackMove(Moves.BODY_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8)
.attr(DefAtkAttr), .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), .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 2),
new AttackMove(Moves.DRUM_BEATING, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8) new AttackMove(Moves.DRUM_BEATING, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8)
.attr(StatChangeAttr, BattleStat.SPD, -1) .attr(StatChangeAttr, BattleStat.SPD, -1)
.makesContact(false), .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), .attr(TrapAttr, BattlerTagType.SNAP_TRAP),
new AttackMove(Moves.PYRO_BALL, Type.FIRE, MoveCategory.PHYSICAL, 120, 90, 5, 10, 0, 8) new AttackMove(Moves.PYRO_BALL, Type.FIRE, MoveCategory.PHYSICAL, 120, 90, 5, 10, 0, 8)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) .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) new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8)
.attr(ClearTerrainAttr) .attr(ClearTerrainAttr)
.condition((user, target, move) => !!user.scene.arena.terrain), .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.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit
//.attr(StatChangeAttr, BattleStat.DEF, -1, true) //.attr(StatChangeAttr, BattleStat.DEF, -1, true)
.attr(MultiHitAttr) .attr(MultiHitAttr)
@ -7875,7 +7895,7 @@ export function initMoves() {
new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8) new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8)
.target(MoveTarget.ALL_NEAR_OTHERS) .target(MoveTarget.ALL_NEAR_OTHERS)
.unimplemented(), .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) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], 1)
.target(MoveTarget.NEAR_ALLY), .target(MoveTarget.NEAR_ALLY),
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8) 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(MultiHitAttr, MultiHitType._3)
.attr(CritOnlyAttr) .attr(CritOnlyAttr)
.punchingMove(), .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), .attr(TrapAttr, BattlerTagType.THUNDER_CAGE),
new AttackMove(Moves.DRAGON_ENERGY, Type.DRAGON, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 8) new AttackMove(Moves.DRAGON_ENERGY, Type.DRAGON, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 8)
.attr(HpPowerAttr) .attr(HpPowerAttr)
@ -7950,7 +7970,7 @@ export function initMoves() {
new SelfStatusMove(Moves.VICTORY_DANCE, Type.FIGHTING, -1, 10, 100, 0, 8) new SelfStatusMove(Moves.VICTORY_DANCE, Type.FIGHTING, -1, 10, 100, 0, 8)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPD ], 1, true) .attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPD ], 1, true)
.danceMove(), .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) .attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true)
.punchingMove(), .punchingMove(),
new AttackMove(Moves.BARB_BARRAGE, Type.POISON, MoveCategory.PHYSICAL, 60, 100, 10, 50, 0, 8) new AttackMove(Moves.BARB_BARRAGE, Type.POISON, MoveCategory.PHYSICAL, 60, 100, 10, 50, 0, 8)
@ -8113,15 +8133,15 @@ export function initMoves() {
.makesContact(false), .makesContact(false),
new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9) new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
.attr(StatChangeAttr, BattleStat.SPDEF, -2), .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) .makesContact(false)
.partial(), .partial(),
new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9) new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
.punchingMove(), .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.ATK, 2)
.attr(StatChangeAttr, BattleStat.DEF, -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), .attr(StatChangeAttr, BattleStat.SPD, -2, true),
new AttackMove(Moves.POPULATION_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9) new AttackMove(Moves.POPULATION_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9)
.attr(MultiHitAttr, MultiHitType._10) .attr(MultiHitAttr, MultiHitType._10)
@ -8135,7 +8155,7 @@ export function initMoves() {
.triageMove() .triageMove()
.attr(RevivalBlessingAttr) .attr(RevivalBlessingAttr)
.target(MoveTarget.USER), .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) .attr(AddBattlerTagAttr, BattlerTagType.SALT_CURED)
.makesContact(false), .makesContact(false),
new AttackMove(Moves.TRIPLE_DIVE, Type.WATER, MoveCategory.PHYSICAL, 30, 95, 10, -1, 0, 9) 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), .attr(StatChangeAttr, BattleStat.SPD, -1),
new AttackMove(Moves.TRAILBLAZE, Type.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9) new AttackMove(Moves.TRAILBLAZE, Type.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9)
.attr(StatChangeAttr, BattleStat.SPD, 1, true), .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), .attr(StatChangeAttr, BattleStat.ATK, -1),
new AttackMove(Moves.HYPER_DRILL, Type.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) new AttackMove(Moves.HYPER_DRILL, Type.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
.ignoresProtect(), .ignoresProtect(),
@ -8302,7 +8322,7 @@ export function initMoves() {
.slicingMove(), .slicingMove(),
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9) new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
.attr(OpponentHighHpPowerAttr), .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) .attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true)
.target(MoveTarget.NEAR_ALLY) .target(MoveTarget.NEAR_ALLY)
.partial(), .partial(),

View File

@ -24,6 +24,9 @@ import * as Overrides from "../overrides";
import { ModifierType, modifierTypes } from "./modifier-type"; import { ModifierType, modifierTypes } from "./modifier-type";
import { Command } from "#app/ui/command-ui-handler.js"; 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; export type ModifierPredicate = (modifier: Modifier) => boolean;
const iconOverflowIndex = 24; const iconOverflowIndex = 24;
@ -520,6 +523,18 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier {
return 1; 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 { getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer {
const pokemon = this.getPokemon(scene); const pokemon = this.getPokemon(scene);
if (!pokemon) { if (!pokemon) {
@ -831,7 +846,7 @@ export class FlinchChanceModifier extends PokemonHeldItemModifier {
const pokemon = args[0] as Pokemon; const pokemon = args[0] as Pokemon;
const flinched = args[1] as Utils.BooleanHolder; 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; flinched.value = true;
return true; return true;
} }

View File

@ -1,7 +1,7 @@
import BattleScene, { bypassLogin } from "./battle-scene"; import BattleScene, { bypassLogin } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from "./utils"; 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 { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler"; import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat"; 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 { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { TempBattleStat } from "./data/temp-battle-stat"; import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; 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 { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena"; import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -646,7 +646,7 @@ export class BattlePhase extends Phase {
const tintSprites = this.scene.currentBattle.trainer.getTintSprites(); const tintSprites = this.scene.currentBattle.trainer.getTintSprites();
for (let i = 0; i < sprites.length; i++) { for (let i = 0; i < sprites.length; i++) {
const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2; const visible = !trainerSlot || !i === (trainerSlot === TrainerSlot.TRAINER) || sprites.length < 2;
[ sprites[i], tintSprites[i] ].map(sprite => { [sprites[i], tintSprites[i]].map(sprite => {
if (visible) { if (visible) {
sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16; sprite.x = trainerSlot || sprites.length < 2 ? 0 : i ? 16 : -16;
} }
@ -943,7 +943,7 @@ export class EncounterPhase extends BattlePhase {
const enemyField = this.scene.getEnemyField(); const enemyField = this.scene.getEnemyField();
this.scene.tweens.add({ 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, x: (_target, _key, value, fieldIndex: integer) => fieldIndex < 2 + (enemyField.length) ? value + 300 : value - 300,
duration: 2000, duration: 2000,
onComplete: () => { onComplete: () => {
@ -958,21 +958,21 @@ export class EncounterPhase extends BattlePhase {
const enemyField = this.scene.getEnemyField(); const enemyField = this.scene.getEnemyField();
if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { 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.battleType === BattleType.TRAINER) {
if (this.scene.currentBattle.double) { 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 { } 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 return enemyField.length === 1
? i18next.t("battle:singleWildAppeared", {pokemonName: enemyField[0].name}) ? i18next.t("battle:singleWildAppeared", { pokemonName: enemyField[0].name })
: i18next.t("battle:multiWildAppeared", {pokemonName1: enemyField[0].name, pokemonName2: enemyField[1].name}); : i18next.t("battle:multiWildAppeared", { pokemonName1: enemyField[0].name, pokemonName2: enemyField[1].name });
} }
doEncounterCommon(showEncounterMessage: boolean = true) { doEncounterCommon(showEncounterMessage: boolean = true) {
@ -1002,7 +1002,7 @@ export class EncounterPhase extends BattlePhase {
this.scene.currentBattle.started = true; this.scene.currentBattle.started = true;
this.scene.playBgm(undefined); this.scene.playBgm(undefined);
this.scene.pbTray.showPbTray(this.scene.getParty()); this.scene.pbTray.showPbTray(this.scene.getParty());
this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty()); this.scene.pbTrayEnemy.showPbTray(this.scene.getEnemyParty());
const doTrainerSummon = () => { const doTrainerSummon = () => {
this.hideEnemyTrainer(); this.hideEnemyTrainer();
const availablePartyMembers = this.scene.getEnemyParty().filter(p => !p.isFainted()).length; 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); this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex);
const showDialogueAndSummon = () => { 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())); 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 ? // how many player pokemon are on the field ?
const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length; 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 // 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 it's a double, there should be 2, otherwise 1
if (this.scene.currentBattle.double) { if (this.scene.currentBattle.double) {
return pokemonsOnFieldCount === requiredPokemonsOnField; return pokemonsOnFieldCount === requiredPokemonsOnField;
@ -1145,7 +1145,7 @@ export class NextEncounterPhase extends EncounterPhase {
const enemyField = this.scene.getEnemyField(); const enemyField = this.scene.getEnemyField();
this.scene.tweens.add({ 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", x: "+=300",
duration: 2000, duration: 2000,
onComplete: () => { onComplete: () => {
@ -1188,7 +1188,7 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase {
const enemyField = this.scene.getEnemyField(); const enemyField = this.scene.getEnemyField();
this.scene.tweens.add({ this.scene.tweens.add({
targets: [ this.scene.arenaEnemy, enemyField ].flat(), targets: [this.scene.arenaEnemy, enemyField].flat(),
x: "+=300", x: "+=300",
duration: 2000, duration: 2000,
onComplete: () => { onComplete: () => {
@ -1254,7 +1254,7 @@ export class SelectBiomePhase extends BattlePhase {
let biomeChoices: Biome[]; let biomeChoices: Biome[];
this.scene.executeWithSeedOffset(() => { this.scene.executeWithSeedOffset(() => {
biomeChoices = (!Array.isArray(biomeLinks[currentBiome]) biomeChoices = (!Array.isArray(biomeLinks[currentBiome])
? [ biomeLinks[currentBiome] as Biome ] ? [biomeLinks[currentBiome] as Biome]
: biomeLinks[currentBiome] as (Biome | [Biome, integer])[]) : biomeLinks[currentBiome] as (Biome | [Biome, integer])[])
.filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1])) .filter((b, i) => !Array.isArray(b) || !Utils.randSeedInt(b[1]))
.map(b => Array.isArray(b) ? b[0] : b); .map(b => Array.isArray(b) ? b[0] : b);
@ -1309,7 +1309,7 @@ export class SwitchBiomePhase extends BattlePhase {
} }
this.scene.tweens.add({ this.scene.tweens.add({
targets: [ this.scene.arenaEnemy, this.scene.lastEnemyTrainer ], targets: [this.scene.arenaEnemy, this.scene.lastEnemyTrainer],
x: "+=300", x: "+=300",
duration: 2000, duration: 2000,
onComplete: () => { onComplete: () => {
@ -1327,7 +1327,7 @@ export class SwitchBiomePhase extends BattlePhase {
this.scene.arenaPlayerTransition.setVisible(true); this.scene.arenaPlayerTransition.setVisible(true);
this.scene.tweens.add({ 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, duration: 1000,
delay: 1000, delay: 1000,
ease: "Sine.easeInOut", ease: "Sine.easeInOut",
@ -1807,7 +1807,7 @@ export class SummonMissingPhase extends SummonPhase {
} }
preSummon(): void { 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()); this.scene.time.delayedCall(250, () => this.summon());
} }
} }
@ -1839,7 +1839,7 @@ export class TurnInitPhase extends FieldPhase {
this.scene.getPlayerField().forEach(p => { this.scene.getPlayerField().forEach(p => {
// If this pokemon is in play and evolved into something illegal under the current challenge, force a switch // If this pokemon is in play and evolved into something illegal under the current challenge, force a switch
if (p.isOnField() && !p.isAllowedInBattle()) { 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()); const allowedPokemon = this.scene.getParty().filter(p => p.isAllowedInBattle());
@ -1920,7 +1920,7 @@ export class CommandPhase extends FieldPhase {
while (moveQueue.length && moveQueue[0] while (moveQueue.length && moveQueue[0]
&& moveQueue[0].move && (!playerPokemon.getMoveset().find(m => m.moveId === moveQueue[0].move) && 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(); moveQueue.shift();
} }
@ -1950,13 +1950,13 @@ export class CommandPhase extends FieldPhase {
case Command.FIGHT: case Command.FIGHT:
let useStruggle = false; let useStruggle = false;
if (cursor === -1 || if (cursor === -1 ||
playerPokemon.trySelectMove(cursor, args[0] as boolean) || playerPokemon.trySelectMove(cursor, args[0] as boolean) ||
(useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m.isUsable(playerPokemon)).length)) { (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 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 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]; const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2];
if (!moveId) { if (!moveId) {
turnCommand.targets = [ this.fieldIndex ]; turnCommand.targets = [this.fieldIndex];
} }
console.log(moveTargets, playerPokemon.name); console.log(moveTargets, playerPokemon.name);
if (moveTargets.targets.length <= 1 || moveTargets.multiple) { if (moveTargets.targets.length <= 1 || moveTargets.multiple) {
@ -2177,7 +2177,7 @@ export class EnemyCommandPhase extends FieldPhase {
const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores); const index = trainer.getNextSummonIndex(enemyPokemon.trainerSlot, partyMemberScores);
battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = battle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
{ command: Command.POKEMON, cursor: index, args: [ false ] }; { command: Command.POKEMON, cursor: index, args: [false] };
battle.enemySwitchCounter++; battle.enemySwitchCounter++;
@ -2214,7 +2214,7 @@ export class SelectTargetPhase extends PokemonPhase {
this.scene.currentBattle.turnCommands[this.fieldIndex] = null; this.scene.currentBattle.turnCommands[this.fieldIndex] = null;
this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex)); this.scene.unshiftPhase(new CommandPhase(this.scene, this.fieldIndex));
} else { } else {
turnCommand.targets = [ cursor ]; turnCommand.targets = [cursor];
} }
if (turnCommand.command === Command.BALL && this.fieldIndex) { if (turnCommand.command === Command.BALL && this.fieldIndex) {
this.scene.currentBattle.turnCommands[this.fieldIndex - 1].skip = true; 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 aPriority = new Utils.IntegerHolder(aMove.priority);
const bPriority = new Utils.IntegerHolder(bMove.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() === 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() === 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); applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b), null, bMove, bPriority);
if (aPriority.value !== bPriority.value) { if (aPriority.value !== bPriority.value) {
@ -2326,7 +2326,7 @@ export class TurnStartPhase extends FieldPhase {
return; return;
} }
}); });
// if only one pokemon is alive, use that one // if only one pokemon is alive, use that one
if (playerActivePokemon.length > 1) { if (playerActivePokemon.length > 1) {
// find which active pokemon has faster speed // find which active pokemon has faster speed
const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; 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))); this.scene.unshiftPhase(new ShowAbilityPhase(this.scene, this.pokemon.getBattlerIndex(), this.pokemon.getPassiveAbility().hasAttr(BlockRedirectAbAttr)));
} }
moveTarget.value = oldTarget; moveTarget.value = oldTarget;
} }
this.targets[0] = moveTarget.value; 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. // of the left Pokemon and gets hit unless this is checked.
if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) { if (targets.includes(battlerIndex) && this.move.getMove().moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
const i = targets.indexOf(battlerIndex); const i = targets.indexOf(battlerIndex);
targets.splice(i,i+1); targets.splice(i, i + 1);
} }
this.targets = targets; 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 }; const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
user.pushMoveHistory(moveHistoryEntry); 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)); const activeTargets = targets.map(t => t.isActive(true));
if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) { if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) {
user.turnData.hitCount = 1; 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)); 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 // 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 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) { if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move).then(() => { && !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => {
if (hitResult < HitResult.NO_EFFECT) { if (hitResult < HitResult.NO_EFFECT && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) {
const flinched = new Utils.BooleanHolder(false); const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched); user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
if (flinched.value) { if (flinched.value) {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id); 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), Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
user, target, move).then(() => { user, target, this.move.getMove()).then(() => {
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, move, hitResult).then(() => { return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
if (!user.isPlayer() && move instanceof AttackMove) { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target); user.scene.applyShuffledModifiers(this.scene, EnemyAttackStatusEffectChanceModifier, false, target);
} }
})).then(() => { })).then(() => {
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, move, hitResult).then(() => { applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move.getMove(), hitResult).then(() => {
if (move instanceof AttackMove) { if (this.move.getMove() instanceof AttackMove) {
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex()); this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
} }
resolve(); resolve();
@ -3151,7 +3151,7 @@ export class MoveAnimTestPhase extends BattlePhase {
} }
initMoveAnim(this.scene, moveId).then(() => { initMoveAnim(this.scene, moveId).then(() => {
loadMoveAnimAssets(this.scene, [ moveId ], true) loadMoveAnimAssets(this.scene, [moveId], true)
.then(() => { .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, () => { 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) { if (player) {
@ -3328,7 +3328,7 @@ export class StatChangePhase extends PokemonPhase {
} }
aggregateStatChanges(random: boolean = false): void { 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; let existingPhase: StatChangePhase;
if (this.stats.length === 1) { if (this.stats.length === 1) {
while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.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 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))) { && p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) {
this.stats.push(...existingPhase.stats); this.stats.push(...existingPhase.stats);
if (!this.scene.tryRemovePhase(p => p === existingPhase)) { if (!this.scene.tryRemovePhase(p => p === existingPhase)) {
@ -3512,7 +3512,7 @@ export class PostTurnStatusEffectPhase extends PokemonPhase {
break; break;
} }
if (damage) { 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)); this.scene.damageNumberHandler.add(this.getPokemon(), pokemon.damage(damage, false, true));
pokemon.updateInfo(); pokemon.updateInfo();
} }
@ -3962,7 +3962,7 @@ export class TrainerVictoryPhase extends BattlePhase {
const trainerType = this.scene.currentBattle.trainer.config.trainerType; const trainerType = this.scene.currentBattle.trainer.config.trainerType;
if (vouchers.hasOwnProperty(TrainerType[trainerType])) { if (vouchers.hasOwnProperty(TrainerType[trainerType])) {
if (!this.scene.validateVoucher(vouchers[TrainerType[trainerType]]) && this.scene.currentBattle.trainer.config.isBoss) { 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) { if (emptyMoveIndex > -1) {
pokemon.setMove(emptyMoveIndex, this.moveId); pokemon.setMove(emptyMoveIndex, this.moveId);
initMoveAnim(this.scene, this.moveId).then(() => { initMoveAnim(this.scene, this.moveId).then(() => {
loadMoveAnimAssets(this.scene, [ this.moveId ], true) loadMoveAnimAssets(this.scene, [this.moveId], true)
.then(() => { .then(() => {
this.scene.ui.setMode(messageMode).then(() => { this.scene.ui.setMode(messageMode).then(() => {
this.scene.playSound("level_up_fanfare"); 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) { if (this.scene.getParty().length === 6) {
const promptRelease = () => { const promptRelease = () => {
this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => { 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.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
this.scene.tweens.add({ this.scene.tweens.add({
targets: [ this.scene.arenaEnemy, enemyField ].flat(), targets: [this.scene.arenaEnemy, enemyField].flat(),
alpha: 0, alpha: 0,
duration: 250, duration: 250,
ease: "Sine.easeIn", 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) => { 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) { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) {
const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier 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]; const itemModifier = itemModifiers[itemIndex];
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity);
} else { } else {
@ -5223,7 +5223,7 @@ export class SelectModifierPhase extends BattlePhase {
getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): integer { getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): integer {
let baseValue = 0; let baseValue = 0;
if (lockRarities) { if (lockRarities) {
const tierValues = [ 50, 125, 300, 750, 2000 ]; const tierValues = [50, 125, 300, 750, 2000];
for (const opt of typeOptions) { for (const opt of typeOptions) {
baseValue += tierValues[opt.type.tier]; baseValue += tierValues[opt.type.tier];
} }
@ -5443,7 +5443,7 @@ export class TrainerMessageTestPhase extends BattlePhase {
continue; continue;
} }
const config = trainerConfigs[type]; 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 => { .map(messages => {
if (messages?.length) { if (messages?.length) {
testMessages.push(...messages); testMessages.push(...messages);

View File

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

View File

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

View File

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