[Ability] Partially implement Gulp Missile (#3327)
* implement gulp missile * add tests * change fly to dive * show ability when reverting to normal form * update ai score, tests * update score condition * adjust conditions, damage * add underwater test * update damage in test * partial commit
This commit is contained in:
parent
9eadce80c6
commit
9875fcc59d
|
@ -6,7 +6,7 @@ import { BattleStat, getBattleStatName } from "./battle-stat";
|
||||||
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
|
import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "../messages";
|
||||||
import { Weather, WeatherType } from "./weather";
|
import { Weather, WeatherType } from "./weather";
|
||||||
import { BattlerTag, GroundedTag } from "./battler-tags";
|
import { BattlerTag, GroundedTag, GulpMissileTag } from "./battler-tags";
|
||||||
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
|
||||||
import { Gender } from "./gender";
|
import { Gender } from "./gender";
|
||||||
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit } from "./move";
|
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit } from "./move";
|
||||||
|
@ -496,6 +496,49 @@ export class PostDefendAbAttr extends AbAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the effects of Gulp Missile when the user is hit by an attack.
|
||||||
|
* @extends PostDefendAbAttr
|
||||||
|
*/
|
||||||
|
export class PostDefendGulpMissileAbAttr extends PostDefendAbAttr {
|
||||||
|
constructor() {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Damages the attacker and triggers the secondary effect based on the form or the BattlerTagType.
|
||||||
|
* @param {Pokemon} pokemon - The defending Pokemon.
|
||||||
|
* @param passive - n/a
|
||||||
|
* @param {Pokemon} attacker - The attacking Pokemon.
|
||||||
|
* @param {Move} move - The move being used.
|
||||||
|
* @param {HitResult} hitResult - n/a
|
||||||
|
* @param {any[]} args - n/a
|
||||||
|
* @returns Whether the effects of the ability are applied.
|
||||||
|
*/
|
||||||
|
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean | Promise<boolean> {
|
||||||
|
const battlerTag = pokemon.getTag(GulpMissileTag);
|
||||||
|
if (!battlerTag || move.category === MoveCategory.STATUS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelled = new Utils.BooleanHolder(false);
|
||||||
|
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
|
||||||
|
|
||||||
|
if (!cancelled.value) {
|
||||||
|
attacker.damageAndUpdate(Math.max(1, Math.floor(attacker.getMaxHp() / 4)), HitResult.OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (battlerTag.tagType === BattlerTagType.GULP_MISSILE_ARROKUDA) {
|
||||||
|
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ BattleStat.DEF ], -1));
|
||||||
|
} else {
|
||||||
|
attacker.trySetStatus(StatusEffect.PARALYSIS, true, pokemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
pokemon.removeTag(battlerTag.tagType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
|
export class PostDefendDisguiseAbAttr extends PostDefendAbAttr {
|
||||||
|
|
||||||
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
applyPostDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
|
||||||
|
@ -5087,7 +5130,11 @@ export function initAbilities() {
|
||||||
.attr(UnsuppressableAbilityAbAttr)
|
.attr(UnsuppressableAbilityAbAttr)
|
||||||
.attr(NoTransformAbilityAbAttr)
|
.attr(NoTransformAbilityAbAttr)
|
||||||
.attr(NoFusionAbilityAbAttr)
|
.attr(NoFusionAbilityAbAttr)
|
||||||
.unimplemented(),
|
.attr(UncopiableAbilityAbAttr)
|
||||||
|
.attr(UnswappableAbilityAbAttr)
|
||||||
|
.attr(PostDefendGulpMissileAbAttr)
|
||||||
|
// Does not transform when Surf/Dive misses/is protected
|
||||||
|
.partial(),
|
||||||
new Ability(Abilities.STALWART, 8)
|
new Ability(Abilities.STALWART, 8)
|
||||||
.attr(BlockRedirectAbAttr),
|
.attr(BlockRedirectAbAttr),
|
||||||
new Ability(Abilities.STEAM_ENGINE, 8)
|
new Ability(Abilities.STEAM_ENGINE, 8)
|
||||||
|
|
|
@ -1647,6 +1647,39 @@ export class StockpilingTag extends BattlerTag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Battler tag for Gulp Missile used by Cramorant.
|
||||||
|
* @extends BattlerTag
|
||||||
|
*/
|
||||||
|
export class GulpMissileTag extends BattlerTag {
|
||||||
|
constructor(tagType: BattlerTagType, sourceMove: Moves) {
|
||||||
|
super(tagType, BattlerTagLapseType.CUSTOM, 0, sourceMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gulp Missile's initial form changes are triggered by using Surf and Dive.
|
||||||
|
* @param {Pokemon} pokemon The Pokemon with Gulp Missile ability.
|
||||||
|
* @returns Whether the BattlerTag can be added.
|
||||||
|
*/
|
||||||
|
canAdd(pokemon: Pokemon): boolean {
|
||||||
|
const isSurfOrDive = [ Moves.SURF, Moves.DIVE ].includes(this.sourceMove);
|
||||||
|
const isNormalForm = pokemon.formIndex === 0 && !pokemon.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA) && !pokemon.getTag(BattlerTagType.GULP_MISSILE_PIKACHU);
|
||||||
|
const isCramorant = pokemon.species.speciesId === Species.CRAMORANT;
|
||||||
|
|
||||||
|
return isSurfOrDive && isNormalForm && isCramorant;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(pokemon: Pokemon): void {
|
||||||
|
super.onAdd(pokemon);
|
||||||
|
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove(pokemon: Pokemon): void {
|
||||||
|
super.onRemove(pokemon);
|
||||||
|
pokemon.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
|
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
|
||||||
switch (tagType) {
|
switch (tagType) {
|
||||||
case BattlerTagType.RECHARGING:
|
case BattlerTagType.RECHARGING:
|
||||||
|
@ -1770,6 +1803,9 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||||
return new StockpilingTag(sourceMove);
|
return new StockpilingTag(sourceMove);
|
||||||
case BattlerTagType.OCTOLOCK:
|
case BattlerTagType.OCTOLOCK:
|
||||||
return new OctolockTag(sourceId);
|
return new OctolockTag(sourceId);
|
||||||
|
case BattlerTagType.GULP_MISSILE_ARROKUDA:
|
||||||
|
case BattlerTagType.GULP_MISSILE_PIKACHU:
|
||||||
|
return new GulpMissileTag(tagType, sourceMove);
|
||||||
case BattlerTagType.NONE:
|
case BattlerTagType.NONE:
|
||||||
default:
|
default:
|
||||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
|
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
|
||||||
import { BattleEndPhase, MoveEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases";
|
import { BattleEndPhase, MoveEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases";
|
||||||
import { BattleStat, getBattleStatName } from "./battle-stat";
|
import { BattleStat, getBattleStatName } from "./battle-stat";
|
||||||
import { EncoreTag, HelpingHandTag, SemiInvulnerableTag, StockpilingTag, TypeBoostTag } from "./battler-tags";
|
import { EncoreTag, GulpMissileTag, HelpingHandTag, SemiInvulnerableTag, StockpilingTag, TypeBoostTag } from "./battler-tags";
|
||||||
import { getPokemonNameWithAffix } from "../messages";
|
import { getPokemonNameWithAffix } from "../messages";
|
||||||
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
|
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
|
||||||
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect";
|
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect";
|
||||||
|
@ -4300,6 +4300,46 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the appropriate battler tag for Gulp Missile when Surf or Dive is used.
|
||||||
|
* @extends MoveEffectAttr
|
||||||
|
*/
|
||||||
|
export class GulpMissileTagAttr extends MoveEffectAttr {
|
||||||
|
constructor() {
|
||||||
|
super(true, MoveEffectTrigger.POST_APPLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio.
|
||||||
|
* @param {Pokemon} user The Pokemon using the move.
|
||||||
|
* @param {Pokemon} target The Pokemon being targeted by the move.
|
||||||
|
* @param {Move} move The move being used.
|
||||||
|
* @param {any[]} args Additional arguments, if any.
|
||||||
|
* @returns Whether the BattlerTag is applied.
|
||||||
|
*/
|
||||||
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
||||||
|
if (!super.apply(user, target, move, args)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.hasAbility(Abilities.GULP_MISSILE) && user.species.speciesId === Species.CRAMORANT) {
|
||||||
|
if (user.getHpRatio() >= .5) {
|
||||||
|
user.addTag(BattlerTagType.GULP_MISSILE_ARROKUDA, 0, move.id);
|
||||||
|
} else {
|
||||||
|
user.addTag(BattlerTagType.GULP_MISSILE_PIKACHU, 0, move.id);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||||
|
const isCramorant = user.hasAbility(Abilities.GULP_MISSILE) && user.species.speciesId === Species.CRAMORANT;
|
||||||
|
return isCramorant && !user.getTag(GulpMissileTag) ? 10 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class CurseAttr extends MoveEffectAttr {
|
export class CurseAttr extends MoveEffectAttr {
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean {
|
apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean {
|
||||||
|
@ -6157,7 +6197,8 @@ export function initMoves() {
|
||||||
new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1),
|
new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1),
|
||||||
new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1)
|
new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1)
|
||||||
.target(MoveTarget.ALL_NEAR_OTHERS)
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
||||||
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
|
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true)
|
||||||
|
.attr(GulpMissileTagAttr),
|
||||||
new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
|
new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
|
||||||
.attr(StatusEffectAttr, StatusEffect.FREEZE),
|
.attr(StatusEffectAttr, StatusEffect.FREEZE),
|
||||||
new AttackMove(Moves.BLIZZARD, Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 10, 0, 1)
|
new AttackMove(Moves.BLIZZARD, Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 10, 0, 1)
|
||||||
|
@ -6822,6 +6863,7 @@ export function initMoves() {
|
||||||
.partial(),
|
.partial(),
|
||||||
new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3)
|
new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3)
|
||||||
.attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", {pokemonName: "{USER}"}), BattlerTagType.UNDERWATER)
|
.attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", {pokemonName: "{USER}"}), BattlerTagType.UNDERWATER)
|
||||||
|
.attr(GulpMissileTagAttr)
|
||||||
.ignoresVirtual(),
|
.ignoresVirtual(),
|
||||||
new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3)
|
new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3)
|
||||||
.attr(MultiHitAttr),
|
.attr(MultiHitAttr),
|
||||||
|
|
|
@ -828,6 +828,12 @@ export const pokemonFormChanges: PokemonFormChanges = {
|
||||||
[Species.EISCUE]: [
|
[Species.EISCUE]: [
|
||||||
new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true),
|
new SpeciesFormChange(Species.EISCUE, "", "no-ice", new SpeciesFormChangeManualTrigger(), true),
|
||||||
new SpeciesFormChange(Species.EISCUE, "no-ice", "", new SpeciesFormChangeManualTrigger(), true),
|
new SpeciesFormChange(Species.EISCUE, "no-ice", "", new SpeciesFormChangeManualTrigger(), true),
|
||||||
|
],
|
||||||
|
[Species.CRAMORANT]: [
|
||||||
|
new SpeciesFormChange(Species.CRAMORANT, "", "gulping", new SpeciesFormChangeManualTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() >= .5)),
|
||||||
|
new SpeciesFormChange(Species.CRAMORANT, "", "gorging", new SpeciesFormChangeManualTrigger, true, new SpeciesFormChangeCondition(p => p.getHpRatio() < .5)),
|
||||||
|
new SpeciesFormChange(Species.CRAMORANT, "gulping", "", new SpeciesFormChangeManualTrigger, true),
|
||||||
|
new SpeciesFormChange(Species.CRAMORANT, "gorging", "", new SpeciesFormChangeManualTrigger, true),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -63,5 +63,7 @@ export enum BattlerTagType {
|
||||||
ICE_FACE = "ICE_FACE",
|
ICE_FACE = "ICE_FACE",
|
||||||
STOCKPILING = "STOCKPILING",
|
STOCKPILING = "STOCKPILING",
|
||||||
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
|
RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE",
|
||||||
ALWAYS_GET_HIT = "ALWAYS_GET_HIT"
|
ALWAYS_GET_HIT = "ALWAYS_GET_HIT",
|
||||||
|
GULP_MISSILE_ARROKUDA = "GULP_MISSILE_ARROKUDA",
|
||||||
|
GULP_MISSILE_PIKACHU = "GULP_MISSILE_PIKACHU"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
|
||||||
|
import {
|
||||||
|
MoveEndPhase,
|
||||||
|
TurnEndPhase,
|
||||||
|
TurnStartPhase,
|
||||||
|
} from "#app/phases";
|
||||||
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
|
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||||
|
import { BattleStat } from "#app/data/battle-stat.js";
|
||||||
|
import { StatusEffect } from "#app/enums/status-effect.js";
|
||||||
|
import { GulpMissileTag } from "#app/data/battler-tags.js";
|
||||||
|
import Pokemon from "#app/field/pokemon.js";
|
||||||
|
|
||||||
|
describe("Abilities - Gulp Missile", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
const NORMAL_FORM = 0;
|
||||||
|
const GULPING_FORM = 1;
|
||||||
|
const GORGING_FORM = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the effect damage of Gulp Missile
|
||||||
|
* See Gulp Missile {@link https://bulbapedia.bulbagarden.net/wiki/Gulp_Missile_(Ability)}
|
||||||
|
* @param {Pokemon} pokemon The pokemon taking the effect damage.
|
||||||
|
* @returns The effect damage of Gulp Missile
|
||||||
|
*/
|
||||||
|
const getEffectDamage = (pokemon: Pokemon): number => {
|
||||||
|
return Math.max(1, Math.floor(pokemon.getMaxHp() * 1/4));
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleType("single")
|
||||||
|
.moveset([Moves.SURF, Moves.DIVE, Moves.SPLASH])
|
||||||
|
.enemySpecies(Species.SNORLAX)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(SPLASH_ONLY)
|
||||||
|
.enemyLevel(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes to Gulping Form if HP is over half when Surf or Dive is used", async () => {
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
||||||
|
await game.toNextTurn();
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getHpRatio()).toBeGreaterThanOrEqual(.5);
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes to Gorging Form if HP is under half when Surf or Dive is used", async () => {
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
|
||||||
|
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.49);
|
||||||
|
expect(cramorant.getHpRatio()).toBe(.49);
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_PIKACHU)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GORGING_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deals ¼ of the attacker's maximum HP when hit by a damaging attack", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
vi.spyOn(enemy, "damageAndUpdate");
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not have any effect when hit by non-damaging attack", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.TAIL_WHIP));
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("lowers the attacker's Defense by 1 stage when hit in Gulping form", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
|
||||||
|
vi.spyOn(enemy, "damageAndUpdate");
|
||||||
|
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy));
|
||||||
|
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(-1);
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
|
||||||
|
expect(cramorant.formIndex).toBe(NORMAL_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("paralyzes the enemy when hit in Gorging form", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
|
||||||
|
vi.spyOn(enemy, "damageAndUpdate");
|
||||||
|
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.45);
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_PIKACHU)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GORGING_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy));
|
||||||
|
expect(enemy.status.effect).toBe(StatusEffect.PARALYSIS);
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_PIKACHU)).toBeUndefined();
|
||||||
|
expect(cramorant.formIndex).toBe(NORMAL_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not activate the ability when underwater", async () => {
|
||||||
|
game.override
|
||||||
|
.enemyMoveset(Array(4).fill(Moves.SURF))
|
||||||
|
.enemySpecies(Species.REGIELEKI)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyLevel(5);
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
// Turn 2 underwater, enemy moves first
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.DIVE));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.formIndex).toBe(NORMAL_FORM);
|
||||||
|
expect(cramorant.getTag(GulpMissileTag)).toBeUndefined();
|
||||||
|
|
||||||
|
// Turn 2 Cramorant comes out and changes form
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
expect(cramorant.formIndex).not.toBe(NORMAL_FORM);
|
||||||
|
expect(cramorant.getTag(GulpMissileTag)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prevents effect damage but inflicts secondary effect on attacker with Magic Guard", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE)).enemyAbility(Abilities.MAGIC_GUARD);
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
const enemy = game.scene.getEnemyPokemon();
|
||||||
|
|
||||||
|
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
const enemyHpPreEffect = enemy.hp;
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
expect(enemy.hp).toBe(enemyHpPreEffect);
|
||||||
|
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(-1);
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
|
||||||
|
expect(cramorant.formIndex).toBe(NORMAL_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cannot be suppressed", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.GASTRO_ACID));
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.hasAbility(Abilities.GULP_MISSILE)).toBe(true);
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cannot be swapped with another ability", async () => {
|
||||||
|
game.override.enemyMoveset(Array(4).fill(Moves.SKILL_SWAP));
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
|
||||||
|
const cramorant = game.scene.getPlayerPokemon();
|
||||||
|
vi.spyOn(cramorant, "getHpRatio").mockReturnValue(.55);
|
||||||
|
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SURF));
|
||||||
|
await game.phaseInterceptor.to(MoveEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to(TurnEndPhase);
|
||||||
|
|
||||||
|
expect(cramorant.hasAbility(Abilities.GULP_MISSILE)).toBe(true);
|
||||||
|
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeDefined();
|
||||||
|
expect(cramorant.formIndex).toBe(GULPING_FORM);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cannot be copied", async () => {
|
||||||
|
game.override.enemyAbility(Abilities.TRACE);
|
||||||
|
|
||||||
|
await game.startBattle([Species.CRAMORANT]);
|
||||||
|
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
|
||||||
|
await game.phaseInterceptor.to(TurnStartPhase);
|
||||||
|
|
||||||
|
expect(game.scene.getEnemyPokemon().hasAbility(Abilities.GULP_MISSILE)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue