Merge remote-tracking branch 'upstream/main' into move-power-trick

This commit is contained in:
cadi 2024-06-27 19:21:11 +09:00
commit 3e2e740bd1
75 changed files with 11996 additions and 9679 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,92 +1,36 @@
{
"1": {
"207cc1": "c67429",
"44b4f4": "eea747",
"1d5486": "923d13",
"826c4d": "dd493b",
"f8eaba": "fff3ec",
"cdac7b": "bd9b8e",
"0d0d0d": "101010",
"090909": "101010",
"3b3f47": "c32625",
"3c3f47": "c32625",
"1f2025": "101010",
"202226": "101010",
"5e5f62": "dd493b",
"1c5486": "923d13",
"1f7cc1": "c67429",
"826b4d": "dd493b",
"992137": "8691d5",
"e6414a": "c9d4ff",
"000000": "101010",
"f4f4f4": "f8f8f8",
"992035": "8691d5",
"2b2d33": "682a23",
"2a2c31": "682a23",
"282a2e": "682a23",
"e0e0e0": "f37754",
"030303": "101010",
"393f47": "c32625",
"1c1d22": "101010",
"195486": "923d13",
"1c7cc1": "c67429",
"82694d": "dd493b",
"991d31": "8691d5",
"ffffff": "f8f8f8",
"26282c": "682a23",
"060606": "101010",
"3a3f47": "c32625",
"1d1f24": "101010",
"1a5486": "923d13",
"1d7cc1": "c67429",
"826a4d": "dd493b",
"991f33": "8691d5",
"292b2f": "682a23",
"27292d": "682a23"
},
"2": {
"207cc1": "582c81",
"44b4f4": "7b43a1",
"1d5486": "411f70",
"826c4d": "f2a366",
"f8eaba": "ffedf4",
"cdac7b": "d7aec0",
"0d0d0d": "101010",
"090909": "101010",
"3b3f47": "bc6532",
"3c3f47": "bc6532",
"1f2025": "101010",
"202226": "101010",
"5e5f62": "f2a366",
"1c5486": "411f70",
"1f7cc1": "582c81",
"826b4d": "f2a366",
"992137": "a62869",
"e6414a": "e15693",
"000000": "101010",
"f4f4f4": "f8f8f8",
"992035": "a62869",
"2b2d33": "202b47",
"2a2c31": "202b47",
"282a2e": "202b47",
"e0e0e0": "ffdb85",
"030303": "101010",
"393f47": "bc6532",
"1c1d22": "101010",
"195486": "411f70",
"1c7cc1": "582c81",
"82694d": "f2a366",
"991d31": "a62869",
"ffffff": "f8f8f8",
"26282c": "202b47",
"060606": "101010",
"3a3f47": "bc6532",
"1d1f24": "101010",
"1a5486": "411f70",
"1d7cc1": "582c81",
"826a4d": "f2a366",
"991f33": "a62869",
"292b2f": "202b47",
"27292d": "202b47"
}
}

View File

@ -219,48 +219,6 @@
"h": 23
}
},
{
"filename": "354-mega_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 26,
"h": 30
},
"frame": {
"x": 0,
"y": 176,
"w": 26,
"h": 30
}
},
{
"filename": "354-mega_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 26,
"h": 30
},
"frame": {
"x": 0,
"y": 206,
"w": 26,
"h": 30
}
},
{
"filename": "666-archipelago_2",
"rotated": false,
@ -1479,27 +1437,6 @@
"h": 24
}
},
{
"filename": "779_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 10,
"y": 7,
"w": 21,
"h": 20
},
"frame": {
"x": 107,
"y": 155,
"w": 21,
"h": 20
}
},
{
"filename": "673_2",
"rotated": false,
@ -1521,27 +1458,6 @@
"h": 22
}
},
{
"filename": "779_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 10,
"y": 7,
"w": 21,
"h": 20
},
"frame": {
"x": 107,
"y": 175,
"w": 21,
"h": 20
}
},
{
"filename": "666-tundra_3",
"rotated": false,
@ -1794,48 +1710,6 @@
"h": 22
}
},
{
"filename": "743_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 20,
"h": 22
},
"frame": {
"x": 78,
"y": 358,
"w": 20,
"h": 22
}
},
{
"filename": "743_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 20,
"h": 22
},
"frame": {
"x": 78,
"y": 380,
"w": 20,
"h": 22
}
},
{
"filename": "690_3",
"rotated": false,
@ -1857,48 +1731,6 @@
"h": 21
}
},
{
"filename": "742_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 19,
"h": 20
},
"frame": {
"x": 78,
"y": 423,
"w": 19,
"h": 20
}
},
{
"filename": "742_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 19,
"h": 20
},
"frame": {
"x": 78,
"y": 443,
"w": 19,
"h": 20
}
},
{
"filename": "720_1",
"rotated": false,
@ -2214,48 +2046,6 @@
"h": 17
}
},
{
"filename": "777_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 13,
"y": 8,
"w": 17,
"h": 19
},
"frame": {
"x": 111,
"y": 396,
"w": 17,
"h": 19
}
},
{
"filename": "777_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 13,
"y": 8,
"w": 17,
"h": 19
},
"frame": {
"x": 111,
"y": 415,
"w": 17,
"h": 19
}
},
{
"filename": "712_2",
"rotated": false,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -157,7 +157,7 @@
}
},
{
"filename": "778_2",
"filename": "778-disguised_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -171,8 +171,8 @@
"h": 22
},
"frame": {
"x": 109,
"y": 0,
"x": 113,
"y": 72,
"w": 17,
"h": 22
}
@ -570,8 +570,8 @@
"h": 24
},
"frame": {
"x": 105,
"y": 28,
"x": 109,
"y": 0,
"w": 21,
"h": 24
}
@ -654,8 +654,8 @@
"h": 24
},
"frame": {
"x": 105,
"y": 52,
"x": 109,
"y": 24,
"w": 21,
"h": 24
}
@ -759,8 +759,8 @@
"h": 19
},
"frame": {
"x": 89,
"y": 56,
"x": 98,
"y": 516,
"w": 16,
"h": 19
}
@ -822,8 +822,8 @@
"h": 24
},
"frame": {
"x": 113,
"y": 76,
"x": 117,
"y": 117,
"w": 13,
"h": 24
}
@ -1081,7 +1081,7 @@
}
},
{
"filename": "778_3",
"filename": "778-disguised_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
@ -1095,8 +1095,8 @@
"h": 22
},
"frame": {
"x": 109,
"y": 171,
"x": 96,
"y": 445,
"w": 17,
"h": 22
}
@ -1116,8 +1116,8 @@
"h": 20
},
"frame": {
"x": 109,
"y": 193,
"x": 113,
"y": 448,
"w": 17,
"h": 20
}
@ -1137,8 +1137,8 @@
"h": 20
},
"frame": {
"x": 109,
"y": 213,
"x": 113,
"y": 94,
"w": 17,
"h": 20
}
@ -1158,8 +1158,8 @@
"h": 17
},
"frame": {
"x": 109,
"y": 233,
"x": 114,
"y": 468,
"w": 17,
"h": 17
}
@ -1179,8 +1179,8 @@
"h": 17
},
"frame": {
"x": 109,
"y": 250,
"x": 113,
"y": 235,
"w": 17,
"h": 17
}
@ -1494,8 +1494,8 @@
"h": 24
},
"frame": {
"x": 106,
"y": 267,
"x": 109,
"y": 171,
"w": 20,
"h": 24
}
@ -1536,8 +1536,8 @@
"h": 24
},
"frame": {
"x": 106,
"y": 291,
"x": 109,
"y": 195,
"w": 20,
"h": 24
}
@ -1579,7 +1579,7 @@
},
"frame": {
"x": 106,
"y": 315,
"y": 318,
"w": 20,
"h": 22
}
@ -1704,8 +1704,8 @@
"h": 26
},
"frame": {
"x": 52,
"y": 458,
"x": 101,
"y": 382,
"w": 23,
"h": 26
}
@ -1872,8 +1872,8 @@
"h": 23
},
"frame": {
"x": 102,
"y": 337,
"x": 106,
"y": 295,
"w": 24,
"h": 23
}
@ -1998,8 +1998,8 @@
"h": 24
},
"frame": {
"x": 100,
"y": 382,
"x": 52,
"y": 458,
"w": 22,
"h": 24
}
@ -2019,8 +2019,8 @@
"h": 24
},
"frame": {
"x": 100,
"y": 406,
"x": 109,
"y": 48,
"w": 21,
"h": 24
}
@ -2061,8 +2061,8 @@
"h": 18
},
"frame": {
"x": 103,
"y": 430,
"x": 104,
"y": 408,
"w": 23,
"h": 18
}
@ -2103,7 +2103,7 @@
"h": 22
},
"frame": {
"x": 75,
"x": 74,
"y": 470,
"w": 20,
"h": 22
@ -2145,8 +2145,8 @@
"h": 20
},
"frame": {
"x": 96,
"y": 448,
"x": 109,
"y": 426,
"w": 22,
"h": 20
}
@ -2250,8 +2250,8 @@
"h": 20
},
"frame": {
"x": 91,
"y": 594,
"x": 102,
"y": 340,
"w": 14,
"h": 20
}
@ -2271,8 +2271,8 @@
"h": 19
},
"frame": {
"x": 105,
"y": 468,
"x": 91,
"y": 594,
"w": 21,
"h": 19
}
@ -2313,8 +2313,8 @@
"h": 24
},
"frame": {
"x": 111,
"y": 487,
"x": 117,
"y": 141,
"w": 13,
"h": 24
}
@ -2334,8 +2334,8 @@
"h": 20
},
"frame": {
"x": 98,
"y": 515,
"x": 116,
"y": 340,
"w": 14,
"h": 20
}
@ -2355,8 +2355,8 @@
"h": 19
},
"frame": {
"x": 112,
"y": 511,
"x": 116,
"y": 537,
"w": 14,
"h": 19
}
@ -2376,8 +2376,8 @@
"h": 19
},
"frame": {
"x": 112,
"y": 530,
"x": 116,
"y": 556,
"w": 14,
"h": 19
}
@ -2397,8 +2397,8 @@
"h": 19
},
"frame": {
"x": 95,
"y": 535,
"x": 96,
"y": 575,
"w": 16,
"h": 19
}
@ -2418,12 +2418,222 @@
"h": 14
},
"frame": {
"x": 95,
"y": 554,
"x": 106,
"y": 265,
"w": 24,
"h": 14
}
},
{
"filename": "742_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 19,
"h": 20
},
"frame": {
"x": 112,
"y": 575,
"w": 19,
"h": 20
}
},
{
"filename": "742_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 19,
"h": 20
},
"frame": {
"x": 112,
"y": 594,
"w": 19,
"h": 20
}
},
{
"filename": "743_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 20,
"h": 22
},
"frame": {
"x": 94,
"y": 470,
"w": 20,
"h": 22
}
},
{
"filename": "743_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 12,
"y": 5,
"w": 20,
"h": 22
},
"frame": {
"x": 89,
"y": 56,
"w": 20,
"h": 22
}
},
{
"filename": "777_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 13,
"y": 8,
"w": 17,
"h": 19
},
"frame": {
"x": 114,
"y": 518,
"w": 17,
"h": 19
}
},
{
"filename": "777_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 13,
"y": 8,
"w": 17,
"h": 19
},
"frame": {
"x": 114,
"y": 486,
"w": 17,
"h": 19
}
},
{
"filename": "778-busted_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 9,
"y": 12,
"w": 21,
"h": 16
},
"frame": {
"x": 109,
"y": 219,
"w": 21,
"h": 16
}
},
{
"filename": "778-busted_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 9,
"y": 12,
"w": 21,
"h": 16
},
"frame": {
"x": 106,
"y": 279,
"w": 21,
"h": 16
}
},
{
"filename": "779_2",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 10,
"y": 7,
"w": 21,
"h": 20
},
"frame": {
"x": 95,
"y": 535,
"w": 21,
"h": 20
}
},
{
"filename": "779_3",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 10,
"y": 7,
"w": 21,
"h": 20
},
"frame": {
"x": 95,
"y": 555,
"w": 21,
"h": 20
}
},
{
"filename": "771_2",
"rotated": false,
@ -2439,8 +2649,8 @@
"h": 13
},
"frame": {
"x": 97,
"y": 568,
"x": 112,
"y": 505,
"w": 18,
"h": 13
}
@ -2460,8 +2670,8 @@
"h": 13
},
"frame": {
"x": 97,
"y": 581,
"x": 109,
"y": 252,
"w": 18,
"h": 13
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,78 @@
{
"textures": [
{
"image": "shiny_icons.png",
"format": "RGBA8888",
"size": {
"w": 15,
"h": 14
},
"scale": 1,
"frames": [
{
"filename": "0",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "1",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "2",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
}
}
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

View File

@ -0,0 +1,78 @@
{
"textures": [
{
"image": "shiny_icons.png",
"format": "RGBA8888",
"size": {
"w": 15,
"h": 14
},
"scale": 1,
"frames": [
{
"filename": "0",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "1",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 15,
"y": 0,
"w": 15,
"h": 14
}
},
{
"filename": "2",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 15,
"h": 14
},
"spriteSourceSize": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 30,
"y": 0,
"w": 15,
"h": 14
}
}
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

View File

@ -9,7 +9,7 @@ import { Weather, WeatherType } from "./weather";
import { BattlerTag, GroundedTag } from "./battler-tags";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr } from "./move";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, MoveAttr, MultiHitAttr, ChargeAttr, SacrificialAttr, SacrificialAttrOnHit } from "./move";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { Stat, getStatName } from "./pokemon-stat";
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier";
@ -301,18 +301,19 @@ export class StabBoostAbAttr extends AbAttr {
export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
protected condition: PokemonDefendCondition;
private powerMultiplier: number;
private damageMultiplier: number;
constructor(condition: PokemonDefendCondition, powerMultiplier: number) {
constructor(condition: PokemonDefendCondition, damageMultiplier: number) {
super();
this.condition = condition;
this.powerMultiplier = powerMultiplier;
this.damageMultiplier = damageMultiplier;
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
(args[0] as Utils.NumberHolder).value = Math.floor((args[0] as Utils.NumberHolder).value * this.damageMultiplier);
return true;
}
@ -321,19 +322,19 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
}
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(moveType: Type, powerMultiplier: number) {
super((user, target, move) => move.type === moveType, powerMultiplier);
constructor(moveType: Type, damageMultiplier: number) {
super((user, target, move) => move.type === moveType, damageMultiplier);
}
}
export class PreDefendMovePowerToOneAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
export class PreDefendMoveDamageToOneAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(condition: PokemonDefendCondition) {
super(condition, 1);
}
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
(args[0] as Utils.NumberHolder).value = 1;
(args[0] as Utils.NumberHolder).value = Math.floor(pokemon.getMaxHp() / 8);
return true;
}
@ -1230,6 +1231,84 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
}
}
/**
* Class for abilities that convert single-strike moves to two-strike moves (i.e. Parental Bond).
* @param damageMultiplier the damage multiplier for the second strike, relative to the first.
*/
export class AddSecondStrikeAbAttr extends PreAttackAbAttr {
private damageMultiplier: number;
constructor(damageMultiplier: number) {
super(false);
this.damageMultiplier = damageMultiplier;
}
/**
* Determines whether this attribute can apply to a given move.
* @param {Move} move the move to which this attribute may apply
* @param numTargets the number of {@linkcode Pokemon} targeted by this move
* @returns true if the attribute can apply to the move, false otherwise
*/
canApplyPreAttack(move: Move, numTargets: integer): boolean {
/**
* Parental Bond cannot apply to multi-hit moves, charging moves, or
* moves that cause the user to faint.
*/
const exceptAttrs: Constructor<MoveAttr>[] = [
MultiHitAttr,
ChargeAttr,
SacrificialAttr,
SacrificialAttrOnHit
];
/** Parental Bond cannot apply to these specific moves */
const exceptMoves: Moves[] = [
Moves.FLING,
Moves.UPROAR,
Moves.ROLLOUT,
Moves.ICE_BALL,
Moves.ENDEAVOR
];
/** Also check if this move is an Attack move and if it's only targeting one Pokemon */
return numTargets === 1
&& !exceptAttrs.some(attr => move.hasAttr(attr))
&& !exceptMoves.some(id => move.id === id)
&& move.category !== MoveCategory.STATUS;
}
/**
* If conditions are met, this doubles the move's hit count (via args[1])
* or multiplies the damage of secondary strikes (via args[2])
* @param {Pokemon} pokemon the Pokemon using the move
* @param passive n/a
* @param defender n/a
* @param {Move} move the move used by the ability source
* @param args\[0\] the number of Pokemon this move is targeting
* @param {Utils.IntegerHolder} args\[1\] the number of strikes with this move
* @param {Utils.NumberHolder} args\[2\] the damage multiplier for the current strike
* @returns
*/
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
const numTargets = args[0] as integer;
const hitCount = args[1] as Utils.IntegerHolder;
const multiplier = args[2] as Utils.NumberHolder;
if (this.canApplyPreAttack(move, numTargets)) {
if (!!hitCount?.value) {
hitCount.value *= 2;
}
if (!!multiplier?.value && pokemon.turnData.hitsLeft % 2 === 1 && pokemon.turnData.hitsLeft !== pokemon.turnData.hitCount) {
multiplier.value *= this.damageMultiplier;
}
return true;
}
return false;
}
}
/**
* Class for abilities that boost the damage of moves
* For abilities that boost the base power of moves, see VariableMovePowerAbAttr
@ -2726,15 +2805,11 @@ export class PostWeatherLapseDamageAbAttr extends PostWeatherLapseAbAttr {
}
applyPostWeatherLapse(pokemon: Pokemon, passive: boolean, weather: Weather, args: any[]): boolean {
if (pokemon.getHpRatio() < 1) {
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${abilityName}!`));
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER);
return true;
}
return false;
const scene = pokemon.scene;
const abilityName = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name;
scene.queueMessage(getPokemonMessage(pokemon, ` is hurt\nby its ${abilityName}!`));
pokemon.damageAndUpdate(Math.ceil(pokemon.getMaxHp() / (16 / this.damageFactor)), HitResult.OTHER);
return true;
}
}
@ -4635,7 +4710,7 @@ export function initAbilities() {
new Ability(Abilities.AERILATE, 6)
.attr(MoveTypeChangeAttr, Type.FLYING, 1.2, (user, target, move) => move.type === Type.NORMAL),
new Ability(Abilities.PARENTAL_BOND, 6)
.unimplemented(),
.attr(AddSecondStrikeAbAttr, 0.25),
new Ability(Abilities.DARK_AURA, 6)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => getPokemonMessage(pokemon, " is radiating a Dark Aura!"))
.attr(FieldMoveTypePowerBoostAbAttr, Type.DARK, 4 / 3),
@ -4721,7 +4796,7 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.DISGUISE, 7)
.attr(PreDefendMovePowerToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PreDefendMoveDamageToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PostSummonFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PostDefendFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)

View File

@ -896,6 +896,12 @@ export class ProtectedTag extends BattlerTag {
if (lapseType === BattlerTagLapseType.CUSTOM) {
new CommonBattleAnim(CommonAnim.PROTECT, pokemon).play(pokemon.scene);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsProtectedLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
// Stop multi-hit moves early
const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
effectPhase.stopMultiHit(pokemon);
}
return true;
}

View File

@ -140,8 +140,9 @@ export class Egg {
constructor(eggOptions?: IEggOptions) {
//if (eggOptions.tier && eggOptions.species) throw Error("Error egg can't have species and tier as option. only choose one of them.")
this._tier = eggOptions.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
this._sourceType = eggOptions.sourceType ?? undefined;
// Ensure _sourceType is defined before invoking rollEggTier(), as it is referenced
this._tier = eggOptions.tier ?? (Overrides.EGG_TIER_OVERRIDE ?? this.rollEggTier());
// If egg was pulled, check if egg pity needs to override the egg tier
if (eggOptions.pulled) {
// Needs this._tier and this._sourceType to work
@ -371,7 +372,7 @@ export class Egg {
// If this is the 10th egg without unlocking something new, attempt to force it.
if (scene.gameData.unlockPity[this.tier] >= 9) {
const lockedPool = speciesPool.filter(s => !scene.gameData.dexData[s].caughtAttr);
const lockedPool = speciesPool.filter(s => !scene.gameData.dexData[s].caughtAttr && !scene.gameData.eggs.some(e => e.species === s));
if (lockedPool.length) { // Skip this if everything is unlocked
speciesPool = lockedPool;
}
@ -416,7 +417,7 @@ export class Egg {
}
}
if (!!scene.gameData.dexData[species].caughtAttr) {
if (!!scene.gameData.dexData[species].caughtAttr || scene.gameData.eggs.some(e => e.species === species)) {
scene.gameData.unlockPity[this.tier] = Math.min(scene.gameData.unlockPity[this.tier] + 1, 10);
} else {
scene.gameData.unlockPity[this.tier] = 0;

View File

@ -811,11 +811,14 @@ export class MoveEffectAttr extends MoveAttr {
public trigger: MoveEffectTrigger;
/** Should this effect only apply on the first hit? */
public firstHitOnly: boolean;
/** Should this effect only apply on the last hit? */
public lastHitOnly: boolean;
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false) {
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false) {
super(selfTarget);
this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY;
this.firstHitOnly = firstHitOnly;
this.lastHitOnly = lastHitOnly;
}
/**
@ -1064,7 +1067,7 @@ export class RecoilAttr extends MoveEffectAttr {
private unblockable: boolean;
constructor(useHp: boolean = false, damageRatio: number = 0.25, unblockable: boolean = false) {
super(true);
super(true, MoveEffectTrigger.POST_APPLY, false, true);
this.useHp = useHp;
this.damageRatio = damageRatio;
@ -1085,8 +1088,8 @@ export class RecoilAttr extends MoveEffectAttr {
return false;
}
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.currDamageDealt : user.getMaxHp()) * this.damageRatio),
user.turnData.currDamageDealt ? 1 : 0);
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio),
user.turnData.damageDealt ? 1 : 0);
if (!recoilDamage) {
return false;
}
@ -2013,7 +2016,7 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
* @param ...effects - List of status effects to cure
*/
constructor(selfTarget: boolean, ...effects: StatusEffect[]) {
super(selfTarget);
super(selfTarget, MoveEffectTrigger.POST_APPLY, false, true);
this.effects = effects;
}
@ -2770,7 +2773,7 @@ export class DoublePowerChanceAttr extends VariablePowerAttr {
export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultiplierAttr {
constructor(limit: integer, resetOnFail: boolean, resetOnLimit?: boolean, ...comboMoves: Moves[]) {
super((user: Pokemon, target: Pokemon, move: Move): number => {
const moveHistory = user.getMoveHistory().reverse().slice(1);
const moveHistory = user.getLastXMoves(limit + 1).slice(1);
let count = 0;
let turnMove: TurnMove;
@ -3139,8 +3142,13 @@ export class PunishmentPowerAttr extends VariablePowerAttr {
export class PresentPowerAttr extends VariablePowerAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
/**
* If this move is multi-hit, and this attribute is applied to any hit
* other than the first, this move cannot result in a heal.
*/
const firstHit = (user.turnData.hitCount === user.turnData.hitsLeft);
const powerSeed = Utils.randSeedInt(100);
const powerSeed = Utils.randSeedInt(firstHit ? 100 : 80);
if (powerSeed <= 40) {
(args[0] as Utils.NumberHolder).value = 40;
} else if (40 < powerSeed && powerSeed <= 70) {
@ -3148,6 +3156,8 @@ export class PresentPowerAttr extends VariablePowerAttr {
} else if (70 < powerSeed && powerSeed <= 80) {
(args[0] as Utils.NumberHolder).value = 120;
} else if (80 < powerSeed && powerSeed <= 100) {
// If this move is multi-hit, disable all other hits
user.stopMultiHit();
target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(),
Math.max(Math.floor(target.getMaxHp() / 4), 1), getPokemonMessage(target, " regained\nhealth!"), true));
}
@ -3905,8 +3915,8 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
public turnCountMax: integer;
private failOnOverlap: boolean;
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer) {
super(selfTarget);
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer, lastHitOnly: boolean = false) {
super(selfTarget, MoveEffectTrigger.POST_APPLY, false, lastHitOnly);
this.tagType = tagType;
this.turnCountMin = turnCountMin;
@ -4071,7 +4081,7 @@ export class ConfuseAttr extends AddBattlerTagAttr {
export class RechargeAttr extends AddBattlerTagAttr {
constructor() {
super(BattlerTagType.RECHARGING, true);
super(BattlerTagType.RECHARGING, true, false, 1, 1, true);
}
}
@ -4504,7 +4514,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
private batonPass: boolean;
constructor(user?: boolean, batonPass?: boolean) {
super(false, MoveEffectTrigger.POST_APPLY, true);
super(false, MoveEffectTrigger.POST_APPLY, false, true);
this.user = !!user;
this.batonPass = !!batonPass;
}
@ -4619,7 +4629,7 @@ export class RemoveTypeAttr extends MoveEffectAttr {
private messageCallback: ((user: Pokemon) => void) | undefined;
constructor(removedType: Type, messageCallback?: (user: Pokemon) => void) {
super(true, MoveEffectTrigger.POST_APPLY);
super(true, MoveEffectTrigger.POST_TARGET);
this.removedType = removedType;
this.messageCallback = messageCallback;
@ -5427,7 +5437,7 @@ export class DiscourageFrequentUseAttr extends MoveAttr {
export class MoneyAttr extends MoveEffectAttr {
constructor() {
super(true, MoveEffectTrigger.HIT);
super(true, MoveEffectTrigger.HIT, true);
}
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
@ -6983,7 +6993,7 @@ export function initMoves() {
.target(MoveTarget.BOTH_SIDES)
.unimplemented(),
new AttackMove(Moves.SMACK_DOWN, Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
@ -7367,14 +7377,14 @@ export function initMoves() {
.triageMove(),
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.THOUSAND_WAVES, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.LANDS_WRATH, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
@ -7516,7 +7526,7 @@ export function initMoves() {
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER),
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
.makesContact(false),
new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
.attr(IgnoreOpponentStatChangesAttr),
@ -7560,7 +7570,7 @@ export function initMoves() {
.attr(HealOnAllyAttr, 0.5, true, false)
.ballBombMove(),
new AttackMove(Moves.ANCHOR_SHOT, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7)
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1),
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true),
new StatusMove(Moves.PSYCHIC_TERRAIN, Type.PSYCHIC, -1, 10, -1, 0, 7)
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
.target(MoveTarget.BOTH_SIDES),
@ -8283,7 +8293,7 @@ export function initMoves() {
.attr(RemoveScreensAttr),
new AttackMove(Moves.MAKE_IT_RAIN, Type.STEEL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
.attr(MoneyAttr)
.attr(StatChangeAttr, BattleStat.SPATK, -1, true, null, true, true)
.attr(StatChangeAttr, BattleStat.SPATK, -1, true, null, true, false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.PSYBLADE, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1)

View File

@ -3327,7 +3327,7 @@ export const starterPassiveAbilities = {
[Species.KRABBY]: Abilities.UNBURDEN,
[Species.VOLTORB]: Abilities.ELECTRIC_SURGE,
[Species.EXEGGCUTE]: Abilities.RIPEN,
[Species.CUBONE]: Abilities.HUGE_POWER,
[Species.CUBONE]: Abilities.PARENTAL_BOND,
[Species.LICKITUNG]: Abilities.THICK_FAT,
[Species.KOFFING]: Abilities.PARENTAL_BOND,
[Species.RHYHORN]: Abilities.FILTER,

View File

@ -1,3 +1,5 @@
import { VariantTier } from "#app/enums/variant-tier.js";
export type Variant = 0 | 1 | 2;
export type VariantSet = [Variant, Variant, Variant];
@ -16,3 +18,14 @@ export function getVariantTint(variant: Variant): integer {
return 0xe81048;
}
}
export function getVariantIcon(variant: Variant): integer {
switch (variant) {
case 0:
return VariantTier.STANDARD;
case 1:
return VariantTier.RARE;
case 2:
return VariantTier.EPIC;
}
}

View File

@ -0,0 +1,5 @@
export enum VariantTier {
STANDARD,
RARE,
EPIC
}

View File

@ -10,20 +10,20 @@ import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp";
import { Stat } from "../data/pokemon-stat";
import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonMultiHitModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, EvolutionStatBoosterModifier, TerastallizeModifier } from "../modifier/modifier";
import { AttackTypeBoosterModifier, DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonMultiHitModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, TerastallizeModifier } from "../modifier/modifier";
import { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases";
import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases";
import { BattleStat } from "../data/battle-stat";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, TypeImmuneTag, getBattlerTag, PowerTrickTag } from "../data/battler-tags";
import { WeatherType } from "../data/weather";
import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AllyMoveCategoryPowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr } from "../data/ability";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AllyMoveCategoryPowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AddSecondStrikeAbAttr } from "../data/ability";
import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle";
import { Mode } from "../ui/ui";
@ -665,7 +665,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.isPlayer(), battleStat as integer as TempBattleStat, statLevel);
}
const statValue = new Utils.NumberHolder(this.getStat(stat));
this.scene.applyModifiers(EvolutionStatBoosterModifier, this.isPlayer(), this, stat, statValue);
this.scene.applyModifiers(StatBoosterModifier, this.isPlayer(), this, stat, statValue);
const fieldApplied = new Utils.BooleanHolder(false);
for (const pokemon of this.scene.getField(true)) {
@ -1808,6 +1808,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
if (cancelled.value) {
source.stopMultiHit(this);
result = HitResult.NO_EFFECT;
} else {
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag;
@ -1893,8 +1894,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk);
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef);
const effectPhase = this.scene.getCurrentPhase();
let numTargets = 1;
if (effectPhase instanceof MoveEffectPhase) {
numTargets = effectPhase.getTargets().length;
}
const twoStrikeMultiplier = new Utils.NumberHolder(1);
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, source, this, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier);
if (!isTypeImmune) {
damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value);
damage.value = Math.ceil(((((2 * source.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * ((this.scene.randBattleSeedInt(15) + 85) / 100) * criticalMultiplier.value);
if (isPhysical && source.status && source.status.effect === StatusEffect.BURN) {
if (!move.hasAttr(BypassBurnDamageReductionAttr)) {
const burnDamageReductionCancelled = new Utils.BooleanHolder(false);
@ -1962,11 +1971,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage);
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, power);
if (power.value === 0) {
damage.value = 0;
}
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, damage);
console.log("damage", damage.value, move.name, power.value, sourceAtk, targetDef);
@ -2263,6 +2268,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.summonData.moveQueue;
}
/**
* If this Pokemon is using a multi-hit move, cancels all subsequent strikes
* @param {Pokemon} target If specified, this only cancels subsequent strikes against this Pokemon
*/
stopMultiHit(target?: Pokemon): void {
const effectPhase = this.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
effectPhase.stopMultiHit(target);
}
}
changeForm(formChange: SpeciesFormChange): Promise<void> {
return new Promise(resolve => {
this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0);
@ -3397,7 +3413,7 @@ export class EnemyPokemon extends Pokemon {
this.trainerSlot = trainerSlot;
if (boss) {
this.setBoss();
this.setBoss(boss, dataSource?.bossSegments);
}
if (Overrides.OPP_STATUS_OVERRIDE) {

View File

@ -35,6 +35,7 @@ export interface ModifierTypeTranslationEntry {
export interface ModifierTypeTranslationEntries {
ModifierType: { [key: string]: ModifierTypeTranslationEntry },
SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry },
AttackTypeBoosterItem: SimpleTranslationEntries,
TempBattleStatBoosterItem: SimpleTranslationEntries,
TempBattleStatBoosterStatName: SimpleTranslationEntries,

View File

@ -90,6 +90,7 @@ export class LoadingScene extends SceneBase {
this.loadImage("shiny_star_small", "ui", "shiny_small.png");
this.loadImage("shiny_star_small_1", "ui", "shiny_small_1.png");
this.loadImage("shiny_star_small_2", "ui", "shiny_small_2.png");
this.loadAtlas("shiny_icons", "ui");
this.loadImage("ha_capsule", "ui", "ha_capsule.png");
this.loadImage("champion_ribbon", "ui", "champion_ribbon.png");
this.loadImage("icon_spliced", "ui");

View File

@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "Ein K.O.-Treffer!",
"attackFailed": "Es ist fehlgeschlagen!",
"attackHitsCount": "{{count}}-mal getroffen!",
"rewardGain": "Du erhältst\n{{modifierName}}!",
"expGain": "{{pokemonName}} erhält\n{{exp}} Erfahrungspunkte!",
"levelUp": "{{pokemonName}} erreicht\nLv. {{level}}!",
"learnMove": "{{pokemonName}} erlernt\n{{moveName}}!",
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "Nachtmahr sucht {{pokemonNameWithAffix}} heim!",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}} wird bereits von Nachtmahr heimgesucht!",
"battlerTagsNightmareLapse": "Nachtmahr schadet {{pokemonNameWithAffix}}!",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}} gibt eine Zugabe",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}} gibt eine Zugabe",
"battlerTagsEncoreOnRemove": "Die Zugabe von {{pokemonNameWithAffix}} ist beendet!",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}} will {{pokemonName}} helfen!",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}} nimmt über seine Wurzeln Nährstoffe auf!",

View File

@ -242,6 +242,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_FUSED_CHANCE": { "name": "Fusionsmarke", "description": "Fügt eine 1%ige Chance hinzu, dass ein wildes Pokémon eine Fusion ist." },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "Kugelblitz", description: "Ein Item, das von Pikachu getragen werden kann. Es erhöht den Angriff und den Spezial-Angriff." },
"THICK_CLUB": { name: "Kampfknochen", description: "Ein Item, das von Tragosso oder Knogga getragen werden kann. Dieser harte Knochen erhöht den Angriff." },
"METAL_POWDER": { name: "Metallstaub", description: "Ein Item, das von Ditto getragen werden kann. Fein und doch hart, erhöht dieses sonderbare Pulver die Verteidigung." },
"QUICK_POWDER": { name: "Flottstaub", description: "Ein Item, das Ditto zum Tragen gegeben werden kann. Fein und doch hart, erhöht dieses sonderbare Pulver die Initiative." }
},
TempBattleStatBoosterItem: {
"x_attack": "X-Angriff",
"x_defense": "X-Verteidigung",

View File

@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "It's a one-hit KO!",
"attackFailed": "But it failed!",
"attackHitsCount": "Hit {{count}} time(s)!",
"rewardGain": "You received\n{{modifierName}}!",
"expGain": "{{pokemonName}} gained\n{{exp}} EXP. Points!",
"levelUp": "{{pokemonName}} grew to\nLv. {{level}}!",
"learnMove": "{{pokemonName}} learned\n{{moveName}}!",
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}} began\nhaving a Nightmare!",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}} is\nalready locked in a Nightmare!",
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}} is locked\nin a Nightmare!",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}} got\nan Encore!",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}} got\nan Encore!",
"battlerTagsEncoreOnRemove": "{{pokemonNameWithAffix}}'s Encore\nended!",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}} is ready to\nhelp {{pokemonName}}!",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}} absorbed\nnutrients with its roots!",

View File

@ -499,7 +499,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"galactic_boss_cyrus_1": {
"encounter": {
1: "You were compelled to come here by such vacuous sentimentality\nI will make you regret paying heed to your heart!"
1: "You were compelled to come here by such vacuous sentimentality.\nI will make you regret paying heed to your heart!"
},
"victory": {
1: "Interesting. And quite curious."
@ -1641,7 +1641,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: `Just a moment, please. The book I'm reading has nearly reached its thrilling climax…
$The hero has obtained a mystic sword and is about to face their final trial Ah, never mind.
$Since you've made it this far, I'll put that aside and battle you.
$Let me see if you'll achieve as much glory as the hero of my book!,`
$Let me see if you'll achieve as much glory as the hero of my book!`
},
"victory": {
1: "I see… It appears you've put me in checkmate."

View File

@ -241,6 +241,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_ENDURE_CHANCE": { name: "Endure Token" },
"ENEMY_FUSED_CHANCE": { name: "Fusion Token", description: "Adds a 1% chance that a wild Pokémon will be a fusion." },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "Light Ball", description: "It's a mysterious orb that boosts Pikachu's Attack and Sp. Atk stats." },
"THICK_CLUB": { name: "Thick Club", description: "This hard bone of unknown origin boosts Cubone or Marowak's Attack stat." },
"METAL_POWDER": { name: "Metal Powder", description: "Extremely fine yet hard, this odd powder boosts Ditto's Defense stat." },
"QUICK_POWDER": { name: "Quick Powder", description: "Extremely fine yet hard, this odd powder boosts Ditto's Speed stat." }
},
TempBattleStatBoosterItem: {
"x_attack": "X Attack",
"x_defense": "X Defense",

View File

@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "¡KO en 1 golpe!",
"attackFailed": "¡Pero ha fallado!",
"attackHitsCount": "N.º de golpes: {{count}}.",
"rewardGain": "¡Has obtenido\n{{modifierName}}!",
"expGain": "{{pokemonName}} ha ganado\n{{exp}} puntos de experiencia.",
"levelUp": "¡{{pokemonName}} ha subido al \nNv. {{level}}!",
"learnMove": "¡{{pokemonName}} ha aprendido {{moveName}}!",
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}} began\nhaving a Nightmare!",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}} is\nalready locked in a Nightmare!",
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}} is locked\nin a Nightmare!",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}} got\nan Encore!",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}} got\nan Encore!",
"battlerTagsEncoreOnRemove": "{{pokemonNameWithAffix}}'s Encore\nended!",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}} is ready to\nhelp {{pokemonName}}!",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}} absorbed\nnutrients with its roots!",

View File

@ -241,6 +241,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_ENDURE_CHANCE": { name: "Ficha Aguante" },
"ENEMY_FUSED_CHANCE": { name: "Ficha Fusión", description: "Agrega un 1% de probabilidad de que un Pokémon salvaje sea una fusión." },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "Bola Luminosa", description: "Asombrosa esfera que aumenta el Ataque y el Ataque Especial. Debe llevarla Pikachu." },
"THICK_CLUB": { name: "Hueso Grueso", description: "Extraño tipo de hueso que potencia los ataques físicos. Debe llevarlo Cubone o Marowak." },
"METAL_POWDER": { name: "Polvo Metálico", description: "Polvo muy fino, pero a la vez poderoso, que aumenta la Defensa. Debe llevarlo Ditto." },
"QUICK_POWDER": { name: "Polvo Veloz", description: "Polvo muy fino, pero a la vez poderoso, que aumenta la Velocidad. Debe llevarlo Ditto." }
},
TempBattleStatBoosterItem: {
"x_attack": "Ataque X",
"x_defense": "Defensa X",

View File

@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "K.O. en un coup !",
"attackFailed": "Mais cela échoue !",
"attackHitsCount": "Touché {{count}} fois !",
"rewardGain": "Vous recevez\n{{modifierName}} !",
"expGain": "{{pokemonName}} gagne\n{{exp}} Points dExp !",
"levelUp": "{{pokemonName}} monte au\nN. {{level}} !",
"learnMove": "{{pokemonName}} apprend\n{{moveName}} !",
@ -87,7 +88,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsDestinyBondLapse": "{{pokemonNameWithAffix}} entraine\n{{pokemonNameWithAffix2}} dans sa chute !",
"battlerTagsInfatuatedOnAdd": "{{pokemonNameWithAffix}} est amoureux\nde {{sourcePokemonName}} !",
"battlerTagsInfatuatedOnOverlap": "{{pokemonNameWithAffix}} est\ndéjà amoureux !",
"battlerTagsInfatuatedLapse": "{{pokemonNameWithAffix}}] est amoureux\nde {{sourcePokemonName}} !",
"battlerTagsInfatuatedLapse": "{{pokemonNameWithAffix}} est amoureux\nde {{sourcePokemonName}} !",
"battlerTagsInfatuatedLapseImmobilize": "Lamour empêche {{pokemonNameWithAffix}}\ndagir !",
"battlerTagsInfatuatedOnRemove": "{{pokemonNameWithAffix}}\nnest plus amoureux !",
"battlerTagsSeededOnAdd": "{{pokemonNameWithAffix}} est infecté !",
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}} commence à cauchemarder !",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}} est\ndéjà prisonnier dun cauchemar !",
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}}est\nprisonnier dun cauchemar !",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}} !\nEncore une fois !",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}} !\nEncore une fois !",
"battlerTagsEncoreOnRemove": "{{pokemonNameWithAffix}} nest\nplus obligé dutiliser la même capacité !",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}} est prêt\nà aider {{pokemonName}} !",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}} absorbe\ndes nutriments avec ses racines !",

View File

@ -2367,7 +2367,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"victory": {
1: `@c{neutral}Je… Jétais pas encore supposée perdre…
$@c{smile}Bon. Ça veut juste dire que je vois devoir encore plus mentrainer !
$@c{smile}Bon. Ça veut juste dire que je vais devoir encore plus mentrainer !
$@c{smile_wave}Jai aussi ça en rab pour toi !\n@c{smile_wave_wink}Inutile de me remercier ~.
$@c{angry_mopen}Cétait le dernier, terminé les cadeaux après celui- !
$@c{smile_wave}Allez, tiens le coup !`
@ -4849,7 +4849,7 @@ export const PGFdialogue: DialogueTranslationEntries = {
},
"victory": {
1: `@c{neutral}Je… Jétais pas encore supposée perdre…
$@c{smile}Bon. Ça veut juste dire que je vois devoir encore plus mentrainer !
$@c{smile}Bon. Ça veut juste dire que je vais devoir encore plus mentrainer !
$@c{smile_wave}Jai aussi ça en rab pour toi !\n@c{smile_wave_wink}Inutile de me remercier ~.
$@c{angry_mopen}Cétait le dernier, terminé les cadeaux après celui- !
$@c{smile_wave}Allez, tiens le coup !`

View File

@ -241,6 +241,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_ENDURE_CHANCE": { name: "Jeton Ténacité" },
"ENEMY_FUSED_CHANCE": { name: "Jeton Fusion", description: "Ajoute 1% de chances quun Pokémon sauvage soit une fusion." },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "Balle Lumière", description: "Objet à faire tenir à Pikachu. Un orbe énigmatique qui augmente son Attaque et son Attaque Spéciale." },
"THICK_CLUB": { name: "Masse Os", description: "Objet à faire tenir à Osselait ou Ossatueur. Un os dur qui augmente leur Attaque." },
"METAL_POWDER": { name: "Poudre Métal", description: "Objet à faire tenir à Métamorph. Cette poudre étrange, très fine mais résistante, augmente sa Défense." },
"QUICK_POWDER": { name: "Poudre Vite", description: "Objet à faire tenir à Métamorph. Cette poudre étrange, très fine mais résistante, augmente sa Vitesse." }
},
TempBattleStatBoosterItem: {
"x_attack": "Attaque +",
"x_defense": "Défense +",

View File

@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "KO con un colpo!",
"attackFailed": "Ma ha fallito!",
"attackHitsCount": "Colpito {{count}} volta/e!",
"rewardGain": "You received\n{{modifierName}}!",
"expGain": "{{pokemonName}} ha guadagnato\n{{exp}} Punti Esperienza!",
"levelUp": "{{pokemonName}} è salito al\nlivello {{level}}!",
"learnMove": "{{pokemonName}} impara\n{{moveName}}!",
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}} sta\navendo un Incubo!",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}} sta\ngià avendo un Incubo!",
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}} è bloccato\nin un Incubo!",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}} ha\nsubito Ripeti!",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}} ha\nsubito Ripeti!",
"battlerTagsEncoreOnRemove": "L'effetto di Ripeti su {{pokemonNameWithAffix}}\n è terminato!",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}} è pronto ad\naiutare {{pokemonName}}!",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}} assorbe\nnutrienti dalle sue radici!",

View File

@ -241,6 +241,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_ENDURE_CHANCE": { name: "Gettone di Resistenza" },
"ENEMY_FUSED_CHANCE": { name: "Gettone della fusione", description: "Aggiunge l'1% di possibilità che un Pokémon selvatico sia una fusione." },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "Elettropalla", description: "Strumento da dare a Pikachu. Sfera insolita che aumenta lAttacco e lAttacco Speciale." },
"THICK_CLUB": { name: "Osso spesso", description: "Strumento da dare a Cubone o Marowak. Osso duro che aumenta lAttacco." },
"METAL_POWDER": { name: "Metalpolvere", description: "Strumento da dare a Ditto. Strana polvere finissima e al tempo stesso dura che migliora la Difesa." },
"QUICK_POWDER": { name: "Velopolvere", description: "Strumento da dare a Ditto. Questa strana polvere, fine e al contempo dura, aumenta la Velocità." }
},
TempBattleStatBoosterItem: {
"x_attack": "Attacco X",
"x_defense": "Difesa X",

View File

@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "일격필살!",
"attackFailed": "하지만 실패했다!",
"attackHitsCount": "{{count}}번 맞았다!",
"rewardGain": "{{modifierName}}[[를]] 받았다!",
"expGain": "{{pokemonName}}[[는]]\n{{exp}} 경험치를 얻었다!",
"levelUp": "{{pokemonName}}[[는]]\n레벨 {{level}}[[로]] 올랐다!",
"learnMove": "{{pokemonName}}[[는]] 새로\n{{moveName}}[[를]] 배웠다!",

View File

@ -1260,68 +1260,68 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"sidney": {
"encounter": {
1: `I like that look you're giving me. I guess you'll give me a good match.
$That's good! Looking real good! All right!
$You and me, let's enjoy a battle that can only be staged here!`,
1: `음, 좋은 표정이야. 꽤나 즐길 수 있겠는데.
$좋아! ! !
$우리 , !`,
},
"victory": {
1: "Well, how do you like that? I lost! Eh, it was fun, so it doesn't matter."
1: "이런, 이런 져버렸군? 뭐, 꽤 즐겼으니 상관없지만."
},
"defeat": {
1: "No hard feelings, alright?"
1: "기분 나빠하지 마, 알겠지?"
}
},
"phoebe": {
"encounter": {
1: `While I trained, I gained the ability to commune with Ghost-type Pokémon.
$Yes, the bond I developed with Pokémon is extremely tight.
$So, come on, just try and see if you can even inflict damage on my Pokémon!`,
1: `송화산에서 수행하면서, 고스트 타입 포켓몬과 마음이 통하게 됐어.
$, !
$이런 !`,
},
"victory": {
1: "Oh, darn. I've gone and lost."
1: "아- 아, 내가 져버렸다."
},
"defeat": {
1: "I look forward to battling you again sometime!"
1: "언젠가 다시 승부할 수 있기를 기대할게!"
}
},
"glacia": {
"encounter": {
1: `All I have seen are challenges by weak Trainers and their Pokémon.
$What about you? It would please me to no end if I could go all out against you!`,
1: `이곳에 도전하러 오는 건 모두 어설픈 트레이너와 포켓몬뿐….
$당신은 ? !`,
},
"victory": {
1: `You and your Pokémon… How hot your spirits burn!
$The all-consuming heat overwhelms.
$It's no surprise that my icy skills failed to harm you.`,
1: `당신과… 당신 포켓몬들의 뜨거운 혼!
$정말로 .
$ !`,
},
"defeat": {
1: "A fiercely passionate battle, indeed."
1: "저런, 정말로 치열한 승부였네요."
}
},
"drake": {
"encounter": {
1: `For us to battle with Pokémon as partners, do you know what it takes? Do you know what is needed?
$If you don't, then you will never prevail over me!`,
1: `파트너로 포켓몬과 함께하는 승부에 무엇이 필요한지 넌 알고 있는가?
$그걸 !`,
},
"victory": {
1: "Superb, it should be said."
1: "훌륭하다, 라고 할 만 하군!"
},
"defeat": {
1: "I gave my all for that battle!"
1: "난 승부에서 최선을 다했으니까!"
}
},
"wallace": {
"encounter": {
1: `There's something about you… A difference in your demeanor.
$I think I sense that in you. Now, show me. Show me the power you wield with your Pokémon.
$And I, in turn, shall present you with a performance of illusions in water by me and my Pokémon!`,
1: `뭐랄까, 너의 분위기가 조금 변한 것 같은…
$그런 . , ? .
$그리고 . !`,
},
"victory": {
1: `Bravo. I realize now your authenticity and magnificence as a Pokémon Trainer.
$I find much joy in having met you and your Pokémon. You have proven yourself worthy.`,
1: `훌륭하군. 넌 정말 굉장한 포켓몬 트레이너다.
$그런 . !`,
},
"defeat": {
1: "A grand illusion!"
1: "거대한 일루전이로군!"
}
},
"lorelei": {
@ -1362,25 +1362,25 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"hala": {
"encounter": {
1: "Old Hala is here to make you holler!"
1: "그럼…진심을 담아서 진지한 할라로 임하겠다!"
},
"victory": {
1: "I could feel the power you gained on your journey."
1: "네가 순례하면서 갖추게 된 강함을 느낄 수 있었다."
},
"defeat": {
1: "Haha! What a delightful battle!"
1: "하하! 경쾌한 승부였구나!"
}
},
"molayne": {
"encounter": {
1: `I gave the captain position to my cousin Sophocles, but I'm confident in my ability.
$My strength is like that of a supernova!`,
1: `사촌인 마마네에게 캡틴 자리는 줬지만, 아직 실력에는 자신 있어요.
$ !`,
},
"victory": {
1: "I certainly found an interesting Trainer to face!"
1: "확실히 겨룰 흥미로운 트레이너를 찾았네요!"
},
"defeat": {
1: "Ahaha. What an interesting battle."
1: "아하하. 흥미로운 배틀이었네요."
}
},
"rika": {
@ -1476,13 +1476,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"olivia": {
"encounter": {
1: "No introduction needed here. Time to battle me, Olivia!"
1: "여기에 소개는 필요 없지. 자, 라이치님과 승부할 시간이다!"
},
"victory": {
1: "Really lovely… Both you and your Pokémon…"
1: "정말 훌륭하군… 당신과 포켓몬 둘 다…"
},
"defeat": {
1: "Mmm-hmm."
1: "흐-음."
}
},
"poppy": {
@ -1571,13 +1571,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"acerola": {
"encounter": {
1: "Battling is just plain fun! Come on, I can take you!"
1: "포켓몬 배틀은 언제나 재미있지! 자, 내가 상대해줄게!"
},
"victory": {
1: "I'm… I'm speechless! How did you do it?!"
1: "아세로라… 입이 딱 벌어졌어! 어떻게 해낸 거야?!"
},
"defeat": {
1: "Ehaha! What an amazing victory!"
1: "후아~! 놀라운 승리네!"
}
},
"larry_elite": {
@ -1664,13 +1664,13 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"kahili": {
"encounter": {
1: "So, here you are… Why don't we see who the winds favor today, you… Or me?"
1: "자, 여기에서… 승리의 바람이 부는 쪽은 당신과 저 중에 어느 쪽일까요?"
},
"victory": {
1: "It's frustrating to me as a member of the Elite Four, but it seems your strength is the real deal."
1: "사천왕으로서 분하지만 당신들의 강함은 진정한 강함이군요."
},
"defeat": {
1: "That was an ace!"
1: "이것이 에이스니까요!"
}
},
"hassel": {
@ -1742,17 +1742,17 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"steven": {
"encounter": {
1: `Tell me… What have you seen on your journey with your Pokémon?
$What have you felt, meeting so many other Trainers out there?
$Traveling this rich land Has it awoken something inside you?
$I want you to come at me with all that you've learned.
$My Pokémon and I will respond in turn with all that we know!`,
1: `넌… 포켓몬과 함께 모험을 하면서 무엇을 봤지?
$많은 ?
$풍요로운 ,
$ .
$나와 !`,
},
"victory": {
1: "So I, the Champion, fall in defeat…"
1: "챔피언인 내가 질 줄이야…"
},
"defeat": {
1: "That was time well spent! Thank you!"
1: "덕분에 즐거웠어! 고마워!"
}
},
"cynthia": {
@ -1784,37 +1784,37 @@ export const PGMdialogue: DialogueTranslationEntries = {
},
"hau": {
"encounter": {
1: `I wonder if a Trainer battles differently depending on whether they're from a warm region or a cold region.
$Let's test it out!`,
1: `트레이너가 따뜻한 지역 출신인지 추운 지역 출신인지에 따라 배틀 스타일이 달라지는지 궁금해졌어.
$그럼 ~!`,
},
"victory": {
1: "That was awesome! I think I kinda understand your vibe a little better now!"
1: "멋진데~! 이제 너의 스타일을 조금 더 잘 알게된 것 같아!"
},
"defeat": {
1: "Ma-an, that was some kinda battle!"
1: "이런, 그건 그냥 승부였는데~!"
}
},
"geeta": {
"encounter": {
1: `I decided to throw my hat in the ring once more.
$Come now Show me the fruits of your training.`,
1: `그 도전장, 한 번 더 승낙하도록 하죠.
$ .`,
},
"victory": {
1: "I eagerly await news of all your achievements!"
1: "당신이 이룰 업적에 대한 소식들, 기다리고 있겠습니다!"
},
"defeat": {
1: "What's the matter? This isn't all, is it?"
1: "무슨 문제라도 있나요? 이게 전부라니, 그럴리 없잖아요?"
}
},
"nemona": {
"encounter": {
1: "Yesss! I'm so psyched! Time for us to let loose!"
1: "만세! 나 너무 기대돼! 이제 전력으로 승부하는거야~!"
},
"victory": {
1: "Well, that stinks, but I still had fun! I'll getcha next time!"
1: "우와, 조금 문제 있지만, 그래도 너무 재밌었어! 다음에는 지지 않을거야!"
},
"defeat": {
1: "Well, that was a great battle! Fruitful for sure."
1: "우와, 너무 멋진 승부였어! 정말로 강하네."
}
},
"leon": {

View File

@ -241,6 +241,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_ENDURE_CHANCE": { name: "버티기 토큰" },
"ENEMY_FUSED_CHANCE": { name: "합체 토큰", description: "야생 포켓몬이 합체되어 등장할 확률이 1% 추가된다." },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "전기구슬", description: "피카츄에게 지니게 하면 공격과 특수공격이 올라가는 이상한 구슬." },
"THICK_CLUB": { name: "굵은뼈", description: "무언가의 단단한 뼈. 탕구리 혹은 텅구리에게 지니게 하면 공격이 올라간다." },
"METAL_POWDER": { name: "금속파우더", description: "메타몽에게 지니게 하면 방어가 올라가는 이상한 가루. 매우 잘고 단단하다." },
"QUICK_POWDER": { name: "스피드파우더", description: "메타몽에게 지니게 하면 스피드가 올라가는 이상한 가루. 매우 잘고 단단하다." }
},
TempBattleStatBoosterItem: {
"x_attack": "플러스파워",
"x_defense": "디펜드업",

View File

@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "Foi um nocaute de um golpe!",
"attackFailed": "Mas falhou!",
"attackHitsCount": "Acertou {{count}} vezes.",
"rewardGain": "Você recebeu\n{{modifierName}}!",
"expGain": "{{pokemonName}} ganhou\n{{exp}} pontos de experiência.",
"levelUp": "{{pokemonName}} subiu para \nNv. {{level}}!",
"learnMove": "{{pokemonName}} aprendeu {{moveName}}!",
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}} começou\na ter um Nightmare!",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}} já\nestá preso em um Nightmare!",
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}} está preso\nem um Nightmare!",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}} ganhou\num Encore!",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}} ganhou\num Encore!",
"battlerTagsEncoreOnRemove": "O Encore de {{pokemonNameWithAffix}}\nacabou!",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}} está pronto para\najudar {{pokemonName}}!",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}} absorveu\nnutrientes com suas raízes!",

View File

@ -182,6 +182,8 @@ export const modifierType: ModifierTypeTranslationEntries = {
"SOOTHE_BELL": { name: "Guizo" },
"EVIOLITE": { name: "Eviolita", description: "Esse misterioso caroço evolutivo aumenta os atributos de Defesa e Def. Esp. quando segurado por um Pokémon que ainda pode evoluir." },
"SOUL_DEW": { name: "Joia da Alma", description: "Aumenta a influência da natureza de um Pokémon em seus atributos em 10% (cumulativo)." },
"NUGGET": { name: "Pepita" },
@ -239,6 +241,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_ENDURE_CHANCE": { name: "Token de Persistência" },
"ENEMY_FUSED_CHANCE": { name: "Token de Fusão", description: "Adiciona uma chance de 1% de que um Pokémon selvagem seja uma fusão." },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "Bola de Luz", description: "Orbe intrigante que aumenta os atributos de Ataque e Ataque Esp. de Pikachu." },
"THICK_CLUB": { name: "Osso Grosso", description: "Este duro osso de origem desconhecida aumenta o atributo de Ataque de Cubone ou Marowak." },
"METAL_POWDER": { name: "Pó Metálico", description: "Extremamente fino, porém duro, este pó estranho aumenta o atributo de Defesa de Ditto." },
"QUICK_POWDER": { name: "Pó Veloz", description: "Extremamente fino, porém duro, este pó estranho aumenta o atributo de Velocidade de Ditto." }
},
TempBattleStatBoosterItem: {
"x_attack": "Ataque X",
"x_defense": "Defesa X",

View File

@ -7,5 +7,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
"iceFaceAvoidedDamage": "{{pokemonName}} 因为 {{abilityName}}\n避免了伤害",
"trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!",
"windPowerCharged": "受 {{moveName}} 的影响, {{pokemonName}} 提升了能力!",
"quickDraw":"{{pokemonName}} can act faster than normal, thanks to its Quick Draw!",
"quickDraw":"因为速击效果发动,\n{{pokemonName}}比平常出招更快了!",
} as const;

View File

@ -127,7 +127,7 @@ export const PGMachv: AchievementTranslationEntries = {
},
"CATCH_SUB_LEGENDARY": {
name: "二级传说",
description: "捕捉一只传说宝可梦",
description: "捕捉一只二级传说宝可梦",
},
"CATCH_LEGENDARY": {
name: "传说",

View File

@ -15,7 +15,7 @@ export const battle: SimpleTranslationEntries = {
"trainerDefeated": "你击败了\n{{trainerName}}",
"moneyWon": "你赢得了\n₽{{moneyAmount}}",
"pokemonCaught": "{{pokemonName}} 被抓住了!",
"addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!",
"addedAsAStarter": "增加了{{pokemonName}}作为\n一个新的基础宝可梦",
"partyFull": "你的队伍已满员.是否放生其他宝可梦\n为 {{pokemonName}} 腾出空间?",
"pokemon": "宝可梦",
"sendOutPokemon": "上吧!\n{{pokemonName}}",
@ -26,6 +26,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "一击必杀!",
"attackFailed": "但是失败了!",
"attackHitsCount": "击中 {{count}} 次!",
"rewardGain": "你获得了\n{{modifierName}}",
"expGain": "{{pokemonName}} 获得了 {{exp}} 经验值!",
"levelUp": "{{pokemonName}} 升级到 Lv.{{level}}",
"learnMove": "{{pokemonName}} 学会了 {{moveName}}",
@ -54,8 +55,8 @@ export const battle: SimpleTranslationEntries = {
"escapeVerbSwitch": "切换",
"escapeVerbFlee": "逃跑",
"notDisabled": "{{moveName}} 不再被禁用!",
"turnEndHpRestore": "{{pokemonName}}'s HP was restored.",
"hpIsFull": "{{pokemonName}}'s\nHP is full!",
"turnEndHpRestore": "{{pokemonName}}的体力恢复了。",
"hpIsFull": "{{pokemonName}}的体力已满!",
"skipItemQuestion": "你确定要跳过拾取道具吗?",
"eggHatching": "咦?",
"ivScannerUseQuestion": "对 {{pokemonName}} 使用个体值扫描仪?",
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}}开始做恶梦了!",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}}已经被恶梦缠身!",
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}}正被恶梦缠身!",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}}接受了再来一次!",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}}接受了再来一次!",
"battlerTagsEncoreOnRemove": "{{pokemonNameWithAffix}}的再来一次状态解除了!",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}}摆出了帮助{{pokemonName}} 的架势!",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}}用扎根回复了体力!",

View File

@ -73,16 +73,16 @@ export const bgmName: SimpleTranslationEntries = {
"battle_wild": "黑白「战斗!野生宝可梦」",
"battle_wild_strong": "黑白「战斗!强大野生宝可梦」",
"end_summit": "探险队DX 「天空之柱 顶层」",
"battle_rocket_grunt": "HGSS Team Rocket Battle",
"battle_aqua_magma_grunt": "ORAS Team Aqua & Magma Battle",
"battle_galactic_grunt": "BDSP Team Galactic Battle",
"battle_rocket_grunt": "心金魂银「战斗!火箭队」",
"battle_aqua_magma_grunt": "Ω红宝石α蓝宝石「战斗!熔岩队・海洋队」",
"battle_galactic_grunt": "晶灿钻石·明亮珍珠「战斗!银河队」",
"battle_plasma_grunt": "黑白「战斗!等离子团」",
"battle_flare_grunt": "XY Team Flare Battle",
"battle_rocket_boss": "USUM Giovanni Battle",
"battle_aqua_magma_boss": "ORAS Archie & Maxie Battle",
"battle_galactic_boss": "BDSP Cyrus Battle",
"battle_plasma_boss": "B2W2 Ghetsis Battle",
"battle_flare_boss": "XY Lysandre Battle",
"battle_flare_grunt": "XY「战斗!闪焰队」",
"battle_rocket_boss": "究极日月「战斗!坂木」",
"battle_aqua_magma_boss": "Ω红宝石α蓝宝石「战斗!水梧桐・赤焰松」",
"battle_galactic_boss": "晶灿钻石·明亮珍珠「战斗!赤日」",
"battle_plasma_boss": "黑2白2「战斗魁奇思」",
"battle_flare_boss": "XY「战斗!弗拉达利」",
// Biome Music
"abyss": "空之探险队「黑暗小丘」",

View File

@ -387,7 +387,7 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "你要有麻烦了!"
},
"victory": {
1: "火箭队又起飞了"
1: "好讨厌的感觉啊"
},
},
"magma_grunt": {
@ -1624,10 +1624,10 @@ export const PGMdialogue: DialogueTranslationEntries = {
1: "让你亲身感受一下什么叫做猛烈的对战气息吧!",
},
"victory": {
1: "这次幸运之神对我微笑了,但是……$谁知道我下次会不会这么幸运。",
1: "这次幸运之神对你微笑了,但是……$谁知道你下次还会不会这么幸运。",
},
"defeat": {
1: "那可真厉害",
1: "那挺厉害的吧",
}
},
"blue": {

View File

@ -18,7 +18,7 @@ export const egg: SimpleTranslationEntries = {
"tooManyEggs": "你的蛋太多啦!",
"pull": "次",
"pulls": "次",
"sameSpeciesEgg": "{{species}} will hatch from this egg!",
"sameSpeciesEgg": "{{species}}将会从蛋中孵化!",
"hatchFromTheEgg": "{{pokemonName}} 从蛋中孵化了!",
"eggMoveUnlock": "蛋招式已解锁: {{moveName}}",
"rareEggMoveUnlock": "稀有蛋招式已解锁: {{moveName}}",

View File

@ -19,6 +19,6 @@ export const menuUiHandler: SimpleTranslationEntries = {
"importData": "导入数据",
"exportData": "导出数据",
"cancel": "取消",
"losingProgressionWarning": "你将失去自战斗开始以来的所有进度。是否\n继续?",
"noEggs": "You are not hatching\nany eggs at the moment!"
"losingProgressionWarning": "你将失去自战斗开始以来的所有进度。\n是否继续?",
"noEggs": "当前没有任何蛋\n正在孵化中"
} as const;

View File

@ -53,5 +53,5 @@ export const menu: SimpleTranslationEntries = {
"no": "否",
"disclaimer": "免责声明",
"disclaimerDescription": "这个游戏尚未完成; 可能存在游戏性问题(包括潜在的丢档风险)、\n 不经通知的调整、 未来可能会更新或完成更多内容",
"choosePokemon": "Choose a Pokémon.",
"choosePokemon": "选择一只宝可梦。",
} as const;

View File

@ -241,6 +241,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
"ENEMY_ENDURE_CHANCE": { name: "忍受硬币" },
"ENEMY_FUSED_CHANCE": { name: "融合硬币", description: "增加1%野生融合宝可梦出现概率。" },
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "电气球", description: "让皮卡丘携带后,攻击和特攻就会 提高的神奇之球。" },
"THICK_CLUB": { name: "粗骨头", description: "某种坚硬的骨头。让卡拉卡拉或嘎啦嘎啦携带后,攻击就会提高。" },
"METAL_POWDER": { name: "金属粉", description: "让百变怪携带后,防御就会提高的神奇粉末。非常细腻坚硬。" },
"QUICK_POWDER": { name: "速度粉", description: "让百变怪携带后,速度就会提高的神奇粉末。非常细腻坚硬。" }
},
TempBattleStatBoosterItem: {
"x_attack": "力量强化",
"x_defense": "防御强化",

View File

@ -31,7 +31,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
"selectMoveSwapWith": "选择要替换成的招式",
"unlockPassive": "解锁被动",
"reduceCost": "降低花费",
"sameSpeciesEgg": "Buy an Egg",
"sameSpeciesEgg": "兑换一颗蛋",
"cycleShiny": ": 闪光",
"cycleForm": ": 形态",
"cycleGender": ": 性别",

View File

@ -13,6 +13,12 @@ export const titles: SimpleTranslationEntries = {
"rival": "劲敌",
"professor": "博士",
"frontier_brain": "开拓头脑",
"rocket_boss": "火箭队老大",
"magma_boss": "熔岩队老大",
"aqua_boss": "海洋队老大",
"galactic_boss": "银河队老大",
"plasma_boss": "等离子队老大",
"flare_boss": "闪焰队老大",
// Maybe if we add the evil teams we can add "Team Rocket" and "Team Aqua" etc. here as well as "Team Rocket Boss" and "Team Aqua Admin" etc.
} as const;
@ -118,7 +124,19 @@ export const trainerClasses: SimpleTranslationEntries = {
"worker": "工人",
"worker_female": "工人",
"workers": "工人组合",
"youngster": "短裤小子"
"youngster": "短裤小子",
"rocket_grunt": "火箭队手下",
"rocket_grunt_female": "火箭队手下",
"magma_grunt": "熔岩队手下",
"magma_grunt_female": "熔岩队手下",
"aqua_grunt": "海洋队手下",
"aqua_grunt_female": "海洋队手下",
"galactic_grunt": "银河队手下",
"galactic_grunt_female": "银河队手下",
"plasma_grunt": "等离子队手下",
"plasma_grunt_female": "等离子队手下",
"flare_grunt": "闪焰队手下",
"flare_grunt_female": "闪焰队手下",
} as const;
// Names of special trainers like gym leaders, elite four, and the champion
@ -304,6 +322,13 @@ export const trainerNames: SimpleTranslationEntries = {
"rival": "芬恩",
"rival_female": "艾薇",
// ---- 组织老大 Bosses ----
"maxie": "赤焰松",
"archie": "水梧桐",
"cyrus": "赤日",
"ghetsis": "魁奇思",
"lysandre": "弗拉达利",
// Double Names
"blue_red_double": "青绿 & 赤红",

View File

@ -23,6 +23,7 @@ export const battle: SimpleTranslationEntries = {
"hitResultOneHitKO": "一擊切殺!",
"attackFailed": "但是失敗了!",
"attackHitsCount": "擊中 {{count}} 次!",
"rewardGain": "You received\n{{modifierName}}!",
"expGain": "{{pokemonName}} 獲得了 {{exp}} 經驗值!",
"levelUp": "{{pokemonName}} 升級到 Lv. {{level}}",
"learnMove": "{{pokemonName}} 學會了{{moveName}}",
@ -93,7 +94,7 @@ export const battle: SimpleTranslationEntries = {
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}}開始做惡夢了!",
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}}已經被惡夢纏身!",
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}}正被惡夢纏身!",
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}}接受了再來一次!",
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}}接受了再來一次!",
"battlerTagsEncoreOnRemove": "{{pokemonNameWithAffix}}的再來一次狀態解除了!",
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}}擺出了幫助{{pokemonName}} 的架勢!",
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}}用扎根回復了體力!",

View File

@ -301,6 +301,12 @@ export const modifierType: ModifierTypeTranslationEntries = {
description: "增加1%野生融合寶可夢出現概率。",
},
},
SpeciesBoosterItem: {
"LIGHT_BALL": { name: "電氣球", description: "讓皮卡丘攜帶後,攻擊和特攻就會 提高的神奇之球。" },
"THICK_CLUB": { name: "粗骨頭", description: "某種堅硬的骨頭。讓卡拉卡拉或嘎啦嘎啦攜帶後,攻擊就會提高。" },
"METAL_POWDER": { name: "金屬粉", description: "讓百變怪攜帶後,防禦就會提高的神奇粉末。非常細緻堅硬。" },
"QUICK_POWDER": { name: "速度粉", description: "讓百變怪攜帶後,速度就會提高的神奇粉末。非常細緻堅硬。" }
},
TempBattleStatBoosterItem: {
x_attack: "力量強化",
x_defense: "防禦強化",

View File

@ -26,6 +26,7 @@ import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
const outputModifierData = false;
const useMaxWeightForOutput = false;
@ -539,6 +540,28 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i
}
}
export type SpeciesStatBoosterItem = keyof typeof SpeciesStatBoosterModifierTypeGenerator.items;
/**
* Modifier type for {@linkcode Modifiers.SpeciesStatBoosterModifier}
* @extends PokemonHeldItemModifierType
* @implements GeneratedPersistentModifierType
*/
export class SpeciesStatBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
private key: SpeciesStatBoosterItem;
constructor(key: SpeciesStatBoosterItem) {
const item = SpeciesStatBoosterModifierTypeGenerator.items[key];
super(`modifierType:SpeciesBoosterItem.${key}`, key.toLowerCase(), (type, args) => new Modifiers.SpeciesStatBoosterModifier(type, (args[0] as Pokemon).id, item.stats, item.multiplier, item.species));
this.id = this.key = key;
}
getPregenArgs(): any[] {
return [ this.key ];
}
}
export class PokemonLevelIncrementModifierType extends PokemonModifierType {
constructor(localeKey: string, iconImage: string) {
super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), (_pokemon: PlayerPokemon) => null);
@ -870,6 +893,81 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
}
}
/**
* Modifier type generator for {@linkcode SpeciesStatBoosterModifierType}, which
* encapsulates the logic for weighting the most useful held item from
* the current list of {@linkcode items}.
* @extends ModifierTypeGenerator
*/
class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator {
/** Object comprised of the currently available species-based stat boosting held items */
public static items = {
LIGHT_BALL: { stats: [Stat.ATK, Stat.SPATK], multiplier: 2, species: [Species.PIKACHU] },
THICK_CLUB: { stats: [Stat.ATK], multiplier: 2, species: [Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK] },
METAL_POWDER: { stats: [Stat.DEF], multiplier: 2, species: [Species.DITTO] },
QUICK_POWDER: { stats: [Stat.SPD], multiplier: 2, species: [Species.DITTO] },
};
constructor() {
super((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs) {
return new SpeciesStatBoosterModifierType(pregenArgs[0] as SpeciesStatBoosterItem);
}
const values = Object.values(SpeciesStatBoosterModifierTypeGenerator.items);
const keys = Object.keys(SpeciesStatBoosterModifierTypeGenerator.items);
const weights = keys.map(() => 0);
for (const p of party) {
const speciesId = p.getSpeciesForm(true).speciesId;
const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null;
const hasFling = p.getMoveset(true).some(m => m.moveId === Moves.FLING);
for (const i in values) {
const checkedSpecies = values[i].species;
const checkedStats = values[i].stats;
// If party member already has the item being weighted currently, skip to the next item
const hasItem = p.getHeldItems().some(m => m instanceof Modifiers.SpeciesStatBoosterModifier
&& (m as Modifiers.SpeciesStatBoosterModifier).contains(checkedSpecies[0], checkedStats[0]));
if (!hasItem) {
if (checkedSpecies.includes(speciesId) || (!!fusionSpeciesId && checkedSpecies.includes(fusionSpeciesId))) {
// Add weight if party member has a matching species or, if applicable, a matching fusion species
weights[i]++;
} else if (checkedSpecies.includes(Species.PIKACHU) && hasFling) {
// Add weight to Light Ball if party member has Fling
weights[i]++;
}
}
}
}
let totalWeight = 0;
for (const weight of weights) {
totalWeight += weight;
}
if (totalWeight !== 0) {
const randInt = Utils.randSeedInt(totalWeight, 1);
let weight = 0;
for (const i in weights) {
if (weights[i] !== 0) {
const curWeight = weight + weights[i];
if (randInt <= weight + weights[i]) {
return new SpeciesStatBoosterModifierType(keys[i] as SpeciesStatBoosterItem);
}
weight = curWeight;
}
}
}
return null;
});
}
}
class TmModifierTypeGenerator extends ModifierTypeGenerator {
constructor(tier: ModifierTier) {
super((party: Pokemon[]) => {
@ -1110,6 +1208,8 @@ export const modifierTypes = {
SUPER_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.SUPER_LURE", "super_lure", 10),
MAX_LURE: () => new DoubleBattleChanceBoosterModifierType("modifierType:ModifierType.MAX_LURE", "max_lure", 25),
SPECIES_STAT_BOOSTER: () => new SpeciesStatBoosterModifierTypeGenerator(),
TEMP_STAT_BOOSTER: () => new ModifierTypeGenerator((party: Pokemon[], pregenArgs?: any[]) => {
if (pregenArgs) {
return new TempBattleStatBoosterModifierType(pregenArgs[0] as TempBattleStat);
@ -1285,12 +1385,13 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.LURE, 2),
new WeightedModifierType(modifierTypes.TEMP_STAT_BOOSTER, 4),
new WeightedModifierType(modifierTypes.BERRY, 2),
new WeightedModifierType(modifierTypes.TM_COMMON, 1),
new WeightedModifierType(modifierTypes.TM_COMMON, 2),
].map(m => {
m.setTier(ModifierTier.COMMON); return m;
}),
[ModifierTier.GREAT]: [
new WeightedModifierType(modifierTypes.GREAT_BALL, 6),
new WeightedModifierType(modifierTypes.PP_UP, 2),
new WeightedModifierType(modifierTypes.FULL_HEAL, (party: Pokemon[]) => {
const statusEffectPartyMemberCount = Math.min(party.filter(p => p.hp && !!p.status && !p.getHeldItems().some(i => {
if (i instanceof Modifiers.TurnStatusEffectModifier) {
@ -1344,7 +1445,7 @@ const modifierPool: ModifierPool = {
return Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15), 8);
}, 8),
new WeightedModifierType(modifierTypes.MAP, (party: Pokemon[]) => party[0].scene.gameMode.isClassic && party[0].scene.currentBattle.waveIndex < 180 ? 1 : 0, 1),
new WeightedModifierType(modifierTypes.TM_GREAT, 2),
new WeightedModifierType(modifierTypes.TM_GREAT, 3),
new WeightedModifierType(modifierTypes.MEMORY_MUSHROOM, (party: Pokemon[]) => {
if (!party.find(p => p.getLearnableLevelMoves().length)) {
return 0;
@ -1360,15 +1461,15 @@ const modifierPool: ModifierPool = {
m.setTier(ModifierTier.GREAT); return m;
}),
[ModifierTier.ULTRA]: [
new WeightedModifierType(modifierTypes.ULTRA_BALL, 24),
new WeightedModifierType(modifierTypes.ULTRA_BALL, 15),
new WeightedModifierType(modifierTypes.MAX_LURE, 4),
new WeightedModifierType(modifierTypes.BIG_NUGGET, skipInLastClassicWaveOrDefault(12)),
new WeightedModifierType(modifierTypes.PP_UP, 9),
new WeightedModifierType(modifierTypes.PP_MAX, 3),
new WeightedModifierType(modifierTypes.MINT, 4),
new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32),
new WeightedModifierType(modifierTypes.AMULET_COIN, 3),
//new WeightedModifierType(modifierTypes.EVIOLITE, (party: Pokemon[]) => party.some(p => ((p.getSpeciesForm(true).speciesId in pokemonEvolutions) || (p.isFusion() && (p.getFusionSpeciesForm(true).speciesId in pokemonEvolutions))) && !p.getHeldItems().some(i => i instanceof Modifiers.EvolutionStatBoosterModifier)) ? 10 : 0),
new WeightedModifierType(modifierTypes.SPECIES_STAT_BOOSTER, 12),
new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => {
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD];
const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
@ -1383,40 +1484,40 @@ const modifierPool: ModifierPool = {
}, 10),
new WeightedModifierType(modifierTypes.REVIVER_SEED, 4),
new WeightedModifierType(modifierTypes.CANDY_JAR, 5),
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 10),
new WeightedModifierType(modifierTypes.TM_ULTRA, 8),
new WeightedModifierType(modifierTypes.ATTACK_TYPE_BOOSTER, 9),
new WeightedModifierType(modifierTypes.TM_ULTRA, 11),
new WeightedModifierType(modifierTypes.RARER_CANDY, 4),
new WeightedModifierType(modifierTypes.GOLDEN_PUNCH, 2),
new WeightedModifierType(modifierTypes.IV_SCANNER, 4),
new WeightedModifierType(modifierTypes.EXP_CHARM, 8),
new WeightedModifierType(modifierTypes.EXP_SHARE, 12),
new WeightedModifierType(modifierTypes.EXP_BALANCE, 4),
new WeightedModifierType(modifierTypes.EXP_SHARE, 10),
new WeightedModifierType(modifierTypes.EXP_BALANCE, 3),
new WeightedModifierType(modifierTypes.TERA_ORB, (party: Pokemon[]) => Math.min(Math.max(Math.floor(party[0].scene.currentBattle.waveIndex / 50) * 2, 1), 4), 4),
new WeightedModifierType(modifierTypes.QUICK_CLAW, 3),
new WeightedModifierType(modifierTypes.WIDE_LENS, 4),
].map(m => {
m.setTier(ModifierTier.ULTRA); return m;
}),
[ModifierTier.ROGUE]: [
new WeightedModifierType(modifierTypes.ROGUE_BALL, 24),
new WeightedModifierType(modifierTypes.ROGUE_BALL, 16),
new WeightedModifierType(modifierTypes.RELIC_GOLD, skipInLastClassicWaveOrDefault(2)),
new WeightedModifierType(modifierTypes.LEFTOVERS, 3),
new WeightedModifierType(modifierTypes.SHELL_BELL, 3),
new WeightedModifierType(modifierTypes.BERRY_POUCH, 4),
new WeightedModifierType(modifierTypes.GRIP_CLAW, 5),
new WeightedModifierType(modifierTypes.BATON, 2),
new WeightedModifierType(modifierTypes.SOUL_DEW, 8),
new WeightedModifierType(modifierTypes.SOUL_DEW, 7),
//new WeightedModifierType(modifierTypes.OVAL_CHARM, 6),
new WeightedModifierType(modifierTypes.SOOTHE_BELL, 4),
new WeightedModifierType(modifierTypes.ABILITY_CHARM, 6),
new WeightedModifierType(modifierTypes.FOCUS_BAND, 5),
new WeightedModifierType(modifierTypes.QUICK_CLAW, 3),
new WeightedModifierType(modifierTypes.KINGS_ROCK, 3),
new WeightedModifierType(modifierTypes.LOCK_CAPSULE, 3),
new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, 10),
new WeightedModifierType(modifierTypes.FORM_CHANGE_ITEM, 18),
new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 8, 32),
new WeightedModifierType(modifierTypes.DYNAMAX_BAND, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 8, 32),
new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(5 - rerollCount * 2, 0) : 0, 5),
new WeightedModifierType(modifierTypes.SUPER_EXP_CHARM, 8),
new WeightedModifierType(modifierTypes.FORM_CHANGE_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 6, 24),
new WeightedModifierType(modifierTypes.MEGA_BRACELET, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36),
new WeightedModifierType(modifierTypes.DYNAMAX_BAND, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 50), 4) * 9, 36),
new WeightedModifierType(modifierTypes.VOUCHER_PLUS, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily ? Math.max(3 - rerollCount * 1, 0) : 0, 3),
].map(m => {
m.setTier(ModifierTier.ROGUE); return m;
}),
@ -1425,7 +1526,7 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.SHINY_CHARM, 14),
new WeightedModifierType(modifierTypes.HEALING_CHARM, 18),
new WeightedModifierType(modifierTypes.MULTI_LENS, 18),
new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily && !party[0].scene.gameMode.isEndless && !party[0].scene.gameMode.isSplicedOnly ? Math.max(6 - rerollCount * 2, 0) : 0, 6),
new WeightedModifierType(modifierTypes.VOUCHER_PREMIUM, (party: Pokemon[], rerollCount: integer) => !party[0].scene.gameMode.isDaily && !party[0].scene.gameMode.isEndless && !party[0].scene.gameMode.isSplicedOnly ? Math.max(5 - rerollCount * 2, 0) : 0, 5),
new WeightedModifierType(modifierTypes.DNA_SPLICERS, (party: Pokemon[]) => !party[0].scene.gameMode.isSplicedOnly && party.filter(p => !p.fusionSpecies).length > 1 ? 24 : 0, 24),
new WeightedModifierType(modifierTypes.MINI_BLACK_HOLE, (party: Pokemon[]) => party[0].scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE] ? 1 : 0, 1),
].map(m => {

View File

@ -23,6 +23,7 @@ import { Nature } from "#app/data/nature";
import * as Overrides from "../overrides";
import { ModifierType, modifierTypes } from "./modifier-type";
import { Command } from "#app/ui/command-ui-handler.js";
import { Species } from "#enums/species";
import { allMoves } from "#app/data/move.js";
import { Abilities } from "#app/enums/abilities.js";
@ -707,16 +708,16 @@ export class PokemonBaseStatModifier extends PokemonHeldItemModifier {
}
/**
* Modifier used for held items, specifically Eviolite, that apply
* {@linkcode Stat} boost(s) using a multiplier if the holder can evolve.
* Modifier used for held items that apply {@linkcode Stat} boost(s)
* using a multiplier.
* @extends PokemonHeldItemModifier
* @see {@linkcode apply}
*/
export class EvolutionStatBoosterModifier extends PokemonHeldItemModifier {
export class StatBoosterModifier extends PokemonHeldItemModifier {
/** The stats that the held item boosts */
private stats: Stat[];
protected stats: Stat[];
/** The multiplier used to increase the relevant stat(s) */
private multiplier: number;
protected multiplier: number;
constructor(type: ModifierType, pokemonId: integer, stats: Stat[], multiplier: number, stackCount?: integer) {
super(type, pokemonId, stackCount);
@ -726,7 +727,7 @@ export class EvolutionStatBoosterModifier extends PokemonHeldItemModifier {
}
clone() {
return new EvolutionStatBoosterModifier(this.type, this.pokemonId, this.stats, this.multiplier, this.stackCount);
return new StatBoosterModifier(this.type, this.pokemonId, this.stats, this.multiplier, this.stackCount);
}
getArgs(): any[] {
@ -734,7 +735,14 @@ export class EvolutionStatBoosterModifier extends PokemonHeldItemModifier {
}
matchType(modifier: Modifier): boolean {
return modifier instanceof EvolutionStatBoosterModifier;
if (modifier instanceof StatBoosterModifier) {
const modifierInstance = modifier as StatBoosterModifier;
if ((modifierInstance.multiplier === this.multiplier) && (modifierInstance.stats.length === this.stats.length)) {
return modifierInstance.stats.every((e, i) => e === this.stats[i]);
}
}
return false;
}
/**
@ -745,7 +753,43 @@ export class EvolutionStatBoosterModifier extends PokemonHeldItemModifier {
* @returns true if the stat could be boosted, false otherwise
*/
shouldApply(args: any[]): boolean {
return this.stats.includes(args[1] as Stat);
return super.shouldApply(args) && this.stats.includes(args[1] as Stat);
}
/**
* Boosts the incoming stat by a {@linkcode multiplier} if the stat is listed
* in {@linkcode stats}.
* @param args [0] {@linkcode Pokemon} N/A
* [1] {@linkcode Stat} N/A
* [2] {@linkcode Utils.NumberHolder} that holds the resulting value of the stat
* @returns true if the stat boost applies successfully, false otherwise
* @see shouldApply
*/
apply(args: any[]): boolean {
const statValue = args[2] as Utils.NumberHolder;
statValue.value *= this.multiplier;
return true;
}
getMaxHeldItemCount(_pokemon: Pokemon): number {
return 1;
}
}
/**
* Modifier used for held items, specifically Eviolite, that apply
* {@linkcode Stat} boost(s) using a multiplier if the holder can evolve.
* @extends StatBoosterModifier
* @see {@linkcode apply}
*/
export class EvolutionStatBoosterModifier extends StatBoosterModifier {
clone() {
return super.clone() as EvolutionStatBoosterModifier;
}
matchType(modifier: Modifier): boolean {
return modifier instanceof EvolutionStatBoosterModifier;
}
/**
@ -771,15 +815,69 @@ export class EvolutionStatBoosterModifier extends PokemonHeldItemModifier {
return true;
} else if (isUnevolved) {
// Full boost applied if holder is unfused and unevolved or, if fused, both parts of fusion are unevolved
statValue.value *= this.multiplier;
return true;
return super.apply(args);
}
return false;
}
}
/**
* Modifier used for held items that apply {@linkcode Stat} boost(s) using a
* multiplier if the holder is of a specific {@linkcode Species}.
* @extends StatBoosterModifier
* @see {@linkcode apply}
*/
export class SpeciesStatBoosterModifier extends StatBoosterModifier {
/** The species that the held item's stat boost(s) apply to */
private species: Species[];
constructor(type: ModifierType, pokemonId: integer, stats: Stat[], multiplier: number, species: Species[], stackCount?: integer) {
super(type, pokemonId, stats, multiplier, stackCount);
this.species = species;
}
clone() {
return new SpeciesStatBoosterModifier(this.type, this.pokemonId, this.stats, this.multiplier, this.species, this.stackCount);
}
getArgs(): any[] {
return [ ...super.getArgs(), this.species ];
}
matchType(modifier: Modifier): boolean {
if (modifier instanceof SpeciesStatBoosterModifier) {
const modifierInstance = modifier as SpeciesStatBoosterModifier;
if (modifierInstance.species.length === this.species.length) {
return super.matchType(modifier) && modifierInstance.species.every((e, i) => e === this.species[i]);
}
}
return false;
}
getMaxHeldItemCount(_pokemon: Pokemon): integer {
return 1;
/**
* Checks if the incoming stat is listed in {@linkcode stats} and if the holder's {@linkcode Species}
* (or its fused species) is listed in {@linkcode species}.
* @param args [0] {@linkcode Pokemon} that holds the held item
* [1] {@linkcode Stat} being checked at the time
* [2] {@linkcode Utils.NumberHolder} N/A
* @returns true if the stat could be boosted, false otherwise
*/
shouldApply(args: any[]): boolean {
const holder = args[0] as Pokemon;
return super.shouldApply(args) && (this.species.includes(holder.getSpeciesForm(true).speciesId) || (holder.isFusion() && this.species.includes(holder.getFusionSpeciesForm(true).speciesId)));
}
/**
* Checks if either parameter is included in the corresponding lists
* @param speciesId {@linkcode Species} being checked
* @param stat {@linkcode Stat} being checked
* @returns true if both parameters are in {@linkcode species} and {@linkcode stats} respectively, false otherwise
*/
contains(speciesId: Species, stat: Stat): boolean {
return this.species.includes(speciesId) && this.stats.includes(stat);
}
}

View File

@ -8,7 +8,7 @@ import { PokeballCounts } from "./battle-scene";
import { PokeballType } from "./data/pokeball";
import { Gender } from "./data/gender";
import { StatusEffect } from "./data/status-effect";
import { modifierTypes } from "./modifier/modifier-type";
import { SpeciesStatBoosterItem, modifierTypes } from "./modifier/modifier-type";
import { VariantTier } from "./enums/variant-tiers";
import { EggTier } from "#enums/egg-type";
import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars
@ -125,11 +125,12 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0;
* - Nature is for MINT
* - Type is for TERA_SHARD or ATTACK_TYPE_BOOSTER (type boosting items i.e Silk Scarf)
* - BerryType is for BERRY
* - SpeciesStatBoosterItem is for SPECIES_STAT_BOOSTER
*/
interface ModifierOverride {
name: keyof typeof modifierTypes & string,
count?: integer
type?: TempBattleStat|Stat|Nature|Type|BerryType
type?: TempBattleStat|Stat|Nature|Type|BerryType|SpeciesStatBoosterItem
}
export const STARTING_MODIFIER_OVERRIDE: Array<ModifierOverride> = [];
export const OPP_MODIFIER_OVERRIDE: Array<ModifierOverride> = [];

View File

@ -26,7 +26,7 @@ import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr } from "./data/ability";
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -2878,6 +2878,7 @@ export class MoveEffectPhase extends PokemonPhase {
const hitCount = new Utils.IntegerHolder(1);
// Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getTarget(), move, hitCount);
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, targets.length, hitCount, new Utils.IntegerHolder(0));
if (move instanceof AttackMove && !move.hasAttr(FixedDamageAttr)) {
this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, hitCount, new Utils.IntegerHolder(0));
}
@ -2885,13 +2886,11 @@ export class MoveEffectPhase extends PokemonPhase {
}
const moveHistoryEntry = { move: this.move.moveId, targets: this.targets, result: MoveResult.PENDING, virtual: this.move.virtual };
user.pushMoveHistory(moveHistoryEntry);
const targetHitChecks = Object.fromEntries(targets.map(p => [p.getBattlerIndex(), this.hitCheck(p)]));
const activeTargets = targets.map(t => t.isActive(true));
if (!activeTargets.length || (!move.hasAttr(VariableTargetAttr) && !move.isMultiTarget() && !targetHitChecks[this.targets[0]])) {
user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1;
this.stopMultiHit();
if (activeTargets.length) {
this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!"));
moveHistoryEntry.result = MoveResult.MISS;
@ -2900,6 +2899,7 @@ export class MoveEffectPhase extends PokemonPhase {
this.scene.queueMessage(i18next.t("battle:attackFailed"));
moveHistoryEntry.result = MoveResult.FAIL;
}
user.pushMoveHistory(moveHistoryEntry);
return this.end();
}
@ -2909,8 +2909,7 @@ export class MoveEffectPhase extends PokemonPhase {
new MoveAnim(move.id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
for (const target of targets) {
if (!targetHitChecks[target.getBattlerIndex()]) {
user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1;
this.stopMultiHit(target);
this.scene.queueMessage(getPokemonMessage(user, "'s\nattack missed!"));
if (moveHistoryEntry.result === MoveResult.PENDING) {
moveHistoryEntry.result = MoveResult.MISS;
@ -2921,25 +2920,33 @@ export class MoveEffectPhase extends PokemonPhase {
const isProtected = !this.move.getMove().checkFlag(MoveFlags.IGNORE_PROTECT, user, target) && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType));
const firstHit = moveHistoryEntry.result !== MoveResult.SUCCESS;
const firstHit = (user.turnData.hitsLeft === user.turnData.hitCount);
if (firstHit) {
user.pushMoveHistory(moveHistoryEntry);
}
moveHistoryEntry.result = MoveResult.SUCCESS;
const hitResult = !isProtected ? target.apply(user, move) : HitResult.NO_EFFECT;
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
const lastHit = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive());
if (lastHit) {
this.scene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
}
applyAttrs.push(new Promise(resolve => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit),
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.PRE_APPLY && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit),
user, target, move).then(() => {
if (hitResult !== HitResult.FAIL) {
const chargeEffect = !!move.getAttrs(ChargeAttr).find(ca => ca.usedChargeEffect(user, this.getTarget(), move));
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_APPLY
&& attr.selfTarget && (!attr.firstHitOnly || firstHit), user, target, move)).then(() => {
&& attr.selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, move)).then(() => {
if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit), user, target, this.move.getMove()).then(() => {
&& !(attr as MoveEffectAttr).selfTarget && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit), user, target, this.move.getMove()).then(() => {
if (hitResult < HitResult.NO_EFFECT && !target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr)) {
const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
@ -2947,7 +2954,7 @@ export class MoveEffectPhase extends PokemonPhase {
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
}
}
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit),
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT && (!attr.firstHitOnly || firstHit) && (!attr.lastHitOnly || lastHit),
user, target, this.move.getMove()).then(() => {
return Utils.executeIf(!target.isFainted() || target.canApplyAbility(), () => applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult).then(() => {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
@ -2974,14 +2981,17 @@ export class MoveEffectPhase extends PokemonPhase {
});
}));
}
// Trigger effect which should only apply one time after all targeted effects have already applied
const postTarget = applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET,
user, null, move);
// Trigger effect which should only apply one time on the last hit after all targeted effects have already applied
const postTarget = (user.turnData.hitsLeft === 1 || !this.getTarget()?.isActive()) ?
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && attr.trigger === MoveEffectTrigger.POST_TARGET, user, null, move) :
null;
if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after
applyAttrs[applyAttrs.length - 1]?.then(() => postTarget);
} else { // Otherwise, push a new asynchronous move effect
applyAttrs.push(postTarget);
if (!!postTarget) {
if (applyAttrs.length) { // If there is a pending asynchronous move effect, do this after
applyAttrs[applyAttrs.length - 1]?.then(() => postTarget);
} else { // Otherwise, push a new asynchronous move effect
applyAttrs.push(postTarget);
}
}
Promise.allSettled(applyAttrs).then(() => this.end());
@ -3104,6 +3114,28 @@ export class MoveEffectPhase extends PokemonPhase {
return this.getTargets().find(() => true);
}
removeTarget(target: Pokemon): void {
const targetIndex = this.targets.findIndex(ind => ind === target.getBattlerIndex());
if (targetIndex !== -1) {
this.targets.splice(this.targets.findIndex(ind => ind === target.getBattlerIndex()), 1);
}
}
stopMultiHit(target?: Pokemon): void {
/** If given a specific target, remove the target from subsequent strikes */
if (target) {
this.removeTarget(target);
}
/**
* If no target specified, or the specified target was the last of this move's
* targets, completely cancel all subsequent strikes.
*/
if (!target || this.targets.length === 0 ) {
this.getUserPokemon().turnData.hitCount = 1;
this.getUserPokemon().turnData.hitsLeft = 1;
}
}
getNewHitPhase() {
return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move);
}
@ -4045,7 +4077,7 @@ export class ModifierRewardPhase extends BattlePhase {
const newModifier = this.modifierType.newModifier();
this.scene.addModifier(newModifier).then(() => {
this.scene.playSound("item_fanfare");
this.scene.ui.showText(`You received\n${newModifier.type.name}!`, null, () => resolve(), null, true);
this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier.type.name }), null, () => resolve(), null, true);
});
});
}
@ -4063,7 +4095,7 @@ export class GameOverModifierRewardPhase extends ModifierRewardPhase {
this.scene.playSound("level_up_fanfare");
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.fadeIn(250).then(() => {
this.scene.ui.showText(`You received\n${newModifier.type.name}!`, null, () => {
this.scene.ui.showText(i18next.t("battle:rewardGain", { modifierName: newModifier.type.name }), null, () => {
this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true));
resolve();
}, null, true, 1500);

View File

@ -50,6 +50,7 @@ export default class PokemonData {
public fusionLuck: integer;
public boss: boolean;
public bossSegments?: integer;
public summonData: PokemonSummonData;
@ -96,6 +97,7 @@ export default class PokemonData {
if (!forHistory) {
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);
this.bossSegments = source.bossSegments;
}
if (sourcePokemon) {

View File

@ -64,4 +64,35 @@ describe("Abilities - DISGUISE", () => {
},
TIMEOUT
);
test(
"damage taken should be equal to 1/8 of its maximum HP, rounded down",
async () => {
const baseForm = 0,
bustedForm = 1;
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DARK_PULSE, Moves.DARK_PULSE, Moves.DARK_PULSE, Moves.DARK_PULSE]);
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(20);
vi.spyOn(Overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(20);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.MAGIKARP);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.MIMIKYU]: baseForm,
});
await game.startBattle([Species.MIMIKYU]);
const mimikyu = game.scene.getPlayerPokemon();
const damage = (Math.floor(mimikyu.getMaxHp()/8));
expect(mimikyu).not.toBe(undefined);
expect(mimikyu.formIndex).toBe(baseForm);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(mimikyu.formIndex).toBe(bustedForm);
expect(game.scene.getEnemyPokemon().turnData.currDamageDealt).toBe(damage);
},
TIMEOUT
);
});

View File

@ -0,0 +1,163 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import { TurnEndPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
import { Species } from "#app/enums/species.js";
describe("Abilities - Dry Skin", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DRY_SKIN);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.CHARMANDER);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH);
vi.spyOn(overrides, "STARTER_SPECIES_OVERRIDE", "get").mockReturnValue(Species.CHANDELURE);
});
it("during sunlight, lose 1/8 of maximum health at the end of each turn", async () => {
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SUNNY_DAY, Moves.SPLASH]);
await game.startBattle();
const enemy = game.scene.getEnemyPokemon();
expect(enemy).not.toBe(undefined);
// first turn
let previousEnemyHp = enemy.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.SUNNY_DAY));
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBeLessThan(previousEnemyHp);
// second turn
previousEnemyHp = enemy.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBeLessThan(previousEnemyHp);
});
it("during rain, gain 1/8 of maximum health at the end of each turn", async () => {
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.RAIN_DANCE, Moves.SPLASH]);
await game.startBattle();
const enemy = game.scene.getEnemyPokemon();
expect(enemy).not.toBe(undefined);
enemy.hp = 1;
// first turn
let previousEnemyHp = enemy.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.RAIN_DANCE));
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBeGreaterThan(previousEnemyHp);
// second turn
previousEnemyHp = enemy.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBeGreaterThan(previousEnemyHp);
});
it("opposing fire attacks do 25% more damage", async () => {
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.EMBER]);
// ensure the enemy doesn't die to this
vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(30);
await game.startBattle();
const enemy = game.scene.getEnemyPokemon();
expect(enemy).not.toBe(undefined);
// first turn
vi.spyOn(game.scene, "randBattleSeedInt").mockReturnValue(0); // this makes moves always deal 85% damage
game.doAttack(getMovePosition(game.scene, 0, Moves.EMBER));
await game.phaseInterceptor.to(TurnEndPhase);
const fireDamageTakenWithDrySkin = enemy.getMaxHp() - enemy.hp;
expect(enemy.hp > 0);
enemy.hp = enemy.getMaxHp();
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
// second turn
game.doAttack(getMovePosition(game.scene, 0, Moves.EMBER));
await game.phaseInterceptor.to(TurnEndPhase);
const fireDamageTakenWithoutDrySkin = enemy.getMaxHp() - enemy.hp;
expect(fireDamageTakenWithDrySkin).toBeGreaterThan(fireDamageTakenWithoutDrySkin);
});
it("opposing water attacks heal 1/4 of maximum health and deal no damage", async () => {
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WATER_GUN]);
await game.startBattle();
const enemy = game.scene.getEnemyPokemon();
expect(enemy).not.toBe(undefined);
enemy.hp = 1;
game.doAttack(getMovePosition(game.scene, 0, Moves.WATER_GUN));
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBeGreaterThan(1);
});
it("opposing water attacks do not heal if they were protected from", async () => {
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WATER_GUN]);
await game.startBattle();
const enemy = game.scene.getEnemyPokemon();
expect(enemy).not.toBe(undefined);
enemy.hp = 1;
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]);
game.doAttack(getMovePosition(game.scene, 0, Moves.WATER_GUN));
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBe(1);
});
it("multi-strike water attacks only heal once", async () => {
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WATER_GUN, Moves.WATER_SHURIKEN]);
await game.startBattle();
const enemy = game.scene.getEnemyPokemon();
expect(enemy).not.toBe(undefined);
enemy.hp = 1;
// first turn
game.doAttack(getMovePosition(game.scene, 0, Moves.WATER_SHURIKEN));
await game.phaseInterceptor.to(TurnEndPhase);
const healthGainedFromWaterShuriken = enemy.hp - 1;
enemy.hp = 1;
// second turn
game.doAttack(getMovePosition(game.scene, 0, Moves.WATER_GUN));
await game.phaseInterceptor.to(TurnEndPhase);
const healthGainedFromWaterGun = enemy.hp - 1;
expect(healthGainedFromWaterShuriken).toBe(healthGainedFromWaterGun);
});
});

View File

@ -0,0 +1,650 @@
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "../utils/gameManager";
import * as Overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { getMovePosition } from "../utils/gameManagerUtils";
import { CommandPhase, DamagePhase, MoveEffectPhase, MoveEndPhase, TurnEndPhase } from "#app/phases.js";
import { BattleStat } from "#app/data/battle-stat.js";
import { Type } from "#app/data/type.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
import { StatusEffect } from "#app/data/status-effect.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - Parental Bond", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.PARENTAL_BOND);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.SNORLAX);
vi.spyOn(Overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.INSOMNIA);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(Overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(100);
});
test(
"ability should add second strike to attack move",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
let enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene, "randBattleSeedInt").mockReturnValue(15);
await game.phaseInterceptor.to(DamagePhase);
const firstStrikeDamage = enemyStartingHp - enemyPokemon.hp;
enemyStartingHp = enemyPokemon.hp;
await game.phaseInterceptor.to(TurnEndPhase, false);
const secondStrikeDamage = enemyStartingHp - enemyPokemon.hp;
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(secondStrikeDamage).toBe(Math.ceil(0.25 * firstStrikeDamage));
}, TIMEOUT
);
test(
"ability should apply secondary effects to both strikes",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.POWER_UP_PUNCH]);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.AMOONGUSS);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.POWER_UP_PUNCH));
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(2);
}, TIMEOUT
);
test(
"ability should not apply to Status moves",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.BABY_DOLL_EYES]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(enemyPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1);
}, TIMEOUT
);
test(
"ability should not apply to multi-hit moves",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DOUBLE_HIT]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.DOUBLE_HIT));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(leadPokemon.turnData.hitCount).toBe(2);
}, TIMEOUT
);
test(
"ability should not apply to self-sacrifice moves",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SELF_DESTRUCT]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SELF_DESTRUCT));
await game.phaseInterceptor.to(DamagePhase, false);
expect(leadPokemon.turnData.hitCount).toBe(1);
}, TIMEOUT
);
test(
"ability should not apply to Rollout",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.ROLLOUT]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.ROLLOUT));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(DamagePhase, false);
expect(leadPokemon.turnData.hitCount).toBe(1);
}, TIMEOUT
);
test(
"ability should not apply multiplier to fixed-damage moves",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DRAGON_RAGE]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(enemyPokemon.hp).toBe(enemyStartingHp - 80);
}, TIMEOUT
);
test(
"ability should not apply multiplier to counter moves",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.COUNTER]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
const playerStartingHp = leadPokemon.hp;
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.COUNTER));
await game.phaseInterceptor.to(DamagePhase);
const playerDamage = playerStartingHp - leadPokemon.hp;
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(enemyPokemon.hp).toBe(enemyStartingHp - 4*playerDamage);
}, TIMEOUT
);
test(
"ability should not apply to multi-target moves",
async () => {
vi.spyOn(Overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.EARTHQUAKE]);
await game.startBattle([Species.CHARIZARD, Species.PIDGEOT]);
const playerPokemon = game.scene.getPlayerField();
expect(playerPokemon.length).toBe(2);
playerPokemon.forEach(p => expect(p).not.toBe(undefined));
const enemyPokemon = game.scene.getEnemyField();
expect(enemyPokemon.length).toBe(2);
enemyPokemon.forEach(p => expect(p).not.toBe(undefined));
game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE));
await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, 1, Moves.EARTHQUAKE));
await game.phaseInterceptor.to(TurnEndPhase, false);
playerPokemon.forEach(p => expect(p.turnData.hitCount).toBe(1));
}, TIMEOUT
);
test(
"ability should apply to multi-target moves when hitting only one target",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.EARTHQUAKE]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE));
await game.phaseInterceptor.to(DamagePhase, false);
expect(leadPokemon.turnData.hitCount).toBe(2);
}, TIMEOUT
);
test(
"ability should only trigger post-target move effects once",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.MIND_BLOWN]);
await game.startBattle([Species.PIDGEOT]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.MIND_BLOWN));
await game.phaseInterceptor.to(DamagePhase, false);
expect(leadPokemon.turnData.hitCount).toBe(2);
// This test will time out if the user faints
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(leadPokemon.hp).toBe(Math.floor(leadPokemon.getMaxHp()/2));
}, TIMEOUT
);
test(
"Burn Up only removes type after second strike with this ability",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.BURN_UP]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(enemyPokemon.hp).toBeGreaterThan(0);
expect(leadPokemon.isOfType(Type.FIRE)).toBe(true);
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(leadPokemon.isOfType(Type.FIRE)).toBe(false);
}, TIMEOUT
);
test(
"Moves boosted by this ability and Multi-Lens should strike 4 times",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS", count: 1}]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(4);
}, TIMEOUT
);
test(
"Super Fang boosted by this ability and Multi-Lens should strike twice",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SUPER_FANG]);
vi.spyOn(Overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS", count: 1}]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.SUPER_FANG));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemyPokemon.hp).toBe(Math.ceil(enemyStartingHp * 0.25));
}, TIMEOUT
);
test(
"Seismic Toss boosted by this ability and Multi-Lens should strike twice",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SEISMIC_TOSS]);
vi.spyOn(Overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS", count: 1}]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
const enemyStartingHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.SEISMIC_TOSS));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemyPokemon.hp).toBe(enemyStartingHp - 200);
}, TIMEOUT
);
test(
"Hyper Beam boosted by this ability should strike twice, then recharge",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.HYPER_BEAM]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.HYPER_BEAM));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeUndefined();
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.getTag(BattlerTagType.RECHARGING)).toBeDefined();
}, TIMEOUT
);
/** TODO: Fix TRAPPED tag lapsing incorrectly, then run this test */
test.skip(
"Anchor Shot boosted by this ability should only trap the target after the second hit",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.ANCHOR_SHOT]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.ANCHOR_SHOT));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeUndefined(); // Passes
await game.phaseInterceptor.to(MoveEndPhase);
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); // Passes
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemyPokemon.getTag(BattlerTagType.TRAPPED)).toBeDefined(); // Fails :(
}, TIMEOUT
);
test(
"Smack Down boosted by this ability should only ground the target after the second hit",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SMACK_DOWN]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SMACK_DOWN));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeUndefined();
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
}, TIMEOUT
);
test(
"U-turn boosted by this ability should strike twice before forcing a switch",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.U_TURN]);
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.U_TURN));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(MoveEffectPhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
// This will cause this test to time out if the switch was forced on the first hit.
await game.phaseInterceptor.to(MoveEffectPhase, false);
}, TIMEOUT
);
test(
"Wake-Up Slap boosted by this ability should only wake up the target after the second hit",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WAKE_UP_SLAP]);
vi.spyOn(Overrides, "OPP_STATUS_OVERRIDE", "get").mockReturnValue(StatusEffect.SLEEP);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.WAKE_UP_SLAP));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValue(true);
await game.phaseInterceptor.to(DamagePhase);
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(enemyPokemon.status?.effect).toBe(StatusEffect.SLEEP);
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemyPokemon.status?.effect).toBeUndefined();
}, TIMEOUT
);
test(
"ability should not cause user to hit into King's Shield more than once",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.KINGS_SHIELD,Moves.KINGS_SHIELD,Moves.KINGS_SHIELD,Moves.KINGS_SHIELD]);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1);
}, TIMEOUT
);
test(
"ability should not cause user to hit into Storm Drain more than once",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WATER_GUN]);
vi.spyOn(Overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.STORM_DRAIN);
await game.startBattle([Species.CHARIZARD]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.WATER_GUN));
await game.phaseInterceptor.to(TurnEndPhase, false);
expect(enemyPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(1);
}, TIMEOUT
);
test(
"ability should not apply to multi-target moves with Multi-Lens",
async () => {
vi.spyOn(Overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.EARTHQUAKE, Moves.SPLASH]);
vi.spyOn(Overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS", count: 1}]);
await game.startBattle([Species.CHARIZARD, Species.PIDGEOT]);
const playerPokemon = game.scene.getPlayerField();
expect(playerPokemon.length).toBe(2);
playerPokemon.forEach(p => expect(p).not.toBe(undefined));
const enemyPokemon = game.scene.getEnemyField();
expect(enemyPokemon.length).toBe(2);
enemyPokemon.forEach(p => expect(p).not.toBe(undefined));
const enemyStartingHp = enemyPokemon.map(p => p.hp);
game.doAttack(getMovePosition(game.scene, 0, Moves.EARTHQUAKE));
await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene, "randBattleSeedInt").mockReturnValue(15);
await game.phaseInterceptor.to(DamagePhase);
const enemyFirstHitDamage = enemyStartingHp.map((hp, i) => hp - enemyPokemon[i].hp);
await game.phaseInterceptor.to(TurnEndPhase, false);
enemyPokemon.forEach((p, i) => expect(enemyStartingHp[i] - p.hp).toBe(2*enemyFirstHitDamage[i]));
}, TIMEOUT
);
});

View File

@ -1,16 +1,13 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#enums/abilities";
import {Species} from "#enums/species";
import {EnemyCommandPhase, TitlePhase, TurnEndPhase, TurnStartPhase,
} from "#app/phases";
import * as Overrides from "#app/overrides";
import { Abilities } from "#enums/abilities";
import { Species } from "#enums/species";
import { FaintPhase } from "#app/phases";
import { Moves } from "#enums/moves";
import { Stat } from "#app/data/pokemon-stat";
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
import { allAbilities, BypassSpeedChanceAbAttr } from "#app/data/ability";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
describe("Abilities - Quick Draw", () => {
let phaserGame: Phaser.Game;
@ -28,90 +25,66 @@ describe("Abilities - Quick Draw", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(
Abilities.QUICK_DRAW
);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(
Species.RATTATA
);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([
Moves.TACKLE,
Moves.TACKLE,
Moves.TACKLE,
Moves.TACKLE,
]);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.QUICK_DRAW);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TAIL_WHIP]);
vi.spyOn(Overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
vi.spyOn(
allAbilities[Abilities.QUICK_DRAW].getAttrs(BypassSpeedChanceAbAttr)[0],
"chance","get"
).mockReturnValue(100);
vi.spyOn(allAbilities[Abilities.QUICK_DRAW].getAttrs(BypassSpeedChanceAbAttr)[0], "chance", "get").mockReturnValue(100);
});
it("makes pokemon going first in its priority bracket", async() => {
test("makes pokemon going first in its priority bracket", async () => {
await game.startBattle([Species.SLOWBRO]);
const pokemon = game.scene.getParty()[0];
const enemy = game.scene.getEnemyParty()[0];
const pokemon = game.scene.getPlayerPokemon();
const enemy = game.scene.getEnemyPokemon();
pokemon.stats[Stat.SPD] = 50;
enemy.stats[Stat.SPD] = 150;
pokemon.hp = 1;
enemy.hp = 1;
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(FaintPhase, false);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.to(TurnEndPhase);
expect(pokemon.battleData.abilityRevealed).toBe(true);
expect(pokemon.isFainted()).toBe(false);
expect(enemy.isFainted()).toBe(true);
expect(pokemon.battleData.abilitiesApplied).contain(Abilities.QUICK_DRAW);
}, 20000);
it("does not triggered by non damage moves", async () => {
test("does not triggered by non damage moves", async () => {
await game.startBattle([Species.SLOWBRO]);
const pokemon = game.scene.getParty()[0];
const enemy = game.scene.getEnemyParty()[0];
const pokemon = game.scene.getPlayerPokemon();
const enemy = game.scene.getEnemyPokemon();
pokemon.stats[Stat.SPD] = 50;
enemy.stats[Stat.SPD] = 150;
pokemon.hp = 1;
enemy.hp = 1;
game.doAttack(getMovePosition(game.scene, 0, Moves.TOXIC));
game.doAttack(getMovePosition(game.scene, 0, Moves.TAIL_WHIP));
await game.phaseInterceptor.to(FaintPhase, false);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.to(TitlePhase);
expect(pokemon.battleData.abilityRevealed).not.toBe(true);
expect(pokemon.isFainted()).toBe(true);
expect(enemy.isFainted()).toBe(false);
expect(pokemon.battleData.abilitiesApplied).not.contain(Abilities.QUICK_DRAW);
}, 20000);
it("does not increase priority", async () => {
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([
Moves.EXTREME_SPEED,
Moves.EXTREME_SPEED,
Moves.EXTREME_SPEED,
Moves.EXTREME_SPEED,
]);
test("does not increase priority", async () => {
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue(Array(4).fill(Moves.EXTREME_SPEED));
await game.startBattle([Species.SLOWBRO]);
const pokemon = game.scene.getParty()[0];
const enemy = game.scene.getEnemyParty()[0];
const pokemon = game.scene.getPlayerPokemon();
const enemy = game.scene.getEnemyPokemon();
pokemon.stats[Stat.SPD] = 50;
enemy.stats[Stat.SPD] = 150;
pokemon.hp = 1;
enemy.hp = 1;
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(FaintPhase, false);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
await game.phaseInterceptor.to(TitlePhase);
expect(pokemon.battleData.abilityRevealed).toBe(true);
expect(pokemon.isFainted()).toBe(true);
expect(enemy.isFainted()).toBe(false);
expect(pokemon.battleData.abilitiesApplied).contain(Abilities.QUICK_DRAW);
}, 20000);
});

View File

@ -1,4 +1,4 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it} from "vitest";
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import BattleScene from "../../battle-scene";
import { Egg, getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg.js";
import { Species } from "#enums/species";
@ -8,6 +8,7 @@ import { EggTier } from "#app/enums/egg-type.js";
import { VariantTier } from "#app/enums/variant-tiers.js";
import GameManager from "../utils/gameManager";
import EggData from "#app/system/egg-data.js";
import * as Utils from "#app/utils.js";
describe("Egg Generation Tests", () => {
let phaserGame: Phaser.Game;
@ -21,6 +22,7 @@ describe("Egg Generation Tests", () => {
afterEach(() => {
game.phaseInterceptor.restoreOg();
vi.restoreAllMocks();
});
beforeEach(async() => {
@ -288,4 +290,17 @@ describe("Egg Generation Tests", () => {
expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1);
});
it("should increase legendary egg rate", () => {
vi.spyOn(Utils, "randInt").mockReturnValue(1);
const scene = game.scene;
const expectedTier1 = EggTier.MASTER;
const expectedTier2 = EggTier.ULTRA;
const result1 = new Egg({scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true}).tier;
const result2 = new Egg({scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true}).tier;
expect(result1).toBe(expectedTier1);
expect(result2).toBe(expectedTier2);
});
});

View File

@ -0,0 +1,200 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phase from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Stat } from "#app/data/pokemon-stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import * as Utils from "#app/utils";
import i18next from "#app/plugins/i18n";
describe("Items - Light Ball", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phase.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
});
it("LIGHT_BALL activates in battle correctly", async() => {
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{ name: "SPECIES_STAT_BOOSTER", type: "LIGHT_BALL" }]);
const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([
Species.PIKACHU
]);
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Light Ball is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
});
it("LIGHT_BALL held by PIKACHU", async() => {
await game.startBattle([
Species.PIKACHU
]);
const partyMember = game.scene.getParty()[0];
const atkStat = partyMember.getStat(Stat.ATK);
const spAtkStat = partyMember.getStat(Stat.SPATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue);
const spAtkValue = new Utils.NumberHolder(spAtkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue);
expect(atkValue.value / atkStat).toBe(1);
expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
expect(atkValue.value / atkStat).toBe(2);
expect(spAtkValue.value / spAtkStat).toBe(2);
}, 20000);
it("LIGHT_BALL held by fused PIKACHU (base)", async() => {
await game.startBattle([
Species.PIKACHU,
Species.MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const atkStat = partyMember.getStat(Stat.ATK);
const spAtkStat = partyMember.getStat(Stat.SPATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue);
const spAtkValue = new Utils.NumberHolder(spAtkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue);
expect(atkValue.value / atkStat).toBe(1);
expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
expect(atkValue.value / atkStat).toBe(2);
expect(spAtkValue.value / spAtkStat).toBe(2);
}, 20000);
it("LIGHT_BALL held by fused PIKACHU (part)", async() => {
await game.startBattle([
Species.MAROWAK,
Species.PIKACHU
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const atkStat = partyMember.getStat(Stat.ATK);
const spAtkStat = partyMember.getStat(Stat.SPATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue);
const spAtkValue = new Utils.NumberHolder(spAtkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue);
expect(atkValue.value / atkStat).toBe(1);
expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
expect(atkValue.value / atkStat).toBe(2);
expect(spAtkValue.value / spAtkStat).toBe(2);
}, 20000);
it("LIGHT_BALL not held by PIKACHU", async() => {
await game.startBattle([
Species.MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const atkStat = partyMember.getStat(Stat.ATK);
const spAtkStat = partyMember.getStat(Stat.SPATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, atkValue);
const spAtkValue = new Utils.NumberHolder(spAtkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPDEF, spAtkValue);
expect(atkValue.value / atkStat).toBe(1);
expect(spAtkValue.value / spAtkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["LIGHT_BALL"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPATK, spAtkValue);
expect(atkValue.value / atkStat).toBe(1);
expect(spAtkValue.value / spAtkStat).toBe(1);
}, 20000);
});

View File

@ -0,0 +1,176 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phase from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Stat } from "#app/data/pokemon-stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import * as Utils from "#app/utils";
import i18next from "#app/plugins/i18n";
describe("Items - Metal Powder", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phase.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
});
it("METAL_POWDER activates in battle correctly", async() => {
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{ name: "SPECIES_STAT_BOOSTER", type: "METAL_POWDER" }]);
const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([
Species.DITTO
]);
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Metal Powder is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
});
it("METAL_POWDER held by DITTO", async() => {
await game.startBattle([
Species.DITTO
]);
const partyMember = game.scene.getParty()[0];
const defStat = partyMember.getStat(Stat.DEF);
// Making sure modifier is not applied without holding item
const defValue = new Utils.NumberHolder(defStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2);
}, 20000);
it("METAL_POWDER held by fused DITTO (base)", async() => {
await game.startBattle([
Species.DITTO,
Species.MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const defStat = partyMember.getStat(Stat.DEF);
// Making sure modifier is not applied without holding item
const defValue = new Utils.NumberHolder(defStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2);
}, 20000);
it("METAL_POWDER held by fused DITTO (part)", async() => {
await game.startBattle([
Species.MAROWAK,
Species.DITTO
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const defStat = partyMember.getStat(Stat.DEF);
// Making sure modifier is not applied without holding item
const defValue = new Utils.NumberHolder(defStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(2);
}, 20000);
it("METAL_POWDER not held by DITTO", async() => {
await game.startBattle([
Species.MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const defStat = partyMember.getStat(Stat.DEF);
// Making sure modifier is not applied without holding item
const defValue = new Utils.NumberHolder(defStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["METAL_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.DEF, defValue);
expect(defValue.value / defStat).toBe(1);
}, 20000);
});

View File

@ -0,0 +1,176 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phase from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Stat } from "#app/data/pokemon-stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import * as Utils from "#app/utils";
import i18next from "#app/plugins/i18n";
describe("Items - Quick Powder", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phase.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
});
it("QUICK_POWDER activates in battle correctly", async() => {
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{ name: "SPECIES_STAT_BOOSTER", type: "QUICK_POWDER" }]);
const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([
Species.DITTO
]);
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Quick Powder is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
});
it("QUICK_POWDER held by DITTO", async() => {
await game.startBattle([
Species.DITTO
]);
const partyMember = game.scene.getParty()[0];
const spdStat = partyMember.getStat(Stat.SPD);
// Making sure modifier is not applied without holding item
const spdValue = new Utils.NumberHolder(spdStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2);
}, 20000);
it("QUICK_POWDER held by fused DITTO (base)", async() => {
await game.startBattle([
Species.DITTO,
Species.MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const spdStat = partyMember.getStat(Stat.SPD);
// Making sure modifier is not applied without holding item
const spdValue = new Utils.NumberHolder(spdStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2);
}, 20000);
it("QUICK_POWDER held by fused DITTO (part)", async() => {
await game.startBattle([
Species.MAROWAK,
Species.DITTO
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const spdStat = partyMember.getStat(Stat.SPD);
// Making sure modifier is not applied without holding item
const spdValue = new Utils.NumberHolder(spdStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(2);
}, 20000);
it("QUICK_POWDER not held by DITTO", async() => {
await game.startBattle([
Species.MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const spdStat = partyMember.getStat(Stat.SPD);
// Making sure modifier is not applied without holding item
const spdValue = new Utils.NumberHolder(spdStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["QUICK_POWDER"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.SPD, spdValue);
expect(spdValue.value / spdStat).toBe(1);
}, 20000);
});

View File

@ -0,0 +1,228 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import Phase from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import { Species } from "#enums/species";
import { Stat } from "#app/data/pokemon-stat";
import { SpeciesStatBoosterModifier } from "#app/modifier/modifier";
import { modifierTypes } from "#app/modifier/modifier-type";
import * as Utils from "#app/utils";
import i18next from "#app/plugins/i18n";
describe("Items - Thick Club", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phase.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
});
it("THICK_CLUB activates in battle correctly", async() => {
vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{ name: "SPECIES_STAT_BOOSTER", type: "THICK_CLUB" }]);
const consoleSpy = vi.spyOn(console, "log");
await game.startBattle([
Species.CUBONE
]);
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Thick Club is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
});
it("THICK_CLUB held by CUBONE", async() => {
await game.startBattle([
Species.CUBONE
]);
const partyMember = game.scene.getParty()[0];
const atkStat = partyMember.getStat(Stat.ATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2);
}, 20000);
it("THICK_CLUB held by MAROWAK", async() => {
await game.startBattle([
Species.MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const atkStat = partyMember.getStat(Stat.ATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2);
}, 20000);
it("THICK_CLUB held by ALOLA_MAROWAK", async() => {
await game.startBattle([
Species.ALOLA_MAROWAK
]);
const partyMember = game.scene.getParty()[0];
const atkStat = partyMember.getStat(Stat.ATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2);
}, 20000);
it("THICK_CLUB held by fused CUBONE line (base)", async() => {
// Randomly choose from the Cubone line
const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ];
const randSpecies = Utils.randInt(species.length);
await game.startBattle([
species[randSpecies],
Species.PIKACHU
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const atkStat = partyMember.getStat(Stat.ATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2);
}, 20000);
it("THICK_CLUB held by fused CUBONE line (part)", async() => {
// Randomly choose from the Cubone line
const species = [ Species.CUBONE, Species.MAROWAK, Species.ALOLA_MAROWAK ];
const randSpecies = Utils.randInt(species.length);
await game.startBattle([
Species.PIKACHU,
species[randSpecies]
]);
const partyMember = game.scene.getParty()[0];
const ally = game.scene.getParty()[1];
// Fuse party members (taken from PlayerPokemon.fuse(...) function)
partyMember.fusionSpecies = ally.species;
partyMember.fusionFormIndex = ally.formIndex;
partyMember.fusionAbilityIndex = ally.abilityIndex;
partyMember.fusionShiny = ally.shiny;
partyMember.fusionVariant = ally.variant;
partyMember.fusionGender = ally.gender;
partyMember.fusionLuck = ally.luck;
const atkStat = partyMember.getStat(Stat.ATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(2);
}, 20000);
it("THICK_CLUB not held by CUBONE", async() => {
await game.startBattle([
Species.PIKACHU
]);
const partyMember = game.scene.getParty()[0];
const atkStat = partyMember.getStat(Stat.ATK);
// Making sure modifier is not applied without holding item
const atkValue = new Utils.NumberHolder(atkStat);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1);
// Giving Eviolite to party member and testing if it applies
partyMember.scene.addModifier(modifierTypes.SPECIES_STAT_BOOSTER().generateType(null, ["THICK_CLUB"]).newModifier(partyMember), true);
partyMember.scene.applyModifiers(SpeciesStatBoosterModifier, true, partyMember, Stat.ATK, atkValue);
expect(atkValue.value / atkStat).toBe(1);
}, 20000);
});

View File

@ -122,6 +122,10 @@ export default class MockSprite {
return this.phaserSprite.setPositionRelative(source, x, y);
}
setY(y) {
return this.phaserSprite.setY(y);
}
setCrop(x, y, width, height) {
// Sets the crop size of this Game Object.
return this.phaserSprite.setCrop(x, y, width, height);

View File

@ -0,0 +1,41 @@
{
"textures": [
{
"image": "shiny_icons.png",
"format": "RGBA8888",
"size": {
"w": 45,
"h": 14
},
"scale": 1,
"frames": [
{
"filename": "0",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 45,
"h": 14
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
},
"frame": {
"x": 0,
"y": 0,
"w": 15,
"h": 14
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:a3275c7504a9b35b288265e191b1d14c:420725f3fb73c2cac0ab3bbc0a46f2e1:3a8b8ca0f0e4be067dd46c07b78ee013$"
}
}

View File

@ -1,6 +1,6 @@
import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "../events/battle-scene";
import { pokemonPrevolutions } from "#app/data/pokemon-evolutions";
import { Variant, getVariantTint } from "#app/data/variant";
import { Variant, getVariantTint, getVariantIcon } from "#app/data/variant";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
@ -199,6 +199,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private pokemonCaughtCountText: Phaser.GameObjects.Text;
private pokemonHatchedIcon : Phaser.GameObjects.Sprite;
private pokemonHatchedCountText: Phaser.GameObjects.Text;
private pokemonShinyIcon: Phaser.GameObjects.Sprite;
private genOptionsText: Phaser.GameObjects.Text;
private instructionsContainer: Phaser.GameObjects.Container;
@ -616,6 +617,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonHatchedIcon.setScale(0.8);
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedIcon);
this.pokemonShinyIcon = this.scene.add.sprite(14, 76, "shiny_icons");
this.pokemonShinyIcon.setOrigin(0.15, 0.2);
this.pokemonShinyIcon.setScale(1);
this.pokemonCaughtHatchedContainer.add ((this.pokemonShinyIcon));
this.pokemonHatchedCountText = addTextObject(this.scene, 24, 19, "0", TextStyle.SUMMARY_ALT);
this.pokemonHatchedCountText.setOrigin(0, 0);
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedCountText);
@ -1506,11 +1512,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
switch (button) {
case Button.CYCLE_SHINY:
if (this.canCycleShiny) {
starterAttributes.variant = !props.shiny ? props.variant : -1; // update shiny setting
const newVariant = props.variant;
this.setSpeciesDetails(this.lastSpecies, !props.shiny, undefined, undefined, props.shiny ? 0 : undefined, undefined, undefined);
if (this.dexAttrCursor & DexAttr.SHINY) {
this.scene.playSound("sparkle");
// Set the variant label to the shiny tint
const tint = getVariantTint(newVariant);
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant));
this.pokemonShinyIcon.setTint(tint);
this.pokemonShinyIcon.setVisible(true);
} else {
this.pokemonShinyIcon.setVisible(false);
success = true;
}
}
@ -1593,13 +1605,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
}
} while (newVariant !== props.variant);
starterAttributes.variant = newVariant; // store the selected variant
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant, undefined, undefined);
// Cycle tint based on current sprite tint
const tint = getVariantTint(newVariant);
this.variantLabel.setTint(tint);
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant));
this.pokemonShinyIcon.setTint(tint);
success = true;
}
break;
@ -1875,8 +1885,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
const variant = defaultProps.variant;
const tint = getVariantTint(variant);
this.variantLabel.setTint(tint);
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
this.pokemonShinyIcon.setTint(tint);
this.setSpecies(species);
this.updateInstructions();
}
@ -1924,6 +1934,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return false;
}
setSpecies(species: PokemonSpecies) {
this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null;
this.dexAttrCursor = species ? this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true) : 0n;
@ -2022,6 +2034,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonLuckText.setText(luck.toString());
this.pokemonLuckText.setTint(getVariantTint(Math.min(luck - 1, 2) as Variant));
this.pokemonLuckLabelText.setVisible(this.pokemonLuckText.visible);
this.pokemonShinyIcon.setVisible(this.pokemonLuckText.visible);
//Growth translate
let growthReadable = Utils.toReadableString(GrowthRate[species.growthRate]);
@ -2045,9 +2058,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonHatchedIcon.setFrame(getEggTierForSpecies(species));
}
this.pokemonHatchedCountText.setText(`${this.speciesStarterDexEntry.hatchedCount}`);
const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true);
const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
const variant = defaultProps.variant;
const tint = getVariantTint(variant);
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
this.pokemonShinyIcon.setTint(tint);
this.pokemonCaughtHatchedContainer.setVisible(true);
if (pokemonPrevolutions.hasOwnProperty(species.speciesId)) {
this.pokemonCaughtHatchedContainer.setY(16);
this.pokemonShinyIcon.setY(135);
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
[
this.pokemonCandyIcon,
this.pokemonCandyOverlayIcon,
@ -2056,12 +2077,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonHatchedIcon,
this.pokemonHatchedCountText
].map(c => c.setVisible(false));
this.pokemonFormText.setY(25);
} else if (species.speciesId === Species.ETERNATUS) {
this.pokemonHatchedIcon.setVisible(false);
this.pokemonHatchedCountText.setVisible(false);
} else {
this.pokemonCaughtHatchedContainer.setY(25);
this.pokemonShinyIcon.setY(117);
this.pokemonCandyIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[0])));
this.pokemonCandyIcon.setVisible(true);
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(colorScheme[1])));
@ -2070,7 +2091,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonCandyCountText.setText(`x${this.scene.gameData.starterData[species.speciesId].candyCount}`);
this.pokemonCandyCountText.setVisible(true);
this.pokemonFormText.setVisible(true);
this.pokemonFormText.setY(42);
this.pokemonHatchedIcon.setVisible(true);
this.pokemonHatchedCountText.setVisible(true);
@ -2153,6 +2173,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.type2Icon.setVisible(false);
this.pokemonLuckLabelText.setVisible(false);
this.pokemonLuckText.setVisible(false);
this.pokemonShinyIcon.setVisible(false);
this.pokemonUncaughtText.setVisible(true);
this.pokemonAbilityLabelText.setVisible(false);
this.pokemonPassiveLabelText.setVisible(false);
@ -2181,6 +2202,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.type2Icon.setVisible(false);
this.pokemonLuckLabelText.setVisible(false);
this.pokemonLuckText.setVisible(false);
this.pokemonShinyIcon.setVisible(false);
this.pokemonUncaughtText.setVisible(!!species);
this.pokemonAbilityLabelText.setVisible(false);
this.pokemonPassiveLabelText.setVisible(false);
@ -2197,6 +2219,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
}
setSpeciesDetails(species: PokemonSpecies, shiny: boolean, formIndex: integer, female: boolean, variant: Variant, abilityIndex: integer, natureIndex: integer, forSeen: boolean = false): void {
const oldProps = species ? this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null;
const oldAbilityIndex = this.abilityCursor > -1 ? this.abilityCursor : this.scene.gameData.getStarterSpeciesDefaultAbilityIndex(species);