Implement Guard Dog, attribute for abilities to give Intimidate immunity (#448)
* abilities: implement guard dog, abilities that give intimidate immunity * abilities: implement rattled's second effect, remove refs to mold breaker * abilities: fix rattled not giving the attack drop still * abilities: make ability bars pop in to some success * abilities: implement suction cups since it has the same effect * moves: add custom fail text, fix animation issues with Guard Dog/Roar * abilities: manually show intimidate ability bar to prevent weirdnesspull/499/head
parent
f5bf35f8de
commit
80ee440109
|
@ -20,6 +20,8 @@ import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
|
||||||
import { Abilities } from "./enums/abilities";
|
import { Abilities } from "./enums/abilities";
|
||||||
import i18next, { Localizable } from "#app/plugins/i18n.js";
|
import i18next, { Localizable } from "#app/plugins/i18n.js";
|
||||||
import { Command } from "../ui/command-ui-handler";
|
import { Command } from "../ui/command-ui-handler";
|
||||||
|
import Battle from "#app/battle.js";
|
||||||
|
import { ability } from "#app/locales/en/ability.js";
|
||||||
|
|
||||||
export class Ability implements Localizable {
|
export class Ability implements Localizable {
|
||||||
public id: Abilities;
|
public id: Abilities;
|
||||||
|
@ -1271,6 +1273,40 @@ export class IgnoreOpponentStatChangesAbAttr extends AbAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class IntimidateImmunityAbAttr extends AbAttr {
|
||||||
|
constructor() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
|
cancelled.value = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
|
||||||
|
return getPokemonMessage(pokemon, `'s ${abilityName} prevented it from being Intimidated!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PostIntimidateStatChangeAbAttr extends AbAttr {
|
||||||
|
private stats: BattleStat[];
|
||||||
|
private levels: integer;
|
||||||
|
private overwrites: boolean;
|
||||||
|
|
||||||
|
constructor(stats: BattleStat[], levels: integer, overwrites?: boolean) {
|
||||||
|
super(true)
|
||||||
|
this.stats = stats
|
||||||
|
this.levels = levels
|
||||||
|
this.overwrites = !!overwrites
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
|
pokemon.scene.pushPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, this.stats, this.levels));
|
||||||
|
cancelled.value = this.overwrites;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostSummonAbAttr extends AbAttr {
|
export class PostSummonAbAttr extends AbAttr {
|
||||||
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
|
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1313,34 +1349,36 @@ export class PostSummonStatChangeAbAttr extends PostSummonAbAttr {
|
||||||
private stats: BattleStat[];
|
private stats: BattleStat[];
|
||||||
private levels: integer;
|
private levels: integer;
|
||||||
private selfTarget: boolean;
|
private selfTarget: boolean;
|
||||||
|
private intimidate: boolean;
|
||||||
|
|
||||||
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean) {
|
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, intimidate?: boolean) {
|
||||||
super();
|
super(false);
|
||||||
|
|
||||||
this.stats = typeof(stats) === 'number'
|
this.stats = typeof(stats) === 'number'
|
||||||
? [ stats as BattleStat ]
|
? [ stats as BattleStat ]
|
||||||
: stats as BattleStat[];
|
: stats as BattleStat[];
|
||||||
this.levels = levels;
|
this.levels = levels;
|
||||||
this.selfTarget = !!selfTarget;
|
this.selfTarget = !!selfTarget;
|
||||||
|
this.intimidate = !!intimidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
|
applyPostSummon(pokemon: Pokemon, passive: boolean, args: any[]): boolean {
|
||||||
const statChangePhases: StatChangePhase[] = [];
|
queueShowAbility(pokemon, passive); // TODO: Better solution than manually showing the ability here
|
||||||
|
if (this.selfTarget) {
|
||||||
if (this.selfTarget)
|
pokemon.scene.pushPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
|
||||||
statChangePhases.push(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.levels));
|
return true;
|
||||||
else {
|
|
||||||
for (let opponent of pokemon.getOpponents())
|
|
||||||
statChangePhases.push(new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels));
|
|
||||||
}
|
}
|
||||||
|
for (let opponent of pokemon.getOpponents()) {
|
||||||
for (let statChangePhase of statChangePhases) {
|
const cancelled = new Utils.BooleanHolder(false)
|
||||||
if (!this.selfTarget && !statChangePhase.getPokemon().summonData)
|
if (this.intimidate) {
|
||||||
pokemon.scene.pushPhase(statChangePhase); // TODO: This causes the ability bar to be shown at the wrong time
|
applyAbAttrs(IntimidateImmunityAbAttr, opponent, cancelled);
|
||||||
else
|
applyAbAttrs(PostIntimidateStatChangeAbAttr, opponent, cancelled);
|
||||||
|
}
|
||||||
|
if (!cancelled.value) {
|
||||||
|
const statChangePhase = new StatChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.levels);
|
||||||
pokemon.scene.unshiftPhase(statChangePhase);
|
pokemon.scene.unshiftPhase(statChangePhase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2316,6 +2354,13 @@ export class FlinchStatChangeAbAttr extends FlinchEffectAbAttr {
|
||||||
|
|
||||||
export class IncreasePpAbAttr extends AbAttr { }
|
export class IncreasePpAbAttr extends AbAttr { }
|
||||||
|
|
||||||
|
export class ForceSwitchOutImmunityAbAttr extends AbAttr {
|
||||||
|
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||||
|
pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` can't be switched out!`))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ReduceBerryUseThresholdAbAttr extends AbAttr {
|
export class ReduceBerryUseThresholdAbAttr extends AbAttr {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -2656,6 +2701,7 @@ export function initAbilities() {
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.OBLIVIOUS, 3)
|
new Ability(Abilities.OBLIVIOUS, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
|
||||||
|
.attr(IntimidateImmunityAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.CLOUD_NINE, 3)
|
new Ability(Abilities.CLOUD_NINE, 3)
|
||||||
.attr(SuppressWeatherEffectAbAttr, true),
|
.attr(SuppressWeatherEffectAbAttr, true),
|
||||||
|
@ -2678,12 +2724,13 @@ export function initAbilities() {
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
new Ability(Abilities.OWN_TEMPO, 3)
|
new Ability(Abilities.OWN_TEMPO, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
|
||||||
|
.attr(IntimidateImmunityAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SUCTION_CUPS, 3)
|
new Ability(Abilities.SUCTION_CUPS, 3)
|
||||||
.ignorable()
|
.attr(ForceSwitchOutImmunityAbAttr)
|
||||||
.unimplemented(),
|
.ignorable(),
|
||||||
new Ability(Abilities.INTIMIDATE, 3)
|
new Ability(Abilities.INTIMIDATE, 3)
|
||||||
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, -1),
|
.attr(PostSummonStatChangeAbAttr, BattleStat.ATK, -1, false, true),
|
||||||
new Ability(Abilities.SHADOW_TAG, 3)
|
new Ability(Abilities.SHADOW_TAG, 3)
|
||||||
.attr(ArenaTrapAbAttr),
|
.attr(ArenaTrapAbAttr),
|
||||||
new Ability(Abilities.ROUGH_SKIN, 3)
|
new Ability(Abilities.ROUGH_SKIN, 3)
|
||||||
|
@ -2732,6 +2779,7 @@ export function initAbilities() {
|
||||||
.attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON),
|
.attr(PostDefendContactApplyStatusEffectAbAttr, 30, StatusEffect.POISON),
|
||||||
new Ability(Abilities.INNER_FOCUS, 3)
|
new Ability(Abilities.INNER_FOCUS, 3)
|
||||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.FLINCHED)
|
.attr(BattlerTagImmunityAbAttr, BattlerTagType.FLINCHED)
|
||||||
|
.attr(IntimidateImmunityAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.MAGMA_ARMOR, 3)
|
new Ability(Abilities.MAGMA_ARMOR, 3)
|
||||||
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE)
|
.attr(StatusEffectImmunityAbAttr, StatusEffect.FREEZE)
|
||||||
|
@ -2928,8 +2976,9 @@ export function initAbilities() {
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
new Ability(Abilities.SLOW_START, 4)
|
new Ability(Abilities.SLOW_START, 4)
|
||||||
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5),
|
.attr(PostSummonAddBattlerTagAbAttr, BattlerTagType.SLOW_START, 5),
|
||||||
new Ability(Abilities.SCRAPPY, 4)
|
new Ability(Abilities.SCRAPPY, 4)
|
||||||
.unimplemented(),
|
.attr(IntimidateImmunityAbAttr)
|
||||||
|
.partial(),
|
||||||
new Ability(Abilities.STORM_DRAIN, 4)
|
new Ability(Abilities.STORM_DRAIN, 4)
|
||||||
.attr(RedirectTypeMoveAbAttr, Type.WATER)
|
.attr(RedirectTypeMoveAbAttr, Type.WATER)
|
||||||
.attr(TypeImmunityStatChangeAbAttr, Type.WATER, BattleStat.SPATK, 1)
|
.attr(TypeImmunityStatChangeAbAttr, Type.WATER, BattleStat.SPATK, 1)
|
||||||
|
@ -3048,7 +3097,7 @@ export function initAbilities() {
|
||||||
new Ability(Abilities.RATTLED, 5)
|
new Ability(Abilities.RATTLED, 5)
|
||||||
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS && (move.type === Type.DARK || move.type === Type.BUG ||
|
.attr(PostDefendStatChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS && (move.type === Type.DARK || move.type === Type.BUG ||
|
||||||
move.type === Type.GHOST), BattleStat.SPD, 1)
|
move.type === Type.GHOST), BattleStat.SPD, 1)
|
||||||
.partial(),
|
.attr(PostIntimidateStatChangeAbAttr, [BattleStat.SPD], 1),
|
||||||
new Ability(Abilities.MAGIC_BOUNCE, 5)
|
new Ability(Abilities.MAGIC_BOUNCE, 5)
|
||||||
.ignorable()
|
.ignorable()
|
||||||
.unimplemented(),
|
.unimplemented(),
|
||||||
|
@ -3433,8 +3482,9 @@ export function initAbilities() {
|
||||||
.ignorable()
|
.ignorable()
|
||||||
.partial(),
|
.partial(),
|
||||||
new Ability(Abilities.GUARD_DOG, 9)
|
new Ability(Abilities.GUARD_DOG, 9)
|
||||||
.ignorable()
|
.attr(PostIntimidateStatChangeAbAttr, [BattleStat.ATK], 1, true)
|
||||||
.unimplemented(),
|
.attr(ForceSwitchOutImmunityAbAttr)
|
||||||
|
.ignorable(),
|
||||||
new Ability(Abilities.ROCKY_PAYLOAD, 9)
|
new Ability(Abilities.ROCKY_PAYLOAD, 9)
|
||||||
.attr(MoveTypePowerBoostAbAttr, Type.ROCK),
|
.attr(MoveTypePowerBoostAbAttr, Type.ROCK),
|
||||||
new Ability(Abilities.WIND_POWER, 9)
|
new Ability(Abilities.WIND_POWER, 9)
|
||||||
|
|
|
@ -12,7 +12,7 @@ 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 { ArenaTagType } from "./enums/arena-tag-type";
|
import { ArenaTagType } from "./enums/arena-tag-type";
|
||||||
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr } from "./ability";
|
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, NoTransformAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr } from "./ability";
|
||||||
import { Abilities } from "./enums/abilities";
|
import { Abilities } from "./enums/abilities";
|
||||||
import { allAbilities } from './ability';
|
import { allAbilities } from './ability';
|
||||||
import { PokemonHeldItemModifier } from "../modifier/modifier";
|
import { PokemonHeldItemModifier } from "../modifier/modifier";
|
||||||
|
@ -326,6 +326,13 @@ export default class Move implements Localizable {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
||||||
|
let failedText = null;
|
||||||
|
for (let attr of this.attrs)
|
||||||
|
failedText = attr.getFailedText(user, target, move, cancelled);
|
||||||
|
return failedText;
|
||||||
|
}
|
||||||
|
|
||||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
|
||||||
|
@ -422,6 +429,10 @@ export abstract class MoveAttr {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2964,16 +2975,14 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (!this.user && target.isMax())
|
|
||||||
return resolve(false);
|
|
||||||
|
|
||||||
// Check if the move category is not STATUS or if the switch out condition is not met
|
// Check if the move category is not STATUS or if the switch out condition is not met
|
||||||
if (move.category !== MoveCategory.STATUS && !this.getSwitchOutCondition()(user, target, move)) {
|
if (!this.getCondition()(user, target, move)) {
|
||||||
//Apply effects before switch out i.e. poison point, flame body, etc
|
//Apply effects before switch out i.e. poison point, flame body, etc
|
||||||
applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, new PokemonMove(move.id), null);
|
applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, new PokemonMove(move.id), null);
|
||||||
return resolve(false);
|
return resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the switch out logic inside the conditional block
|
// Move the switch out logic inside the conditional block
|
||||||
// This ensures that the switch out only happens when the conditions are met
|
// This ensures that the switch out only happens when the conditions are met
|
||||||
const switchOutTarget = this.user ? user : target;
|
const switchOutTarget = this.user ? user : target;
|
||||||
|
@ -3020,15 +3029,23 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||||
resolve(true);
|
resolve(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move);
|
return (user, target, move) => (move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move));
|
||||||
|
}
|
||||||
|
|
||||||
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
||||||
|
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, cancelled);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSwitchOutCondition(): MoveConditionFunc {
|
getSwitchOutCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => {
|
return (user, target, move) => {
|
||||||
const switchOutTarget = (this.user ? user : target);
|
const switchOutTarget = (this.user ? user : target);
|
||||||
const player = switchOutTarget instanceof PlayerPokemon;
|
const player = switchOutTarget instanceof PlayerPokemon;
|
||||||
|
|
||||||
|
if (!this.user && move.category == MoveCategory.STATUS && (target.hasAbilityWithAttr(ForceSwitchOutImmunityAbAttr) || target.isMax()))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!player && !user.scene.currentBattle.battleType) {
|
if (!player && !user.scene.currentBattle.battleType) {
|
||||||
if (this.batonPass)
|
if (this.batonPass)
|
||||||
|
|
|
@ -2278,18 +2278,21 @@ export class MovePhase extends BattlePhase {
|
||||||
|
|
||||||
// Assume conditions affecting targets only apply to moves with a single target
|
// Assume conditions affecting targets only apply to moves with a single target
|
||||||
let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove());
|
let success = this.move.getMove().applyConditions(this.pokemon, targets[0], this.move.getMove());
|
||||||
let failedText = null;
|
let cancelled = new Utils.BooleanHolder(true);
|
||||||
|
let failedText = this.move.getMove().getFailedText(this.pokemon, targets[0], this.move.getMove(), cancelled);
|
||||||
if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove()))
|
if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove()))
|
||||||
success = false;
|
success = false;
|
||||||
else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) {
|
else if (success && this.scene.arena.isMoveTerrainCancelled(this.pokemon, this.targets, this.move.getMove())) {
|
||||||
success = false;
|
success = false;
|
||||||
failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain.terrainType);
|
if (failedText == null)
|
||||||
|
failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain.terrainType);
|
||||||
}
|
}
|
||||||
if (success)
|
if (success)
|
||||||
this.scene.unshiftPhase(this.getEffectPhase());
|
this.scene.unshiftPhase(this.getEffectPhase());
|
||||||
else {
|
else {
|
||||||
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
|
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
|
||||||
this.showFailedText(failedText);
|
if (!cancelled.value)
|
||||||
|
this.showFailedText(failedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.end();
|
this.end();
|
||||||
|
|
Loading…
Reference in New Issue