Merge remote-tracking branch 'upstream/main' into move-power-trick
13336
public/images/items.json
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 132 KiB |
BIN
public/images/items/light_ball.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
public/images/items/metal_powder.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
public/images/items/quick_powder.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
public/images/items/thick_club.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.7 KiB |
@ -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"
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
@ -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
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.3 KiB |
78
public/images/ui/legacy/shiny_icons.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
BIN
public/images/ui/legacy/shiny_icons.png
Normal file
After Width: | Height: | Size: 296 B |
78
public/images/ui/shiny_icons.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
BIN
public/images/ui/shiny_icons.png
Normal file
After Width: | Height: | Size: 296 B |
@ -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,16 +2805,12 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostTerrainChangeAbAttr extends AbAttr {
|
||||
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
5
src/enums/variant-tier.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum VariantTier {
|
||||
STANDARD,
|
||||
RARE,
|
||||
EPIC
|
||||
}
|
@ -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) {
|
||||
|
@ -35,6 +35,7 @@ export interface ModifierTypeTranslationEntry {
|
||||
|
||||
export interface ModifierTypeTranslationEntries {
|
||||
ModifierType: { [key: string]: ModifierTypeTranslationEntry },
|
||||
SpeciesBoosterItem: { [key: string]: ModifierTypeTranslationEntry },
|
||||
AttackTypeBoosterItem: SimpleTranslationEntries,
|
||||
TempBattleStatBoosterItem: SimpleTranslationEntries,
|
||||
TempBattleStatBoosterStatName: SimpleTranslationEntries,
|
||||
|
@ -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");
|
||||
|
@ -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!",
|
||||
|
@ -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",
|
||||
|
@ -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!",
|
||||
|
@ -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."
|
||||
|
@ -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",
|
||||
|
@ -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!",
|
||||
|
@ -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",
|
||||
|
@ -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 d’Exp !",
|
||||
"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": "L’amour empêche {{pokemonNameWithAffix}}\nd’agir !",
|
||||
"battlerTagsInfatuatedOnRemove": "{{pokemonNameWithAffix}}\nn’est plus amoureux !",
|
||||
"battlerTagsSeededOnAdd": "{{pokemonNameWithAffix}} est infecté !",
|
||||
@ -96,7 +97,7 @@ export const battle: SimpleTranslationEntries = {
|
||||
"battlerTagsNightmareOnAdd": "{{pokemonNameWithAffix}} commence à cauchemarder !",
|
||||
"battlerTagsNightmareOnOverlap": "{{pokemonNameWithAffix}} est\ndéjà prisonnier d’un cauchemar !",
|
||||
"battlerTagsNightmareLapse": "{{pokemonNameWithAffix}}est\nprisonnier d’un cauchemar !",
|
||||
"battlerTagsEncoreOnAdd": "({{pokemonNameWithAffix}} !\nEncore une fois !",
|
||||
"battlerTagsEncoreOnAdd": "{{pokemonNameWithAffix}} !\nEncore une fois !",
|
||||
"battlerTagsEncoreOnRemove": "{{pokemonNameWithAffix}} n’est\nplus obligé d’utiliser la même capacité !",
|
||||
"battlerTagsHelpingHandOnAdd": "{{pokemonNameWithAffix}} est prêt\nà aider {{pokemonName}} !",
|
||||
"battlerTagsIngrainLapse": "{{pokemonNameWithAffix}} absorbe\ndes nutriments avec ses racines !",
|
||||
|
@ -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 m’entrainer !
|
||||
$@c{smile}Bon. Ça veut juste dire que je vais devoir encore plus m’entrainer !
|
||||
$@c{smile_wave}J’ai 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-là !
|
||||
$@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 m’entrainer !
|
||||
$@c{smile}Bon. Ça veut juste dire que je vais devoir encore plus m’entrainer !
|
||||
$@c{smile_wave}J’ai 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-là !
|
||||
$@c{smile_wave}Allez, tiens le coup !`
|
||||
|
@ -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 qu’un 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 +",
|
||||
|
@ -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!",
|
||||
|
@ -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 l’Attacco e l’Attacco Speciale." },
|
||||
"THICK_CLUB": { name: "Osso spesso", description: "Strumento da dare a Cubone o Marowak. Osso duro che aumenta l’Attacco." },
|
||||
"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",
|
||||
|
@ -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}}[[를]] 배웠다!",
|
||||
|
@ -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": {
|
||||
|
@ -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": "디펜드업",
|
||||
|
@ -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!",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -127,7 +127,7 @@ export const PGMachv: AchievementTranslationEntries = {
|
||||
},
|
||||
"CATCH_SUB_LEGENDARY": {
|
||||
name: "二级传说",
|
||||
description: "捕捉一只准传说宝可梦",
|
||||
description: "捕捉一只二级传说宝可梦",
|
||||
},
|
||||
"CATCH_LEGENDARY": {
|
||||
name: "传说",
|
||||
|
@ -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}}用扎根回复了体力!",
|
||||
|
@ -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": "空之探险队「黑暗小丘」",
|
||||
|
@ -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": {
|
||||
|
@ -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}}",
|
||||
|
@ -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;
|
||||
|
@ -53,5 +53,5 @@ export const menu: SimpleTranslationEntries = {
|
||||
"no": "否",
|
||||
"disclaimer": "免责声明",
|
||||
"disclaimerDescription": "这个游戏尚未完成; 可能存在游戏性问题(包括潜在的丢档风险)、\n 不经通知的调整、 未来可能会更新或完成更多内容",
|
||||
"choosePokemon": "Choose a Pokémon.",
|
||||
"choosePokemon": "选择一只宝可梦。",
|
||||
} as const;
|
||||
|
@ -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": "防御强化",
|
||||
|
@ -31,7 +31,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
|
||||
"selectMoveSwapWith": "选择要替换成的招式",
|
||||
"unlockPassive": "解锁被动",
|
||||
"reduceCost": "降低花费",
|
||||
"sameSpeciesEgg": "Buy an Egg",
|
||||
"sameSpeciesEgg": "兑换一颗蛋",
|
||||
"cycleShiny": ": 闪光",
|
||||
"cycleForm": ": 形态",
|
||||
"cycleGender": ": 性别",
|
||||
|
@ -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": "青绿 & 赤红",
|
||||
|
@ -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}}用扎根回復了體力!",
|
||||
|
@ -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: "防禦強化",
|
||||
|
@ -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 => {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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> = [];
|
||||
|
@ -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;
|
||||
|
||||
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,15 +2981,18 @@ 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 (!!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);
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
);
|
||||
});
|
||||
|
163
src/test/abilities/dry_skin.test.ts
Normal 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);
|
||||
});
|
||||
});
|
650
src/test/abilities/parental_bond.test.ts
Normal 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
|
||||
);
|
||||
});
|
@ -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 * as Overrides from "#app/overrides";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Species } from "#enums/species";
|
||||
import {EnemyCommandPhase, TitlePhase, TurnEndPhase, TurnStartPhase,
|
||||
} from "#app/phases";
|
||||
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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
200
src/test/items/light_ball.test.ts
Normal 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);
|
||||
});
|
176
src/test/items/metal_powder.test.ts
Normal 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);
|
||||
});
|
176
src/test/items/quick_powder.test.ts
Normal 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);
|
||||
});
|
228
src/test/items/thick_club.test.ts
Normal 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);
|
||||
});
|
@ -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);
|
||||
|
41
src/ui/settings/shiny_icons.json
Normal 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$"
|
||||
}
|
||||
}
|
@ -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);
|
||||
|