Compare commits

...

22 Commits

Author SHA1 Message Date
Bertie690
bd27e92403
Merge 1d1ad8e9dc into 79576ad117 2025-08-09 00:51:36 -05:00
NightKev
79576ad117
[GitHub] Update .github/CODEOWNERS file (#6240)
* [GitHub] Update `.github/CODEOWNERS` file

`@pagefaultgames/senior-dev-team` added to
`package.json` and `pnpm-lock.yaml`

`@pagefaultgames/balance-team` added to `/src/data/trainers`

* Move senior dev team entries to the bottom of the file
2025-08-09 03:18:40 +00:00
Madmadness65
f0a56a3049
[Sprite] Add unique trainer sprite for Rocket Boss Giovanni (#6235) 2025-08-09 02:15:33 +00:00
Madmadness65
7316628448 [Misc] Fix comment formatting nitpick in ability-id
Adds a space between every `/**` and `{@link`.
2025-08-08 20:31:51 -05:00
Bertie690
1d1ad8e9dc Applied benje's reviews and fixed my godawful penmanship 2025-08-07 22:08:00 -04:00
Bertie690
24f3d75055
AAAAAAAAAAAAA
Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
2025-08-07 21:36:11 -04:00
Bertie690
839a099b3a
Update test/@types/vitest.d.ts
Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
2025-08-07 21:28:43 -04:00
Bertie690
c07f786042
Update test/@types/test-helpers.ts
Co-authored-by: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com>
2025-08-07 21:28:32 -04:00
Bertie690
6866248b41
Update test/test-utils/matchers/to-have-positional-tag.ts
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-08-05 00:05:04 -04:00
Bertie690
9298ff8282
Update test/test-utils/matchers/to-have-positional-tag.ts
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-08-05 00:04:55 -04:00
Bertie690
7e7ca6b3fa
Update test/test-utils/matchers/to-have-positional-tag.ts
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-08-05 00:04:39 -04:00
Bertie690
cd890025d1
Update test/@types/vitest.d.ts
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-08-05 00:04:31 -04:00
Bertie690
71801fe298
Update test/test-utils/matchers/to-have-types.ts
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-08-05 00:04:20 -04:00
Bertie690
e968063eaa Added toHavePositionalTag matcher 2025-08-03 17:21:24 -04:00
Bertie690
b98ff5ae90 Moar fixups to strings 2025-08-03 17:12:59 -04:00
Bertie690
c89accc673 Shuffled a few funcs around 2025-08-03 16:26:39 -04:00
Bertie690
df8d1dc8c7 More semantic changes 2025-08-03 16:15:32 -04:00
Bertie690
9455030fbe More improvements and minor fixes 2025-08-03 15:48:08 -04:00
Bertie690
dbea701d6d
Update test/test-utils/matchers/to-have-arena-tag.ts
Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
2025-08-03 13:27:26 -04:00
Bertie690
641f5f5b97 Removed accidental test file addition 2025-08-03 11:16:01 -04:00
Bertie690
f5154179b3 Fixed imports and stufff 2025-08-03 11:12:19 -04:00
Bertie690
49825a6729 [Test] Added toHaveArenaTagMatcher + fixed prior matchers 2025-08-02 23:52:07 -04:00
27 changed files with 786 additions and 472 deletions

12
.github/CODEOWNERS vendored
View File

@ -3,9 +3,6 @@
# everything (whole code-base) - Junior Devs # everything (whole code-base) - Junior Devs
* @pagefaultgames/junior-dev-team * @pagefaultgames/junior-dev-team
# github actions/templates etc. - Dev Leads
/.github @pagefaultgames/senior-dev-team
# Art Team # Art Team
/public/**/*.png @pagefaultgames/art-team /public/**/*.png @pagefaultgames/art-team
/public/**/*.json @pagefaultgames/art-team /public/**/*.json @pagefaultgames/art-team
@ -19,4 +16,11 @@
/public/audio @pagefaultgames/composer-team /public/audio @pagefaultgames/composer-team
# Balance Files; contain actual code logic and must also be owned by dev team # Balance Files; contain actual code logic and must also be owned by dev team
/src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team /src/data/balance @pagefaultgames/balance-team @pagefaultgames/junior-dev-team
/src/data/trainers @pagefaultgames/balance-team @pagefaultgames/junior-dev-team
# GitHub actions/templates etc. - Senior Devs
# Should be defined last in the file to make sure these always override all other definitions
/.github @pagefaultgames/senior-dev-team
package.json @pagefaultgames/senior-dev-team
pnpm-lock.yaml @pagefaultgames/senior-dev-team

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "rocket_boss_giovanni_1.png",
"format": "RGBA8888",
"size": {
"w": 79,
"h": 79
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 39,
"h": 79
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 39,
"h": 79
},
"frame": {
"x": 0,
"y": 0,
"w": 39,
"h": 79
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:d6c5e1804414106d43a7c46f83468d39:1f3f7898a58950988acac6ee7167e012:5f742cbdaafcd5ae864f18ec2af7512a$"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

View File

@ -11,7 +11,7 @@ import { BooleanHolder, toDmgValue } from "#utils/common";
* These are the moves assigned to a {@linkcode Pokemon} object. * These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID. * It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks things like * Compared to {@linkcode Move}, this class also tracks things like
* PP Ups recieved, PP used, etc. * PP Ups received, PP used, etc.
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented. * @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID. * @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move. * @see {@linkcode usePp} - removes a point of PP from the move.

View File

@ -223,9 +223,8 @@ export class TrainerConfig {
case TrainerType.LARRY_ELITE: case TrainerType.LARRY_ELITE:
trainerType = TrainerType.LARRY; trainerType = TrainerType.LARRY;
break; break;
case TrainerType.ROCKET_BOSS_GIOVANNI_1:
case TrainerType.ROCKET_BOSS_GIOVANNI_2: case TrainerType.ROCKET_BOSS_GIOVANNI_2:
trainerType = TrainerType.GIOVANNI; trainerType = TrainerType.ROCKET_BOSS_GIOVANNI_1;
break; break;
case TrainerType.MAXIE_2: case TrainerType.MAXIE_2:
trainerType = TrainerType.MAXIE; trainerType = TrainerType.MAXIE;
@ -895,7 +894,7 @@ export class TrainerConfig {
/** /**
* Helper function to check if a specialty type is set * Helper function to check if a specialty type is set
* @returns true if specialtyType is defined and not Type.UNKNOWN * @returns `true` if `specialtyType` is defined and not {@link PokemonType.UNKNOWN}
*/ */
hasSpecialtyType(): boolean { hasSpecialtyType(): boolean {
return !isNullOrUndefined(this.specialtyType) && this.specialtyType !== PokemonType.UNKNOWN; return !isNullOrUndefined(this.specialtyType) && this.specialtyType !== PokemonType.UNKNOWN;

View File

@ -1,624 +1,624 @@
export enum AbilityId { export enum AbilityId {
/**{@link https://bulbapedia.bulbagarden.net/wiki/None_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/None_(ability) | Source} */
NONE, NONE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Stench_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Stench_(ability) | Source} */
STENCH, STENCH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Drizzle_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Drizzle_(ability) | Source} */
DRIZZLE, DRIZZLE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Speed_Boost_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Speed_Boost_(ability) | Source} */
SPEED_BOOST, SPEED_BOOST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Battle_Armor_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Battle_Armor_(ability) | Source} */
BATTLE_ARMOR, BATTLE_ARMOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sturdy_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sturdy_(ability) | Source} */
STURDY, STURDY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Damp_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Damp_(ability) | Source} */
DAMP, DAMP,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Limber_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Limber_(ability) | Source} */
LIMBER, LIMBER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Veil_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Veil_(ability) | Source} */
SAND_VEIL, SAND_VEIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Static_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Static_(ability) | Source} */
STATIC, STATIC,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Volt_Absorb_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Volt_Absorb_(ability) | Source} */
VOLT_ABSORB, VOLT_ABSORB,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Absorb_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Absorb_(ability) | Source} */
WATER_ABSORB, WATER_ABSORB,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Oblivious_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Oblivious_(ability) | Source} */
OBLIVIOUS, OBLIVIOUS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Cloud_Nine_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Cloud_Nine_(ability) | Source} */
CLOUD_NINE, CLOUD_NINE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Compound_Eyes_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Compound_Eyes_(ability) | Source} */
COMPOUND_EYES, COMPOUND_EYES,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Insomnia_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Insomnia_(ability) | Source} */
INSOMNIA, INSOMNIA,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Color_Change_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Color_Change_(ability) | Source} */
COLOR_CHANGE, COLOR_CHANGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Immunity_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Immunity_(ability) | Source} */
IMMUNITY, IMMUNITY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Flash_Fire_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Flash_Fire_(ability) | Source} */
FLASH_FIRE, FLASH_FIRE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Shield_Dust_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Shield_Dust_(ability) | Source} */
SHIELD_DUST, SHIELD_DUST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Own_Tempo_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Own_Tempo_(ability) | Source} */
OWN_TEMPO, OWN_TEMPO,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Suction_Cups_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Suction_Cups_(ability) | Source} */
SUCTION_CUPS, SUCTION_CUPS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Intimidate_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Intimidate_(ability) | Source} */
INTIMIDATE, INTIMIDATE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Tag_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Tag_(ability) | Source} */
SHADOW_TAG, SHADOW_TAG,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Rough_Skin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Rough_Skin_(ability) | Source} */
ROUGH_SKIN, ROUGH_SKIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Guard_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Guard_(ability) | Source} */
WONDER_GUARD, WONDER_GUARD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Levitate_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Levitate_(ability) | Source} */
LEVITATE, LEVITATE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Effect_Spore_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Effect_Spore_(ability) | Source} */
EFFECT_SPORE, EFFECT_SPORE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Synchronize_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Synchronize_(ability) | Source} */
SYNCHRONIZE, SYNCHRONIZE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Clear_Body_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Clear_Body_(ability) | Source} */
CLEAR_BODY, CLEAR_BODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Natural_Cure_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Natural_Cure_(ability) | Source} */
NATURAL_CURE, NATURAL_CURE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Lightning_Rod_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Lightning_Rod_(ability) | Source} */
LIGHTNING_ROD, LIGHTNING_ROD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Serene_Grace_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Serene_Grace_(ability) | Source} */
SERENE_GRACE, SERENE_GRACE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Swift_Swim_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Swift_Swim_(ability) | Source} */
SWIFT_SWIM, SWIFT_SWIM,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Chlorophyll_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Chlorophyll_(ability) | Source} */
CHLOROPHYLL, CHLOROPHYLL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Illuminate_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Illuminate_(ability) | Source} */
ILLUMINATE, ILLUMINATE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Trace_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Trace_(ability) | Source} */
TRACE, TRACE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Huge_Power_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Huge_Power_(ability) | Source} */
HUGE_POWER, HUGE_POWER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Point_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Point_(ability) | Source} */
POISON_POINT, POISON_POINT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Inner_Focus_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Inner_Focus_(ability) | Source} */
INNER_FOCUS, INNER_FOCUS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Magma_Armor_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Magma_Armor_(ability) | Source} */
MAGMA_ARMOR, MAGMA_ARMOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Veil_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Veil_(ability) | Source} */
WATER_VEIL, WATER_VEIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Magnet_Pull_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Magnet_Pull_(ability) | Source} */
MAGNET_PULL, MAGNET_PULL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Soundproof_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Soundproof_(ability) | Source} */
SOUNDPROOF, SOUNDPROOF,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Rain_Dish_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Rain_Dish_(ability) | Source} */
RAIN_DISH, RAIN_DISH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Stream_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Stream_(ability) | Source} */
SAND_STREAM, SAND_STREAM,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Pressure_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Pressure_(ability) | Source} */
PRESSURE, PRESSURE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Thick_Fat_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Thick_Fat_(ability) | Source} */
THICK_FAT, THICK_FAT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Early_Bird_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Early_Bird_(ability) | Source} */
EARLY_BIRD, EARLY_BIRD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Flame_Body_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Flame_Body_(ability) | Source} */
FLAME_BODY, FLAME_BODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Run_Away_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Run_Away_(ability) | Source} */
RUN_AWAY, RUN_AWAY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Keen_Eye_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Keen_Eye_(ability) | Source} */
KEEN_EYE, KEEN_EYE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Hyper_Cutter_(ability) | Source} */
HYPER_CUTTER, HYPER_CUTTER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Pickup_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Pickup_(ability) | Source} */
PICKUP, PICKUP,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Truant_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Truant_(ability) | Source} */
TRUANT, TRUANT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Hustle_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Hustle_(ability) | Source} */
HUSTLE, HUSTLE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Cute_Charm_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Cute_Charm_(ability) | Source} */
CUTE_CHARM, CUTE_CHARM,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Plus_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Plus_(ability) | Source} */
PLUS, PLUS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Minus_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Minus_(ability) | Source} */
MINUS, MINUS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Forecast_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Forecast_(ability) | Source} */
FORECAST, FORECAST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sticky_Hold_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sticky_Hold_(ability) | Source} */
STICKY_HOLD, STICKY_HOLD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Shed_Skin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Shed_Skin_(ability) | Source} */
SHED_SKIN, SHED_SKIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Guts_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Guts_(ability) | Source} */
GUTS, GUTS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Marvel_Scale_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Marvel_Scale_(ability) | Source} */
MARVEL_SCALE, MARVEL_SCALE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(ability) | Source} */
LIQUID_OOZE, LIQUID_OOZE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Overgrow_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Overgrow_(ability) | Source} */
OVERGROW, OVERGROW,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Blaze_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Blaze_(ability) | Source} */
BLAZE, BLAZE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Torrent_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Torrent_(ability) | Source} */
TORRENT, TORRENT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Swarm_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Swarm_(ability) | Source} */
SWARM, SWARM,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Rock_Head_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Rock_Head_(ability) | Source} */
ROCK_HEAD, ROCK_HEAD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Drought_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Drought_(ability) | Source} */
DROUGHT, DROUGHT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Arena_Trap_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Arena_Trap_(ability) | Source} */
ARENA_TRAP, ARENA_TRAP,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Vital_Spirit_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Vital_Spirit_(ability) | Source} */
VITAL_SPIRIT, VITAL_SPIRIT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/White_Smoke_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/White_Smoke_(ability) | Source} */
WHITE_SMOKE, WHITE_SMOKE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Pure_Power_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Pure_Power_(ability) | Source} */
PURE_POWER, PURE_POWER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Shell_Armor_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Shell_Armor_(ability) | Source} */
SHELL_ARMOR, SHELL_ARMOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Air_Lock_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Air_Lock_(ability) | Source} */
AIR_LOCK, AIR_LOCK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Tangled_Feet_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Tangled_Feet_(ability) | Source} */
TANGLED_FEET, TANGLED_FEET,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Motor_Drive_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Motor_Drive_(ability) | Source} */
MOTOR_DRIVE, MOTOR_DRIVE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Rivalry_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Rivalry_(ability) | Source} */
RIVALRY, RIVALRY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Steadfast_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Steadfast_(ability) | Source} */
STEADFAST, STEADFAST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Snow_Cloak_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Snow_Cloak_(ability) | Source} */
SNOW_CLOAK, SNOW_CLOAK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Gluttony_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Gluttony_(ability) | Source} */
GLUTTONY, GLUTTONY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Anger_Point_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Anger_Point_(ability) | Source} */
ANGER_POINT, ANGER_POINT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Unburden_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Unburden_(ability) | Source} */
UNBURDEN, UNBURDEN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Heatproof_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Heatproof_(ability) | Source} */
HEATPROOF, HEATPROOF,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Simple_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Simple_(ability) | Source} */
SIMPLE, SIMPLE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Dry_Skin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Dry_Skin_(ability) | Source} */
DRY_SKIN, DRY_SKIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Download_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Download_(ability) | Source} */
DOWNLOAD, DOWNLOAD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Iron_Fist_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Iron_Fist_(ability) | Source} */
IRON_FIST, IRON_FIST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Heal_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Heal_(ability) | Source} */
POISON_HEAL, POISON_HEAL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Adaptability_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Adaptability_(ability) | Source} */
ADAPTABILITY, ADAPTABILITY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Skill_Link_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Skill_Link_(ability) | Source} */
SKILL_LINK, SKILL_LINK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Hydration_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Hydration_(ability) | Source} */
HYDRATION, HYDRATION,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Solar_Power_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Solar_Power_(ability) | Source} */
SOLAR_POWER, SOLAR_POWER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Quick_Feet_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Feet_(ability) | Source} */
QUICK_FEET, QUICK_FEET,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Normalize_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Normalize_(ability) | Source} */
NORMALIZE, NORMALIZE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sniper_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sniper_(ability) | Source} */
SNIPER, SNIPER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Magic_Guard_(ability) | Source} */
MAGIC_GUARD, MAGIC_GUARD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/No_Guard_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/No_Guard_(ability) | Source} */
NO_GUARD, NO_GUARD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Stall_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Stall_(ability) | Source} */
STALL, STALL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Technician_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Technician_(ability) | Source} */
TECHNICIAN, TECHNICIAN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Leaf_Guard_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Leaf_Guard_(ability) | Source} */
LEAF_GUARD, LEAF_GUARD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Klutz_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Klutz_(ability) | Source} */
KLUTZ, KLUTZ,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Mold_Breaker_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Mold_Breaker_(ability) | Source} */
MOLD_BREAKER, MOLD_BREAKER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Super_Luck_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Super_Luck_(ability) | Source} */
SUPER_LUCK, SUPER_LUCK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Aftermath_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Aftermath_(ability) | Source} */
AFTERMATH, AFTERMATH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Anticipation_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Anticipation_(ability) | Source} */
ANTICIPATION, ANTICIPATION,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Forewarn_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Forewarn_(ability) | Source} */
FOREWARN, FOREWARN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Unaware_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Unaware_(ability) | Source} */
UNAWARE, UNAWARE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Tinted_Lens_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Tinted_Lens_(ability) | Source} */
TINTED_LENS, TINTED_LENS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Filter_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Filter_(ability) | Source} */
FILTER, FILTER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Slow_Start_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Slow_Start_(ability) | Source} */
SLOW_START, SLOW_START,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Scrappy_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Scrappy_(ability) | Source} */
SCRAPPY, SCRAPPY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Storm_Drain_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Storm_Drain_(ability) | Source} */
STORM_DRAIN, STORM_DRAIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Ice_Body_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Ice_Body_(ability) | Source} */
ICE_BODY, ICE_BODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Solid_Rock_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Solid_Rock_(ability) | Source} */
SOLID_ROCK, SOLID_ROCK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Snow_Warning_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Snow_Warning_(ability) | Source} */
SNOW_WARNING, SNOW_WARNING,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Honey_Gather_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Honey_Gather_(ability) | Source} */
HONEY_GATHER, HONEY_GATHER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Frisk_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Frisk_(ability) | Source} */
FRISK, FRISK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Reckless_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Reckless_(ability) | Source} */
RECKLESS, RECKLESS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Multitype_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Multitype_(ability) | Source} */
MULTITYPE, MULTITYPE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Flower_Gift_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Flower_Gift_(ability) | Source} */
FLOWER_GIFT, FLOWER_GIFT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Bad_Dreams_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Bad_Dreams_(ability) | Source} */
BAD_DREAMS, BAD_DREAMS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Pickpocket_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Pickpocket_(ability) | Source} */
PICKPOCKET, PICKPOCKET,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sheer_Force_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sheer_Force_(ability) | Source} */
SHEER_FORCE, SHEER_FORCE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Contrary_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Contrary_(ability) | Source} */
CONTRARY, CONTRARY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Unnerve_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Unnerve_(ability) | Source} */
UNNERVE, UNNERVE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Defiant_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Defiant_(ability) | Source} */
DEFIANT, DEFIANT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Defeatist_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Defeatist_(ability) | Source} */
DEFEATIST, DEFEATIST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Cursed_Body_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Cursed_Body_(ability) | Source} */
CURSED_BODY, CURSED_BODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Healer_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Healer_(ability) | Source} */
HEALER, HEALER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Friend_Guard_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Friend_Guard_(ability) | Source} */
FRIEND_GUARD, FRIEND_GUARD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Weak_Armor_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Weak_Armor_(ability) | Source} */
WEAK_ARMOR, WEAK_ARMOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Heavy_Metal_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Heavy_Metal_(ability) | Source} */
HEAVY_METAL, HEAVY_METAL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Light_Metal_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Light_Metal_(ability) | Source} */
LIGHT_METAL, LIGHT_METAL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Multiscale_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Multiscale_(ability) | Source} */
MULTISCALE, MULTISCALE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Boost_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Boost_(ability) | Source} */
TOXIC_BOOST, TOXIC_BOOST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Flare_Boost_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Flare_Boost_(ability) | Source} */
FLARE_BOOST, FLARE_BOOST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Harvest_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Harvest_(ability) | Source} */
HARVEST, HARVEST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Telepathy_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Telepathy_(ability) | Source} */
TELEPATHY, TELEPATHY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Moody_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Moody_(ability) | Source} */
MOODY, MOODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Overcoat_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Overcoat_(ability) | Source} */
OVERCOAT, OVERCOAT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Touch_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Touch_(ability) | Source} */
POISON_TOUCH, POISON_TOUCH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Regenerator_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Regenerator_(ability) | Source} */
REGENERATOR, REGENERATOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Big_Pecks_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Big_Pecks_(ability) | Source} */
BIG_PECKS, BIG_PECKS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Rush_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Rush_(ability) | Source} */
SAND_RUSH, SAND_RUSH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Skin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Wonder_Skin_(ability) | Source} */
WONDER_SKIN, WONDER_SKIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Analytic_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Analytic_(ability) | Source} */
ANALYTIC, ANALYTIC,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Illusion_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Illusion_(ability) | Source} */
ILLUSION, ILLUSION,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Imposter_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Imposter_(ability) | Source} */
IMPOSTER, IMPOSTER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Infiltrator_(ability) | Source} */
INFILTRATOR, INFILTRATOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Mummy_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Mummy_(ability) | Source} */
MUMMY, MUMMY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Moxie_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Moxie_(ability) | Source} */
MOXIE, MOXIE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Justified_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Justified_(ability) | Source} */
JUSTIFIED, JUSTIFIED,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Rattled_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Rattled_(ability) | Source} */
RATTLED, RATTLED,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Magic_Bounce_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Magic_Bounce_(ability) | Source} */
MAGIC_BOUNCE, MAGIC_BOUNCE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sap_Sipper_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sap_Sipper_(ability) | Source} */
SAP_SIPPER, SAP_SIPPER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Prankster_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Prankster_(ability) | Source} */
PRANKSTER, PRANKSTER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Force_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Force_(ability) | Source} */
SAND_FORCE, SAND_FORCE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Iron_Barbs_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Iron_Barbs_(ability) | Source} */
IRON_BARBS, IRON_BARBS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Zen_Mode_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Zen_Mode_(ability) | Source} */
ZEN_MODE, ZEN_MODE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Victory_Star_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Victory_Star_(ability) | Source} */
VICTORY_STAR, VICTORY_STAR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Turboblaze_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Turboblaze_(ability) | Source} */
TURBOBLAZE, TURBOBLAZE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Teravolt_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Teravolt_(ability) | Source} */
TERAVOLT, TERAVOLT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Aroma_Veil_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Aroma_Veil_(ability) | Source} */
AROMA_VEIL, AROMA_VEIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Flower_Veil_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Flower_Veil_(ability) | Source} */
FLOWER_VEIL, FLOWER_VEIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Cheek_Pouch_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Cheek_Pouch_(ability) | Source} */
CHEEK_POUCH, CHEEK_POUCH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Protean_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Protean_(ability) | Source} */
PROTEAN, PROTEAN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Fur_Coat_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Fur_Coat_(ability) | Source} */
FUR_COAT, FUR_COAT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Magician_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Magician_(ability) | Source} */
MAGICIAN, MAGICIAN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Bulletproof_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Bulletproof_(ability) | Source} */
BULLETPROOF, BULLETPROOF,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Competitive_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Competitive_(ability) | Source} */
COMPETITIVE, COMPETITIVE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Strong_Jaw_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Strong_Jaw_(ability) | Source} */
STRONG_JAW, STRONG_JAW,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Refrigerate_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Refrigerate_(ability) | Source} */
REFRIGERATE, REFRIGERATE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sweet_Veil_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sweet_Veil_(ability) | Source} */
SWEET_VEIL, SWEET_VEIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Stance_Change_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Stance_Change_(ability) | Source} */
STANCE_CHANGE, STANCE_CHANGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Gale_Wings_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Gale_Wings_(ability) | Source} */
GALE_WINGS, GALE_WINGS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Mega_Launcher_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Mega_Launcher_(ability) | Source} */
MEGA_LAUNCHER, MEGA_LAUNCHER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Grass_Pelt_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Grass_Pelt_(ability) | Source} */
GRASS_PELT, GRASS_PELT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Symbiosis_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Symbiosis_(ability) | Source} */
SYMBIOSIS, SYMBIOSIS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Tough_Claws_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Tough_Claws_(ability) | Source} */
TOUGH_CLAWS, TOUGH_CLAWS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Pixilate_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Pixilate_(ability) | Source} */
PIXILATE, PIXILATE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Gooey_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Gooey_(ability) | Source} */
GOOEY, GOOEY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Aerilate_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Aerilate_(ability) | Source} */
AERILATE, AERILATE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Parental_Bond_(ability) | Source} */
PARENTAL_BOND, PARENTAL_BOND,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Dark_Aura_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Dark_Aura_(ability) | Source} */
DARK_AURA, DARK_AURA,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Fairy_Aura_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Fairy_Aura_(ability) | Source} */
FAIRY_AURA, FAIRY_AURA,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Aura_Break_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Aura_Break_(ability) | Source} */
AURA_BREAK, AURA_BREAK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Primordial_Sea_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Primordial_Sea_(ability) | Source} */
PRIMORDIAL_SEA, PRIMORDIAL_SEA,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Desolate_Land_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Desolate_Land_(ability) | Source} */
DESOLATE_LAND, DESOLATE_LAND,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Delta_Stream_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Delta_Stream_(ability) | Source} */
DELTA_STREAM, DELTA_STREAM,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Stamina_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Stamina_(ability) | Source} */
STAMINA, STAMINA,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Wimp_Out_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Wimp_Out_(ability) | Source} */
WIMP_OUT, WIMP_OUT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Emergency_Exit_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Emergency_Exit_(ability) | Source} */
EMERGENCY_EXIT, EMERGENCY_EXIT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Compaction_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Compaction_(ability) | Source} */
WATER_COMPACTION, WATER_COMPACTION,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Merciless_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Merciless_(ability) | Source} */
MERCILESS, MERCILESS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Shields_Down_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Shields_Down_(ability) | Source} */
SHIELDS_DOWN, SHIELDS_DOWN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Stakeout_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Stakeout_(ability) | Source} */
STAKEOUT, STAKEOUT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Water_Bubble_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Water_Bubble_(ability) | Source} */
WATER_BUBBLE, WATER_BUBBLE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Steelworker_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Steelworker_(ability) | Source} */
STEELWORKER, STEELWORKER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Berserk_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Berserk_(ability) | Source} */
BERSERK, BERSERK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Slush_Rush_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Slush_Rush_(ability) | Source} */
SLUSH_RUSH, SLUSH_RUSH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Long_Reach_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Long_Reach_(ability) | Source} */
LONG_REACH, LONG_REACH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Voice_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Liquid_Voice_(ability) | Source} */
LIQUID_VOICE, LIQUID_VOICE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Triage_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Triage_(ability) | Source} */
TRIAGE, TRIAGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Galvanize_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Galvanize_(ability) | Source} */
GALVANIZE, GALVANIZE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Surge_Surfer_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Surge_Surfer_(ability) | Source} */
SURGE_SURFER, SURGE_SURFER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Schooling_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Schooling_(ability) | Source} */
SCHOOLING, SCHOOLING,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Disguise_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Disguise_(ability) | Source} */
DISGUISE, DISGUISE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Battle_Bond_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Battle_Bond_(ability) | Source} */
BATTLE_BOND, BATTLE_BOND,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Power_Construct_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Power_Construct_(ability) | Source} */
POWER_CONSTRUCT, POWER_CONSTRUCT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Corrosion_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Corrosion_(ability) | Source} */
CORROSION, CORROSION,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Comatose_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Comatose_(ability) | Source} */
COMATOSE, COMATOSE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Queenly_Majesty_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Queenly_Majesty_(ability) | Source} */
QUEENLY_MAJESTY, QUEENLY_MAJESTY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Innards_Out_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Innards_Out_(ability) | Source} */
INNARDS_OUT, INNARDS_OUT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Dancer_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Dancer_(ability) | Source} */
DANCER, DANCER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Battery_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Battery_(ability) | Source} */
BATTERY, BATTERY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Fluffy_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Fluffy_(ability) | Source} */
FLUFFY, FLUFFY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Dazzling_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Dazzling_(ability) | Source} */
DAZZLING, DAZZLING,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Soul_Heart_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Soul_Heart_(ability) | Source} */
SOUL_HEART, SOUL_HEART,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Tangling_Hair_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Tangling_Hair_(ability) | Source} */
TANGLING_HAIR, TANGLING_HAIR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Receiver_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Receiver_(ability) | Source} */
RECEIVER, RECEIVER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Power_Of_Alchemy_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Power_Of_Alchemy_(ability) | Source} */
POWER_OF_ALCHEMY, POWER_OF_ALCHEMY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Beast_Boost_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Beast_Boost_(ability) | Source} */
BEAST_BOOST, BEAST_BOOST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Rks_System_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Rks_System_(ability) | Source} */
RKS_SYSTEM, RKS_SYSTEM,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Electric_Surge_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Electric_Surge_(ability) | Source} */
ELECTRIC_SURGE, ELECTRIC_SURGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Psychic_Surge_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Psychic_Surge_(ability) | Source} */
PSYCHIC_SURGE, PSYCHIC_SURGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Misty_Surge_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Misty_Surge_(ability) | Source} */
MISTY_SURGE, MISTY_SURGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Grassy_Surge_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Grassy_Surge_(ability) | Source} */
GRASSY_SURGE, GRASSY_SURGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Full_Metal_Body_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Full_Metal_Body_(ability) | Source} */
FULL_METAL_BODY, FULL_METAL_BODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Shield_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Shadow_Shield_(ability) | Source} */
SHADOW_SHIELD, SHADOW_SHIELD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Prism_Armor_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Prism_Armor_(ability) | Source} */
PRISM_ARMOR, PRISM_ARMOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Neuroforce_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Neuroforce_(ability) | Source} */
NEUROFORCE, NEUROFORCE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Intrepid_Sword_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Intrepid_Sword_(ability) | Source} */
INTREPID_SWORD, INTREPID_SWORD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Dauntless_Shield_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Dauntless_Shield_(ability) | Source} */
DAUNTLESS_SHIELD, DAUNTLESS_SHIELD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Libero_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Libero_(ability) | Source} */
LIBERO, LIBERO,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Ball_Fetch_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Ball_Fetch_(ability) | Source} */
BALL_FETCH, BALL_FETCH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Cotton_Down_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Cotton_Down_(ability) | Source} */
COTTON_DOWN, COTTON_DOWN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Propeller_Tail_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Propeller_Tail_(ability) | Source} */
PROPELLER_TAIL, PROPELLER_TAIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Mirror_Armor_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Mirror_Armor_(ability) | Source} */
MIRROR_ARMOR, MIRROR_ARMOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Gulp_Missile_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Gulp_Missile_(ability) | Source} */
GULP_MISSILE, GULP_MISSILE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Stalwart_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Stalwart_(ability) | Source} */
STALWART, STALWART,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Steam_Engine_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Steam_Engine_(ability) | Source} */
STEAM_ENGINE, STEAM_ENGINE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Punk_Rock_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Punk_Rock_(ability) | Source} */
PUNK_ROCK, PUNK_ROCK,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sand_Spit_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sand_Spit_(ability) | Source} */
SAND_SPIT, SAND_SPIT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Ice_Scales_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Ice_Scales_(ability) | Source} */
ICE_SCALES, ICE_SCALES,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Ripen_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Ripen_(ability) | Source} */
RIPEN, RIPEN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Ice_Face_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Ice_Face_(ability) | Source} */
ICE_FACE, ICE_FACE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Power_Spot_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Power_Spot_(ability) | Source} */
POWER_SPOT, POWER_SPOT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Mimicry_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Mimicry_(ability) | Source} */
MIMICRY, MIMICRY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Screen_Cleaner_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Screen_Cleaner_(ability) | Source} */
SCREEN_CLEANER, SCREEN_CLEANER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Steely_Spirit_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Steely_Spirit_(ability) | Source} */
STEELY_SPIRIT, STEELY_SPIRIT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Perish_Body_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Perish_Body_(ability) | Source} */
PERISH_BODY, PERISH_BODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Wandering_Spirit_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Wandering_Spirit_(ability) | Source} */
WANDERING_SPIRIT, WANDERING_SPIRIT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Gorilla_Tactics_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Gorilla_Tactics_(ability) | Source} */
GORILLA_TACTICS, GORILLA_TACTICS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(ability) | Source} */
NEUTRALIZING_GAS, NEUTRALIZING_GAS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Pastel_Veil_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Pastel_Veil_(ability) | Source} */
PASTEL_VEIL, PASTEL_VEIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Hunger_Switch_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Hunger_Switch_(ability) | Source} */
HUNGER_SWITCH, HUNGER_SWITCH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Quick_Draw_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Quick_Draw_(ability) | Source} */
QUICK_DRAW, QUICK_DRAW,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Unseen_Fist_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Unseen_Fist_(ability) | Source} */
UNSEEN_FIST, UNSEEN_FIST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Curious_Medicine_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Curious_Medicine_(ability) | Source} */
CURIOUS_MEDICINE, CURIOUS_MEDICINE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Transistor_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Transistor_(ability) | Source} */
TRANSISTOR, TRANSISTOR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Dragons_Maw_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Dragons_Maw_(ability) | Source} */
DRAGONS_MAW, DRAGONS_MAW,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Chilling_Neigh_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Chilling_Neigh_(ability) | Source} */
CHILLING_NEIGH, CHILLING_NEIGH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Grim_Neigh_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Grim_Neigh_(ability) | Source} */
GRIM_NEIGH, GRIM_NEIGH,
/**{@link https://bulbapedia.bulbagarden.net/wiki/As_One_Glastrier_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/As_One_Glastrier_(ability) | Source} */
AS_ONE_GLASTRIER, AS_ONE_GLASTRIER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/As_One_Spectrier_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/As_One_Spectrier_(ability) | Source} */
AS_ONE_SPECTRIER, AS_ONE_SPECTRIER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Lingering_Aroma_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Lingering_Aroma_(ability) | Source} */
LINGERING_AROMA, LINGERING_AROMA,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Seed_Sower_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Seed_Sower_(ability) | Source} */
SEED_SOWER, SEED_SOWER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Thermal_Exchange_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Thermal_Exchange_(ability) | Source} */
THERMAL_EXCHANGE, THERMAL_EXCHANGE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Anger_Shell_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Anger_Shell_(ability) | Source} */
ANGER_SHELL, ANGER_SHELL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Purifying_Salt_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Purifying_Salt_(ability) | Source} */
PURIFYING_SALT, PURIFYING_SALT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Well_Baked_Body_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Well_Baked_Body_(ability) | Source} */
WELL_BAKED_BODY, WELL_BAKED_BODY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Wind_Rider_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Wind_Rider_(ability) | Source} */
WIND_RIDER, WIND_RIDER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Guard_Dog_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Guard_Dog_(ability) | Source} */
GUARD_DOG, GUARD_DOG,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Rocky_Payload_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Rocky_Payload_(ability) | Source} */
ROCKY_PAYLOAD, ROCKY_PAYLOAD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Wind_Power_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Wind_Power_(ability) | Source} */
WIND_POWER, WIND_POWER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Zero_To_Hero_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Zero_To_Hero_(ability) | Source} */
ZERO_TO_HERO, ZERO_TO_HERO,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Commander_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(ability) | Source} */
COMMANDER, COMMANDER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Electromorphosis_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Electromorphosis_(ability) | Source} */
ELECTROMORPHOSIS, ELECTROMORPHOSIS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Protosynthesis_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Protosynthesis_(ability) | Source} */
PROTOSYNTHESIS, PROTOSYNTHESIS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Quark_Drive_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Quark_Drive_(ability) | Source} */
QUARK_DRIVE, QUARK_DRIVE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Good_As_Gold_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Good_As_Gold_(ability) | Source} */
GOOD_AS_GOLD, GOOD_AS_GOLD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Vessel_Of_Ruin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Vessel_Of_Ruin_(ability) | Source} */
VESSEL_OF_RUIN, VESSEL_OF_RUIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sword_Of_Ruin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sword_Of_Ruin_(ability) | Source} */
SWORD_OF_RUIN, SWORD_OF_RUIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Tablets_Of_Ruin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Tablets_Of_Ruin_(ability) | Source} */
TABLETS_OF_RUIN, TABLETS_OF_RUIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Beads_Of_Ruin_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Beads_Of_Ruin_(ability) | Source} */
BEADS_OF_RUIN, BEADS_OF_RUIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Orichalcum_Pulse_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Orichalcum_Pulse_(ability) | Source} */
ORICHALCUM_PULSE, ORICHALCUM_PULSE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Hadron_Engine_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Hadron_Engine_(ability) | Source} */
HADRON_ENGINE, HADRON_ENGINE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Opportunist_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Opportunist_(ability) | Source} */
OPPORTUNIST, OPPORTUNIST,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Cud_Chew_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Cud_Chew_(ability) | Source} */
CUD_CHEW, CUD_CHEW,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Sharpness_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Sharpness_(ability) | Source} */
SHARPNESS, SHARPNESS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Supreme_Overlord_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Supreme_Overlord_(ability) | Source} */
SUPREME_OVERLORD, SUPREME_OVERLORD,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Costar_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Costar_(ability) | Source} */
COSTAR, COSTAR,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Debris_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Debris_(ability) | Source} */
TOXIC_DEBRIS, TOXIC_DEBRIS,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Armor_Tail_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Armor_Tail_(ability) | Source} */
ARMOR_TAIL, ARMOR_TAIL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Earth_Eater_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Earth_Eater_(ability) | Source} */
EARTH_EATER, EARTH_EATER,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(ability) | Source} */
MYCELIUM_MIGHT, MYCELIUM_MIGHT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Minds_Eye_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Minds_Eye_(ability) | Source} */
MINDS_EYE, MINDS_EYE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Supersweet_Syrup_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Supersweet_Syrup_(ability) | Source} */
SUPERSWEET_SYRUP, SUPERSWEET_SYRUP,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Hospitality_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Hospitality_(ability) | Source} */
HOSPITALITY, HOSPITALITY,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Chain_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Toxic_Chain_(ability) | Source} */
TOXIC_CHAIN, TOXIC_CHAIN,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Teal_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Teal_(ability) | Source} */
EMBODY_ASPECT_TEAL, EMBODY_ASPECT_TEAL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Wellspring_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Wellspring_(ability) | Source} */
EMBODY_ASPECT_WELLSPRING, EMBODY_ASPECT_WELLSPRING,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Hearthflame_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Hearthflame_(ability) | Source} */
EMBODY_ASPECT_HEARTHFLAME, EMBODY_ASPECT_HEARTHFLAME,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Cornerstone_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Embody_Aspect_Cornerstone_(ability) | Source} */
EMBODY_ASPECT_CORNERSTONE, EMBODY_ASPECT_CORNERSTONE,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shift_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shift_(ability) | Source} */
TERA_SHIFT, TERA_SHIFT,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shell_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Tera_Shell_(ability) | Source} */
TERA_SHELL, TERA_SHELL,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Teraform_Zero_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Teraform_Zero_(ability) | Source} */
TERAFORM_ZERO, TERAFORM_ZERO,
/**{@link https://bulbapedia.bulbagarden.net/wiki/Poison_Puppeteer_(ability) | Source} */ /** {@link https://bulbapedia.bulbagarden.net/wiki/Poison_Puppeteer_(ability) | Source} */
POISON_PUPPETEER, POISON_PUPPETEER,
} }

View File

@ -0,0 +1,27 @@
import type { AtLeastOne, NonFunctionPropertiesRecursive as nonFunc } from "#types/type-helpers";
/**
* Helper type to admit an object containing the given properties
* _and_ at least 1 other non-function property.
* @example
* ```ts
* type foo = {
* qux: 1 | 2 | 3,
* bar: number,
* baz: string
* quux: () => void; // ignored!
* }
*
* type quxAndSomethingElse = OneOther<foo, "qux">
*
* const good1: quxAndSomethingElse = {qux: 1, bar: 3} // OK!
* const good2: quxAndSomethingElse = {qux: 2, baz: "4", bar: 12} // OK!
* const bad1: quxAndSomethingElse = {baz: "4", bar: 12} // Errors because `qux` is required
* const bad2: quxAndSomethingElse = {qux: 1} // Errors because at least 1 thing _other_ than `qux` is required
* ```
* @typeParam O - The object to source keys from
* @typeParam K - One or more of O's keys to render mandatory
*/
export type OneOther<O extends object, K extends keyof O> = AtLeastOne<Omit<nonFunc<O>, K>> & {
[key in K]: O[K];
};

View File

@ -1,23 +1,32 @@
import type { TerrainType } from "#app/data/terrain"; import type { TerrainType } from "#app/data/terrain";
import type { ArenaTag } from "#data/arena-tag";
import type { AbilityId } from "#enums/ability-id"; import type { AbilityId } from "#enums/ability-id";
import type { ArenaTagType } from "#enums/arena-tag-type";
import type { BattlerTagType } from "#enums/battler-tag-type"; import type { BattlerTagType } from "#enums/battler-tag-type";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import type { BattleStat, EffectiveStat, Stat } from "#enums/stat"; import type { BattleStat, EffectiveStat, Stat } from "#enums/stat";
import type { StatusEffect } from "#enums/status-effect"; import type { StatusEffect } from "#enums/status-effect";
import type { WeatherType } from "#enums/weather-type"; import type { WeatherType } from "#enums/weather-type";
import type { Arena } from "#field/arena";
import type { Pokemon } from "#field/pokemon"; import type { Pokemon } from "#field/pokemon";
import type { ToHaveEffectiveStatMatcherOptions } from "#test/test-utils/matchers/to-have-effective-stat"; import type { toHaveEffectiveStatOptions } from "#test/test-utils/matchers/to-have-effective-stat";
import type { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect"; import type { expectedStatusType } from "#test/test-utils/matchers/to-have-status-effect";
import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types"; import type { toHaveTypesOptions } from "#test/test-utils/matchers/to-have-types";
import type { TurnMove } from "#types/turn-move"; import type { TurnMove } from "#types/turn-move";
import type { AtLeastOne } from "#types/type-helpers"; import type { AtLeastOne } from "#types/type-helpers";
import type { toDmgValue } from "utils/common";
import type { expect } from "vitest"; import type { expect } from "vitest";
import type { PositionalTag } from "#data/positional-tags/positional-tag";
import { PositionalTagType } from "#enums/positional-tag-type";
import type Overrides from "#app/overrides"; import type Overrides from "#app/overrides";
import type { ArenaTagSide } from "#enums/arena-tag-side";
import type { PokemonMove } from "#moves/pokemon-move"; import type { PokemonMove } from "#moves/pokemon-move";
import { toHaveArenaTagOptions } from "#test/test-utils/matchers/to-have-arena-tag";
import { toHavePositionalTagOptions } from "#test/test-utils/matchers/to-have-positional-tag";
declare module "vitest" { declare module "vitest" {
interface Assertion { interface Assertion<T> {
/** /**
* Check whether an array contains EXACTLY the given items (in any order). * Check whether an array contains EXACTLY the given items (in any order).
* *
@ -27,45 +36,9 @@ declare module "vitest" {
* @param expected - The expected contents of the array, in any order * @param expected - The expected contents of the array, in any order
* @see {@linkcode expect.arrayContaining} * @see {@linkcode expect.arrayContaining}
*/ */
toEqualArrayUnsorted<E>(expected: E[]): void; toEqualArrayUnsorted(expected: T[]): void;
/** // #region Arena Matchers
* Check whether a {@linkcode Pokemon}'s current typing includes the given types.
*
* @param expected - The expected types (in any order)
* @param options - The options passed to the matcher
*/
toHaveTypes(expected: [PokemonType, ...PokemonType[]], options?: toHaveTypesOptions): void;
/**
* Matcher to check the contents of a {@linkcode Pokemon}'s move history.
*
* @param expectedValue - The expected value; can be a {@linkcode MoveId} or a partially filled {@linkcode TurnMove}
* containing the desired properties to check
* @param index - The index of the move history entry to check, in order from most recent to least recent.
* Default `0` (last used move)
* @see {@linkcode Pokemon.getLastXMoves}
*/
toHaveUsedMove(expected: MoveId | AtLeastOne<TurnMove>, index?: number): void;
/**
* Check whether a {@linkcode Pokemon}'s effective stat is as expected
* (checked after all stat value modifications).
*
* @param stat - The {@linkcode EffectiveStat} to check
* @param expectedValue - The expected value of {@linkcode stat}
* @param options - (Optional) The {@linkcode ToHaveEffectiveStatMatcherOptions}
* @remarks
* If you want to check the stat **before** modifiers are applied, use {@linkcode Pokemon.getStat} instead.
*/
toHaveEffectiveStat(stat: EffectiveStat, expectedValue: number, options?: ToHaveEffectiveStatMatcherOptions): void;
/**
* Check whether a {@linkcode Pokemon} has taken a specific amount of damage.
* @param expectedDamageTaken - The expected amount of damage taken
* @param roundDown - Whether to round down {@linkcode expectedDamageTaken} with {@linkcode toDmgValue}; default `true`
*/
toHaveTakenDamage(expectedDamageTaken: number, roundDown?: boolean): void;
/** /**
* Check whether the current {@linkcode WeatherType} is as expected. * Check whether the current {@linkcode WeatherType} is as expected.
@ -80,9 +53,60 @@ declare module "vitest" {
toHaveTerrain(expectedTerrainType: TerrainType): void; toHaveTerrain(expectedTerrainType: TerrainType): void;
/** /**
* Check whether a {@linkcode Pokemon} is at full HP. * Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}.
* @param expectedTag - A partially-filled {@linkcode ArenaTag} containing the desired properties
*/ */
toHaveFullHp(): void; toHaveArenaTag<A extends ArenaTagType>(expectedTag: toHaveArenaTagOptions<A>): void;
/**
* Check whether the current {@linkcode Arena} contains the given {@linkcode ArenaTag}.
* @param expectedType - The {@linkcode ArenaTagType} of the desired tag
* @param side - The {@linkcode ArenaTagSide | side(s) of the field} the tag should affect; default {@linkcode ArenaTagSide.BOTH}
*/
toHaveArenaTag(expectedType: ArenaTagType, side?: ArenaTagSide): void;
/**
* Check whether the current {@linkcode Arena} contains the given {@linkcode PositionalTag}.
* @param expectedTag - A partially-filled `PositionalTag` containing the desired properties
*/
toHavePositionalTag<P extends PositionalTagType>(expectedTag: toHavePositionalTagOptions<P>): void;
/**
* Check whether the current {@linkcode Arena} contains the given number of {@linkcode PositionalTag}s.
* @param expectedType - The {@linkcode PositionalTagType} of the desired tag
* @param count - The number of instances of {@linkcode expectedType} that should be active;
* defaults to `1` and must be within the range `[0, 4]`
*/
toHavePositionalTag(expectedType: PositionalTagType, count?: number): void;
// #endregion Arena Matchers
// #region Pokemon Matchers
/**
* Check whether a {@linkcode Pokemon}'s current typing includes the given types.
* @param expectedTypes - The expected {@linkcode PokemonType}s to check against; must have length `>0`
* @param options - The {@linkcode toHaveTypesOptions | options} passed to the matcher
*/
toHaveTypes(expectedTypes: PokemonType[], options?: toHaveTypesOptions): void;
/**
* Check whether a {@linkcode Pokemon} has used a move matching the given criteria.
* @param expectedMove - The {@linkcode MoveId} the Pokemon is expected to have used,
* or a partially filled {@linkcode TurnMove} containing the desired properties to check
* @param index - The index of the move history entry to check, in order from most recent to least recent; default `0`
* @see {@linkcode Pokemon.getLastXMoves}
*/
toHaveUsedMove(expectedMove: MoveId | AtLeastOne<TurnMove>, index?: number): void;
/**
* Check whether a {@linkcode Pokemon}'s effective stat is as expected
* (checked after all stat value modifications).
* @param stat - The {@linkcode EffectiveStat} to check
* @param expectedValue - The expected value of {@linkcode stat}
* @param options - The {@linkcode toHaveEffectiveStatOptions | options} passed to the matcher
* @remarks
* If you want to check the stat **before** modifiers are applied, use {@linkcode Pokemon.getStat} instead.
*/
toHaveEffectiveStat(stat: EffectiveStat, expectedValue: number, options?: toHaveEffectiveStatOptions): void;
/** /**
* Check whether a {@linkcode Pokemon} has a specific {@linkcode StatusEffect | non-volatile status effect}. * Check whether a {@linkcode Pokemon} has a specific {@linkcode StatusEffect | non-volatile status effect}.
@ -106,7 +130,7 @@ declare module "vitest" {
/** /**
* Check whether a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}. * Check whether a {@linkcode Pokemon} has applied a specific {@linkcode AbilityId}.
* @param expectedAbilityId - The expected {@linkcode AbilityId} * @param expectedAbilityId - The `AbilityId` to check for
*/ */
toHaveAbilityApplied(expectedAbilityId: AbilityId): void; toHaveAbilityApplied(expectedAbilityId: AbilityId): void;
@ -116,24 +140,36 @@ declare module "vitest" {
*/ */
toHaveHp(expectedHp: number): void; toHaveHp(expectedHp: number): void;
/**
* Check whether a {@linkcode Pokemon} has taken a specific amount of damage.
* @param expectedDamageTaken - The expected amount of damage taken
* @param roundDown - Whether to round down `expectedDamageTaken` with {@linkcode toDmgValue}; default `true`
*/
toHaveTakenDamage(expectedDamageTaken: number, roundDown?: boolean): void;
/** /**
* Check whether a {@linkcode Pokemon} is currently fainted (as determined by {@linkcode Pokemon.isFainted}). * Check whether a {@linkcode Pokemon} is currently fainted (as determined by {@linkcode Pokemon.isFainted}).
* @remarks * @remarks
* When checking whether an enemy wild Pokemon is fainted, one must reference it in a variable _before_ the fainting effect occurs * When checking whether an enemy wild Pokemon is fainted, one must store a reference to it in a variable _before_ the fainting effect occurs.
* as otherwise the Pokemon will be GC'ed and rendered `undefined`. * Otherwise, the Pokemon will be removed from the field and garbage collected.
*/ */
toHaveFainted(): void; toHaveFainted(): void;
/**
* Check whether a {@linkcode Pokemon} is at full HP.
*/
toHaveFullHp(): void;
/** /**
* Check whether a {@linkcode Pokemon} has consumed the given amount of PP for one of its moves. * Check whether a {@linkcode Pokemon} has consumed the given amount of PP for one of its moves.
* @param expectedValue - The {@linkcode MoveId} of the {@linkcode PokemonMove} that should have consumed PP * @param moveId - The `MoveId` corresponding to the {@linkcode PokemonMove} that should have consumed PP
* @param ppUsed - The numerical amount of PP that should have been consumed, * @param ppUsed - The numerical amount of PP that should have been consumed,
* or `all` to indicate the move should be _out_ of PP * or `all` to indicate the move should be _out_ of PP
* @remarks * @remarks
* If the Pokemon's moveset has been set via {@linkcode Overrides.MOVESET_OVERRIDE}/{@linkcode Overrides.OPP_MOVESET_OVERRIDE}, * If the Pokemon's moveset has been set via {@linkcode Overrides.MOVESET_OVERRIDE}/{@linkcode Overrides.OPP_MOVESET_OVERRIDE}
* does not contain {@linkcode expectedMove} * or does not contain exactly one copy of {@linkcode moveId}, this will fail the test.
* or contains the desired move more than once, this will fail the test.
*/ */
toHaveUsedPP(expectedMove: MoveId, ppUsed: number | "all"): void; toHaveUsedPP(moveId: MoveId, ppUsed: number | "all"): void;
// #endregion Pokemon Matchers
} }
} }

View File

@ -1,10 +1,12 @@
import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted"; import { toEqualArrayUnsorted } from "#test/test-utils/matchers/to-equal-array-unsorted";
import { toHaveAbilityApplied } from "#test/test-utils/matchers/to-have-ability-applied"; import { toHaveAbilityApplied } from "#test/test-utils/matchers/to-have-ability-applied";
import { toHaveArenaTag } from "#test/test-utils/matchers/to-have-arena-tag";
import { toHaveBattlerTag } from "#test/test-utils/matchers/to-have-battler-tag"; import { toHaveBattlerTag } from "#test/test-utils/matchers/to-have-battler-tag";
import { toHaveEffectiveStat } from "#test/test-utils/matchers/to-have-effective-stat"; import { toHaveEffectiveStat } from "#test/test-utils/matchers/to-have-effective-stat";
import { toHaveFainted } from "#test/test-utils/matchers/to-have-fainted"; import { toHaveFainted } from "#test/test-utils/matchers/to-have-fainted";
import { toHaveFullHp } from "#test/test-utils/matchers/to-have-full-hp"; import { toHaveFullHp } from "#test/test-utils/matchers/to-have-full-hp";
import { toHaveHp } from "#test/test-utils/matchers/to-have-hp"; import { toHaveHp } from "#test/test-utils/matchers/to-have-hp";
import { toHavePositionalTag } from "#test/test-utils/matchers/to-have-positional-tag";
import { toHaveStatStage } from "#test/test-utils/matchers/to-have-stat-stage"; import { toHaveStatStage } from "#test/test-utils/matchers/to-have-stat-stage";
import { toHaveStatusEffect } from "#test/test-utils/matchers/to-have-status-effect"; import { toHaveStatusEffect } from "#test/test-utils/matchers/to-have-status-effect";
import { toHaveTakenDamage } from "#test/test-utils/matchers/to-have-taken-damage"; import { toHaveTakenDamage } from "#test/test-utils/matchers/to-have-taken-damage";
@ -22,18 +24,20 @@ import { expect } from "vitest";
expect.extend({ expect.extend({
toEqualArrayUnsorted, toEqualArrayUnsorted,
toHaveWeather,
toHaveTerrain,
toHaveArenaTag,
toHavePositionalTag,
toHaveTypes, toHaveTypes,
toHaveUsedMove, toHaveUsedMove,
toHaveEffectiveStat, toHaveEffectiveStat,
toHaveTakenDamage,
toHaveWeather,
toHaveTerrain,
toHaveFullHp,
toHaveStatusEffect, toHaveStatusEffect,
toHaveStatStage, toHaveStatStage,
toHaveBattlerTag, toHaveBattlerTag,
toHaveAbilityApplied, toHaveAbilityApplied,
toHaveHp, toHaveHp,
toHaveTakenDamage,
toHaveFullHp,
toHaveFainted, toHaveFainted,
toHaveUsedPP, toHaveUsedPP,
}); });

View File

@ -39,15 +39,6 @@ describe("Move - Wish", () => {
.enemyLevel(100); .enemyLevel(100);
}); });
/**
* Expect that wish is active with the specified number of attacks.
* @param numAttacks - The number of wish instances that should be queued; default `1`
*/
function expectWishActive(numAttacks = 1) {
const wishes = game.scene.arena.positionalTagManager["tags"].filter(t => t.tagType === PositionalTagType.WISH);
expect(wishes).toHaveLength(numAttacks);
}
it("should heal the Pokemon in the current slot for 50% of the user's maximum HP", async () => { it("should heal the Pokemon in the current slot for 50% of the user's maximum HP", async () => {
await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]); await game.classicMode.startBattle([SpeciesId.ALOMOMOLA, SpeciesId.BLISSEY]);
@ -58,19 +49,19 @@ describe("Move - Wish", () => {
game.move.use(MoveId.WISH); game.move.use(MoveId.WISH);
await game.toNextTurn(); await game.toNextTurn();
expectWishActive(); expect(game).toHavePositionalTag(PositionalTagType.WISH);
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.toEndOfTurn(); await game.toEndOfTurn();
expectWishActive(0); expect(game).toHavePositionalTag(PositionalTagType.WISH, 0);
expect(game.textInterceptor.logs).toContain( expect(game.textInterceptor.logs).toContain(
i18next.t("arenaTag:wishTagOnAdd", { i18next.t("arenaTag:wishTagOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(alomomola), pokemonNameWithAffix: getPokemonNameWithAffix(alomomola),
}), }),
); );
expect(alomomola.hp).toBe(1); expect(alomomola).toHaveHp(1);
expect(blissey.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1); expect(blissey).toHaveHp(toDmgValue(alomomola.getMaxHp() / 2) + 1);
}); });
it("should work if the user has full HP, but not if it already has an active Wish", async () => { it("should work if the user has full HP, but not if it already has an active Wish", async () => {
@ -82,13 +73,13 @@ describe("Move - Wish", () => {
game.move.use(MoveId.WISH); game.move.use(MoveId.WISH);
await game.toNextTurn(); await game.toNextTurn();
expectWishActive(); expect(game).toHavePositionalTag(PositionalTagType.WISH);
game.move.use(MoveId.WISH); game.move.use(MoveId.WISH);
await game.toEndOfTurn(); await game.toEndOfTurn();
expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1); expect(alomomola.hp).toBe(toDmgValue(alomomola.getMaxHp() / 2) + 1);
expect(alomomola.getLastXMoves()[0].result).toBe(MoveResult.FAIL); expect(alomomola).toHaveUsedMove({ result: MoveResult.FAIL });
}); });
it("should function independently of Future Sight", async () => { it("should function independently of Future Sight", async () => {
@ -103,7 +94,8 @@ describe("Move - Wish", () => {
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]); await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.toNextTurn(); await game.toNextTurn();
expectWishActive(1); expect(game).toHavePositionalTag(PositionalTagType.WISH);
expect(game).toHavePositionalTag(PositionalTagType.DELAYED_ATTACK);
}); });
it("should work in double battles and trigger in order of creation", async () => { it("should work in double battles and trigger in order of creation", async () => {
@ -127,7 +119,7 @@ describe("Move - Wish", () => {
await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex())); await game.setTurnOrder(oldOrder.map(p => p.getBattlerIndex()));
await game.toNextTurn(); await game.toNextTurn();
expectWishActive(4); expect(game).toHavePositionalTag(PositionalTagType.WISH, 4);
// Lower speed to change turn order // Lower speed to change turn order
alomomola.setStatStage(Stat.SPD, 6); alomomola.setStatStage(Stat.SPD, 6);
@ -141,7 +133,7 @@ describe("Move - Wish", () => {
await game.phaseInterceptor.to("PositionalTagPhase"); await game.phaseInterceptor.to("PositionalTagPhase");
// all wishes have activated and added healing phases // all wishes have activated and added healing phases
expectWishActive(0); expect(game).toHavePositionalTag(PositionalTagType.WISH, 0);
const healPhases = game.scene.phaseManager.phaseQueue.filter(p => p.is("PokemonHealPhase")); const healPhases = game.scene.phaseManager.phaseQueue.filter(p => p.is("PokemonHealPhase"));
expect(healPhases).toHaveLength(4); expect(healPhases).toHaveLength(4);
@ -165,14 +157,14 @@ describe("Move - Wish", () => {
game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2); game.move.use(MoveId.WISH, BattlerIndex.PLAYER_2);
await game.toNextTurn(); await game.toNextTurn();
expectWishActive(); expect(game).toHavePositionalTag(PositionalTagType.WISH);
game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER); game.move.use(MoveId.SPLASH, BattlerIndex.PLAYER);
game.move.use(MoveId.MEMENTO, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2); game.move.use(MoveId.MEMENTO, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY_2);
await game.toEndOfTurn(); await game.toEndOfTurn();
// Wish went away without doing anything // Wish went away without doing anything
expectWishActive(0); expect(game).toHavePositionalTag(PositionalTagType.WISH, 0);
expect(game.textInterceptor.logs).not.toContain( expect(game.textInterceptor.logs).not.toContain(
i18next.t("arenaTag:wishTagOnAdd", { i18next.t("arenaTag:wishTagOnAdd", {
pokemonNameWithAffix: getPokemonNameWithAffix(blissey), pokemonNameWithAffix: getPokemonNameWithAffix(blissey),

View File

@ -1,4 +1,5 @@
import { getOnelineDiffStr } from "#test/test-utils/string-utils"; import { getOnelineDiffStr } from "#test/test-utils/string-utils";
import { receivedStr } from "#test/test-utils/test-utils";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
/** /**
@ -14,22 +15,22 @@ export function toEqualArrayUnsorted(
): SyncExpectationResult { ): SyncExpectationResult {
if (!Array.isArray(received)) { if (!Array.isArray(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected an array, but got ${this.utils.stringify(received)}!`, message: () => `Expected to receive an array, but got ${receivedStr(received)}!`,
}; };
} }
if (received.length !== expected.length) { if (received.length !== expected.length) {
return { return {
pass: false, pass: false,
message: () => `Expected to receive array of length ${received.length}, but got ${expected.length} instead!`, message: () => `Expected to receive an array of length ${received.length}, but got ${expected.length} instead!`,
actual: received,
expected, expected,
actual: received,
}; };
} }
const actualSorted = received.slice().sort(); const actualSorted = received.toSorted();
const expectedSorted = expected.slice().sort(); const expectedSorted = expected.toSorted();
const pass = this.equals(actualSorted, expectedSorted, [...this.customTesters, this.utils.iterableEquality]); const pass = this.equals(actualSorted, expectedSorted, [...this.customTesters, this.utils.iterableEquality]);
const actualStr = getOnelineDiffStr.call(this, actualSorted); const actualStr = getOnelineDiffStr.call(this, actualSorted);

View File

@ -21,8 +21,8 @@ export function toHaveAbilityApplied(
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to recieve a Pokemon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokemon, but got ${receivedStr(received)}!`,
}; };
} }

View File

@ -0,0 +1,77 @@
import type { ArenaTag, ArenaTagTypeMap } from "#data/arena-tag";
import type { ArenaTagSide } from "#enums/arena-tag-side";
import type { ArenaTagType } from "#enums/arena-tag-type";
import type { OneOther } from "#test/@types/test-helpers";
// biome-ignore lint/correctness/noUnusedImports: TSDoc
import type { GameManager } from "#test/test-utils/game-manager";
import { getOnelineDiffStr } from "#test/test-utils/string-utils";
import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
// intersection required to preserve T for inferences
export type toHaveArenaTagOptions<T extends ArenaTagType> = OneOther<ArenaTagTypeMap[T], "tagType" | "side"> & {
tagType: T;
};
/**
* Matcher to check if the {@linkcode Arena} has a given {@linkcode ArenaTag} active.
* @param received - The object to check. Should be the current {@linkcode GameManager}.
* @param expectedTag - The `ArenaTagType` of the desired tag, or a partially-filled object
* containing the desired properties
* @param side - The {@linkcode ArenaTagSide | side of the field} the tag should affect, or
* {@linkcode ArenaTagSide.BOTH} to check both sides
* @returns The result of the matching
*/
export function toHaveArenaTag<T extends ArenaTagType>(
this: MatcherState,
received: unknown,
expectedTag: T | toHaveArenaTagOptions<T>,
side?: ArenaTagSide,
): SyncExpectationResult {
if (!isGameManagerInstance(received)) {
return {
pass: this.isNot,
message: () => `Expected to receive a GameManager, but got ${receivedStr(received)}!`,
};
}
if (!received.scene?.arena) {
return {
pass: this.isNot,
message: () => `Expected GameManager.${received.scene ? "scene.arena" : "scene"} to be defined!`,
};
}
// Coerce lone `tagType`s into objects
// Bangs are ok as we enforce safety via overloads
// @ts-expect-error - Typescript is being stupid as tag type and side will always exist
const etag: Partial<ArenaTag> & { tagType: T; side: ArenaTagSide } =
typeof expectedTag === "object" ? expectedTag : { tagType: expectedTag, side: side! };
// We need to get all tags for the case of checking properties of a tag present on both sides of the arena
const tags = received.scene.arena.findTagsOnSide(t => t.tagType === etag.tagType, etag.side);
if (tags.length === 0) {
return {
pass: false,
message: () => `Expected the Arena to have a tag of type ${etag.tagType}, but it didn't!`,
expected: etag.tagType,
actual: received.scene.arena.tags.map(t => t.tagType),
};
}
// Pass if any of the matching tags meet our criteria
const pass = tags.some(tag =>
this.equals(tag, expectedTag, [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality]),
);
const expectedStr = getOnelineDiffStr.call(this, expectedTag);
return {
pass,
message: () =>
pass
? `Expected the Arena to NOT have a tag matching ${expectedStr}, but it did!`
: `Expected the Arena to have a tag matching ${expectedStr}, but it didn't!`,
expected: expectedTag,
actual: tags,
};
}

View File

@ -6,7 +6,7 @@ import { getStatName } from "#test/test-utils/string-utils";
import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils"; import { isPokemonInstance, receivedStr } from "#test/test-utils/test-utils";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect"; import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export interface ToHaveEffectiveStatMatcherOptions { export interface toHaveEffectiveStatOptions {
/** /**
* The target {@linkcode Pokemon} * The target {@linkcode Pokemon}
* @see {@linkcode Pokemon.getEffectiveStat} * @see {@linkcode Pokemon.getEffectiveStat}
@ -30,7 +30,7 @@ export interface ToHaveEffectiveStatMatcherOptions {
* @param received - The object to check. Should be a {@linkcode Pokemon} * @param received - The object to check. Should be a {@linkcode Pokemon}
* @param stat - The {@linkcode EffectiveStat} to check * @param stat - The {@linkcode EffectiveStat} to check
* @param expectedValue - The expected value of the {@linkcode stat} * @param expectedValue - The expected value of the {@linkcode stat}
* @param options - The {@linkcode ToHaveEffectiveStatMatcherOptions} * @param options - The {@linkcode toHaveEffectiveStatOptions}
* @returns Whether the matcher passed * @returns Whether the matcher passed
*/ */
export function toHaveEffectiveStat( export function toHaveEffectiveStat(
@ -38,11 +38,11 @@ export function toHaveEffectiveStat(
received: unknown, received: unknown,
stat: EffectiveStat, stat: EffectiveStat,
expectedValue: number, expectedValue: number,
{ enemy, move, isCritical = false }: ToHaveEffectiveStatMatcherOptions = {}, { enemy, move, isCritical = false }: toHaveEffectiveStatOptions = {},
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }

View File

@ -12,7 +12,7 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export function toHaveFainted(this: MatcherState, received: unknown): SyncExpectationResult { export function toHaveFainted(this: MatcherState, received: unknown): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }

View File

@ -12,7 +12,7 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export function toHaveFullHp(this: MatcherState, received: unknown): SyncExpectationResult { export function toHaveFullHp(this: MatcherState, received: unknown): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }

View File

@ -13,7 +13,7 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export function toHaveHp(this: MatcherState, received: unknown, expectedHp: number): SyncExpectationResult { export function toHaveHp(this: MatcherState, received: unknown, expectedHp: number): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }

View File

@ -0,0 +1,107 @@
// biome-ignore-start lint/correctness/noUnusedImports: TSDoc
import type { GameManager } from "#test/test-utils/game-manager";
// biome-ignore-end lint/correctness/noUnusedImports: TSDoc
import type { serializedPosTagMap } from "#data/positional-tags/load-positional-tag";
import type { PositionalTagType } from "#enums/positional-tag-type";
import type { OneOther } from "#test/@types/test-helpers";
import { getOnelineDiffStr } from "#test/test-utils/string-utils";
import { isGameManagerInstance, receivedStr } from "#test/test-utils/test-utils";
import { toTitleCase } from "#utils/strings";
import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export type toHavePositionalTagOptions<P extends PositionalTagType> = OneOther<serializedPosTagMap[P], "tagType"> & {
tagType: P;
};
/**
* Matcher to check if the {@linkcode Arena} has a certain number of {@linkcode PositionalTag}s active.
* @param received - The object to check. Should be the current {@linkcode GameManager}
* @param expectedTag - The {@linkcode PositionalTagType} of the desired tag, or a partially-filled {@linkcode PositionalTag}
* containing the desired properties
* @param count - The number of tags that should be active; defaults to `1` and must be within the range `[0, 4]`
* @returns The result of the matching
*/
export function toHavePositionalTag<P extends PositionalTagType>(
this: MatcherState,
received: unknown,
expectedTag: P | toHavePositionalTagOptions<P>,
count = 1,
): SyncExpectationResult {
if (!isGameManagerInstance(received)) {
return {
pass: this.isNot,
message: () => `Expected to receive a GameManager, but got ${receivedStr(received)}!`,
};
}
if (!received.scene?.arena?.positionalTagManager) {
return {
pass: this.isNot,
message: () =>
`Expected GameManager.${received.scene?.arena ? "scene.arena.positionalTagManager" : received.scene ? "scene.arena" : "scene"} to be defined!`,
};
}
// TODO: Increase limit if triple battles are added
if (count < 0 || count > 4) {
return {
pass: this.isNot,
message: () => `Expected count to be between 0 and 4, but got ${count} instead!`,
};
}
const allTags = received.scene.arena.positionalTagManager.tags;
const tagType = typeof expectedTag === "string" ? expectedTag : expectedTag.tagType;
const matchingTags = allTags.filter(t => t.tagType === tagType);
// If checking exclusively tag type, check solely the number of matching tags on field
if (typeof expectedTag === "string") {
const pass = matchingTags.length === count;
const expectedStr = getPosTagStr(expectedTag);
return {
pass,
message: () =>
pass
? `Expected the Arena to NOT have ${count} ${expectedStr} active, but it did!`
: `Expected the Arena to have ${count} ${expectedStr} active, but got ${matchingTags.length} instead!`,
expected: expectedTag,
actual: allTags,
};
}
// Check for equality with the provided object
if (matchingTags.length === 0) {
return {
pass: false,
message: () => `Expected the Arena to have a tag of type ${expectedTag.tagType}, but it didn't!`,
expected: expectedTag.tagType,
actual: received.scene.arena.tags.map(t => t.tagType),
};
}
// Pass if any of the matching tags meet the criteria
const pass = matchingTags.some(tag =>
this.equals(tag, expectedTag, [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality]),
);
const expectedStr = getOnelineDiffStr.call(this, expectedTag);
return {
pass,
message: () =>
pass
? `Expected the Arena to NOT have a tag matching ${expectedStr}, but it did!`
: `Expected the Arena to have a tag matching ${expectedStr}, but it didn't!`,
expected: expectedTag,
actual: matchingTags,
};
}
function getPosTagStr(pType: PositionalTagType, count = 1): string {
let ret = toTitleCase(pType) + "Tag";
if (count > 1) {
ret += "s";
}
return ret;
}

View File

@ -23,14 +23,14 @@ export function toHaveStatStage(
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }
if (expectedStage < -6 || expectedStage > 6) { if (expectedStage < -6 || expectedStage > 6) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected ${expectedStage} to be within the range [-6, 6]!`, message: () => `Expected ${expectedStage} to be within the range [-6, 6]!`,
}; };
} }

View File

@ -28,7 +28,7 @@ export function toHaveStatusEffect(
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }
@ -37,10 +37,8 @@ export function toHaveStatusEffect(
const actualEffect = received.status?.effect ?? StatusEffect.NONE; const actualEffect = received.status?.effect ?? StatusEffect.NONE;
// Check exclusively effect equality first, coercing non-matching status effects to numbers. // Check exclusively effect equality first, coercing non-matching status effects to numbers.
if (actualEffect !== (expectedStatus as Exclude<typeof expectedStatus, StatusEffect>)?.effect) { if (typeof expectedStatus === "object" && actualEffect !== expectedStatus.effect) {
// This is actually 100% safe as `expectedStatus?.effect` will evaluate to `undefined` if a StatusEffect was passed, expectedStatus = expectedStatus.effect;
// which will never match actualEffect by definition
expectedStatus = (expectedStatus as Exclude<typeof expectedStatus, StatusEffect>).effect;
} }
if (typeof expectedStatus === "number") { if (typeof expectedStatus === "number") {

View File

@ -24,7 +24,7 @@ export function toHaveTakenDamage(
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }

View File

@ -20,15 +20,15 @@ export function toHaveTerrain(
): SyncExpectationResult { ): SyncExpectationResult {
if (!isGameManagerInstance(received)) { if (!isGameManagerInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected GameManager, but got ${receivedStr(received)}!`, message: () => `Expected to receive a GameManager, but got ${receivedStr(received)}!`,
}; };
} }
if (!received.scene?.arena) { if (!received.scene?.arena) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`, message: () => `Expected GameManager.${received.scene ? "scene.arena" : "scene"} to be defined!`,
}; };
} }
@ -41,8 +41,8 @@ export function toHaveTerrain(
pass, pass,
message: () => message: () =>
pass pass
? `Expected Arena to NOT have ${expectedStr} active, but it did!` ? `Expected the Arena to NOT have ${expectedStr} active, but it did!`
: `Expected Arena to have ${expectedStr} active, but got ${actualStr} instead!`, : `Expected the Arena to have ${expectedStr} active, but got ${actualStr} instead!`,
expected: expectedTerrainType, expected: expectedTerrainType,
actual, actual,
}; };

View File

@ -7,10 +7,16 @@ import { isPokemonInstance, receivedStr } from "../test-utils";
export interface toHaveTypesOptions { export interface toHaveTypesOptions {
/** /**
* Whether to enforce exact matches (`true`) or superset matches (`false`). * Value dictating the strength of the enforced typing match.
* @defaultValue `true` *
* Possible values (in ascending order of strength) are:
* - `"ordered"`: Enforce that the {@linkcode Pokemon}'s types are identical **and in the same order**
* - `"unordered"`: Enforce that the {@linkcode Pokemon}'s types are identical **without checking order**
* - `"superset"`: Enforce that the {@linkcode Pokemon}'s types are **a superset of** the expected types
* (all must be present, but extras can be there)
* @defaultValue `"unordered"`
*/ */
exact?: boolean; mode?: "ordered" | "unordered" | "superset";
/** /**
* Optional arguments to pass to {@linkcode Pokemon.getTypes}. * Optional arguments to pass to {@linkcode Pokemon.getTypes}.
*/ */
@ -18,35 +24,54 @@ export interface toHaveTypesOptions {
} }
/** /**
* Matcher that checks if an array contains exactly the given items, disregarding order. * Matcher that checks if a Pokemon's typing is as expected.
* @param received - The object to check. Should be an array of one or more {@linkcode PokemonType}s. * @param received - The object to check. Should be a {@linkcode Pokemon}
* @param options - The {@linkcode toHaveTypesOptions | options} for this matcher * @param expectedTypes - An array of one or more {@linkcode PokemonType}s to compare against.
* @param mode - The mode to perform the matching in.
* Possible values (in ascending order of strength) are:
* - `"ordered"`: Enforce that the {@linkcode Pokemon}'s types are identical **and in the same order**
* - `"unordered"`: Enforce that the {@linkcode Pokemon}'s types are identical **without checking order**
* - `"superset"`: Enforce that the {@linkcode Pokemon}'s types are **a superset of** the expected types
* (all must be present, but extras can be there)
*
* Default `unordered`
* @param args - Extra arguments passed to {@linkcode Pokemon.getTypes}
* @returns The result of the matching * @returns The result of the matching
*/ */
export function toHaveTypes( export function toHaveTypes(
this: MatcherState, this: MatcherState,
received: unknown, received: unknown,
expected: [PokemonType, ...PokemonType[]], expectedTypes: [PokemonType, ...PokemonType[]],
options: toHaveTypesOptions = {}, { mode = "unordered", args = [] }: toHaveTypesOptions = {},
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to recieve a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }
const actualTypes = received.getTypes(...(options.args ?? [])).sort(); // Return early if no types were passed in
const expectedTypes = expected.slice().sort(); if (expectedTypes.length === 0) {
return {
pass: this.isNot,
message: () => "Expected to receive a non-empty array of PokemonTypes!",
};
}
// Avoid sorting the types if strict ordering is desired
const actualSorted = mode === "ordered" ? received.getTypes(...args) : received.getTypes(...args).toSorted();
const expectedSorted = mode === "ordered" ? expectedTypes : expectedTypes.toSorted();
// Exact matches do not care about subset equality // Exact matches do not care about subset equality
const matchers = options.exact const matchers =
? [...this.customTesters, this.utils.iterableEquality] mode === "superset"
: [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality]; ? [...this.customTesters, this.utils.iterableEquality]
const pass = this.equals(actualTypes, expectedTypes, matchers); : [...this.customTesters, this.utils.subsetEquality, this.utils.iterableEquality];
const pass = this.equals(actualSorted, expectedSorted, matchers);
const actualStr = stringifyEnumArray(PokemonType, actualTypes); const actualStr = stringifyEnumArray(PokemonType, actualSorted);
const expectedStr = stringifyEnumArray(PokemonType, expectedTypes); const expectedStr = stringifyEnumArray(PokemonType, expectedSorted);
const pkmName = getPokemonNameWithAffix(received); const pkmName = getPokemonNameWithAffix(received);
return { return {
@ -55,7 +80,7 @@ export function toHaveTypes(
pass pass
? `Expected ${pkmName} to NOT have types ${expectedStr}, but it did!` ? `Expected ${pkmName} to NOT have types ${expectedStr}, but it did!`
: `Expected ${pkmName} to have types ${expectedStr}, but got ${actualStr} instead!`, : `Expected ${pkmName} to have types ${expectedStr}, but got ${actualStr} instead!`,
expected: expectedTypes, expected: expectedSorted,
actual: actualTypes, actual: actualSorted,
}; };
} }

View File

@ -13,7 +13,7 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
/** /**
* Matcher to check the contents of a {@linkcode Pokemon}'s move history. * Matcher to check the contents of a {@linkcode Pokemon}'s move history.
* @param received - The actual value received. Should be a {@linkcode Pokemon} * @param received - The actual value received. Should be a {@linkcode Pokemon}
* @param expectedValue - The {@linkcode MoveId} the Pokemon is expected to have used, * @param expectedMove - The {@linkcode MoveId} the Pokemon is expected to have used,
* or a partially filled {@linkcode TurnMove} containing the desired properties to check * or a partially filled {@linkcode TurnMove} containing the desired properties to check
* @param index - The index of the move history entry to check, in order from most recent to least recent. * @param index - The index of the move history entry to check, in order from most recent to least recent.
* Default `0` (last used move) * Default `0` (last used move)
@ -22,12 +22,12 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export function toHaveUsedMove( export function toHaveUsedMove(
this: MatcherState, this: MatcherState,
received: unknown, received: unknown,
expectedResult: MoveId | AtLeastOne<TurnMove>, expectedMove: MoveId | AtLeastOne<TurnMove>,
index = 0, index = 0,
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }
@ -37,34 +37,33 @@ export function toHaveUsedMove(
if (move === undefined) { if (move === undefined) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected ${pkmName} to have used ${index + 1} moves, but it didn't!`, message: () => `Expected ${pkmName} to have used ${index + 1} moves, but it didn't!`,
actual: received.getLastXMoves(-1), actual: received.getLastXMoves(-1),
}; };
} }
// Coerce to a `TurnMove` // Coerce to a `TurnMove`
if (typeof expectedResult === "number") { if (typeof expectedMove === "number") {
expectedResult = { move: expectedResult }; expectedMove = { move: expectedMove };
} }
const moveIndexStr = index === 0 ? "last move" : `${getOrdinal(index)} most recent move`; const moveIndexStr = index === 0 ? "last move" : `${getOrdinal(index)} most recent move`;
const pass = this.equals(move, expectedResult, [ const pass = this.equals(move, expectedMove, [
...this.customTesters, ...this.customTesters,
this.utils.subsetEquality, this.utils.subsetEquality,
this.utils.iterableEquality, this.utils.iterableEquality,
]); ]);
const expectedStr = getOnelineDiffStr.call(this, expectedResult); const expectedStr = getOnelineDiffStr.call(this, expectedMove);
return { return {
pass, pass,
message: () => message: () =>
pass pass
? `Expected ${pkmName}'s ${moveIndexStr} to NOT match ${expectedStr}, but it did!` ? `Expected ${pkmName}'s ${moveIndexStr} to NOT match ${expectedStr}, but it did!`
: // Replace newlines with spaces to preserve one-line ness : `Expected ${pkmName}'s ${moveIndexStr} to match ${expectedStr}, but it didn't!`,
`Expected ${pkmName}'s ${moveIndexStr} to match ${expectedStr}, but it didn't!`, expected: expectedMove,
expected: expectedResult,
actual: move, actual: move,
}; };
} }

View File

@ -13,7 +13,7 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
/** /**
* Matcher to check the amount of PP consumed by a {@linkcode Pokemon}. * Matcher to check the amount of PP consumed by a {@linkcode Pokemon}.
* @param received - The actual value received. Should be a {@linkcode Pokemon} * @param received - The actual value received. Should be a {@linkcode Pokemon}
* @param expectedValue - The {@linkcode MoveId} that should have consumed PP * @param moveId - The {@linkcode MoveId} that should have consumed PP
* @param ppUsed - The numerical amount of PP that should have been consumed, * @param ppUsed - The numerical amount of PP that should have been consumed,
* or `all` to indicate the move should be _out_ of PP * or `all` to indicate the move should be _out_ of PP
* @returns Whether the matcher passed * @returns Whether the matcher passed
@ -23,12 +23,12 @@ import type { MatcherState, SyncExpectationResult } from "@vitest/expect";
export function toHaveUsedPP( export function toHaveUsedPP(
this: MatcherState, this: MatcherState,
received: unknown, received: unknown,
expectedMove: MoveId, moveId: MoveId,
ppUsed: number | "all", ppUsed: number | "all",
): SyncExpectationResult { ): SyncExpectationResult {
if (!isPokemonInstance(received)) { if (!isPokemonInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`, message: () => `Expected to receive a Pokémon, but got ${receivedStr(received)}!`,
}; };
} }
@ -36,22 +36,22 @@ export function toHaveUsedPP(
const override = received.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE; const override = received.isPlayer() ? Overrides.MOVESET_OVERRIDE : Overrides.OPP_MOVESET_OVERRIDE;
if (coerceArray(override).length > 0) { if (coerceArray(override).length > 0) {
return { return {
pass: false, pass: this.isNot,
message: () => message: () =>
`Cannot test for PP consumption with ${received.isPlayer() ? "player" : "enemy"} moveset overrides active!`, `Cannot test for PP consumption with ${received.isPlayer() ? "player" : "enemy"} moveset overrides active!`,
}; };
} }
const pkmName = getPokemonNameWithAffix(received); const pkmName = getPokemonNameWithAffix(received);
const moveStr = getEnumStr(MoveId, expectedMove); const moveStr = getEnumStr(MoveId, moveId);
const movesetMoves = received.getMoveset().filter(pm => pm.moveId === expectedMove); const movesetMoves = received.getMoveset().filter(pm => pm.moveId === moveId);
if (movesetMoves.length !== 1) { if (movesetMoves.length !== 1) {
return { return {
pass: false, pass: this.isNot,
message: () => message: () =>
`Expected MoveId.${moveStr} to appear in ${pkmName}'s moveset exactly once, but got ${movesetMoves.length} times!`, `Expected MoveId.${moveStr} to appear in ${pkmName}'s moveset exactly once, but got ${movesetMoves.length} times!`,
expected: expectedMove, expected: moveId,
actual: received.getMoveset(), actual: received.getMoveset(),
}; };
} }

View File

@ -20,15 +20,15 @@ export function toHaveWeather(
): SyncExpectationResult { ): SyncExpectationResult {
if (!isGameManagerInstance(received)) { if (!isGameManagerInstance(received)) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected GameManager, but got ${receivedStr(received)}!`, message: () => `Expected to receive a GameManager, but got ${receivedStr(received)}!`,
}; };
} }
if (!received.scene?.arena) { if (!received.scene?.arena) {
return { return {
pass: false, pass: this.isNot,
message: () => `Expected GameManager.${received.scene ? "scene" : "scene.arena"} to be defined!`, message: () => `Expected GameManager.${received.scene ? "scene.arena" : "scene"} to be defined!`,
}; };
} }
@ -41,8 +41,8 @@ export function toHaveWeather(
pass, pass,
message: () => message: () =>
pass pass
? `Expected Arena to NOT have ${expectedStr} weather active, but it did!` ? `Expected the Arena to NOT have ${expectedStr} weather active, but it did!`
: `Expected Arena to have ${expectedStr} weather active, but got ${actualStr} instead!`, : `Expected the Arena to have ${expectedStr} weather active, but got ${actualStr} instead!`,
expected: expectedWeatherType, expected: expectedWeatherType,
actual, actual,
}; };

View File

@ -34,10 +34,10 @@ interface getEnumStrOptions {
* @returns The stringified representation of `val` as dictated by the options. * @returns The stringified representation of `val` as dictated by the options.
* @example * @example
* ```ts * ```ts
* enum fakeEnum { * enum testEnum {
* ONE: 1, * ONE = 1,
* TWO: 2, * TWO = 2,
* THREE: 3, * THREE = 3,
* } * }
* getEnumStr(fakeEnum, fakeEnum.ONE); // Output: "ONE (=1)" * getEnumStr(fakeEnum, fakeEnum.ONE); // Output: "ONE (=1)"
* getEnumStr(fakeEnum, fakeEnum.TWO, {casing: "Title", prefix: "fakeEnum.", suffix: "!!!"}); // Output: "fakeEnum.TWO!!! (=2)" * getEnumStr(fakeEnum, fakeEnum.TWO, {casing: "Title", prefix: "fakeEnum.", suffix: "!!!"}); // Output: "fakeEnum.TWO!!! (=2)"
@ -174,10 +174,14 @@ export function getStatName(s: Stat): string {
* Convert an object into a oneline diff to be shown in an error message. * Convert an object into a oneline diff to be shown in an error message.
* @param obj - The object to return the oneline diff of * @param obj - The object to return the oneline diff of
* @returns The updated diff * @returns The updated diff
* @example
* ```ts
* const diff = getOnelineDiffStr.call(this, obj)
* ```
*/ */
export function getOnelineDiffStr(this: MatcherState, obj: unknown): string { export function getOnelineDiffStr(this: MatcherState, obj: unknown): string {
return this.utils return this.utils
.stringify(obj, undefined, { maxLength: 35, indent: 0, printBasicPrototype: false }) .stringify(obj, undefined, { maxLength: 35, indent: 0, printBasicPrototype: false })
.replace(/\n/g, " ") // Replace newlines with spaces .replace(/\n/g, " ") // Replace newlines with spaces
.replace(/,(\s*)}$/g, "$1}"); .replace(/,(\s*)}$/g, "$1}"); // Trim trailing commas
} }