Merge branch 'beta' into zygarde-10-pc

This commit is contained in:
Madmadness65 2024-10-15 13:42:34 -05:00 committed by GitHub
commit 58af59b7e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
107 changed files with 2800 additions and 7026 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 879 B

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,40 @@
{
"1": {
"566678": "0e6296",
"e0e4f4": "513981",
"625287": "4e4094",
"536273": "1f1233",
"988b98": "b24c86",
"9170b9": "8b69c3",
"c4cce1": "3b235c",
"e6d3e9": "f1a4c5",
"bfacc1": "da75a5",
"515f70": "197497",
"c5cee3": "63cee1",
"8e96aa": "301848",
"8b93a6": "3aa8c4",
"80737f": "8a2166",
"4b454f": "6f1357",
"36404c": "0c5474",
"b791f2": "c7a1e5"
},
"2": {
"566678": "8e480b",
"e0e4f4": "176463",
"625287": "274159",
"536273": "02262c",
"988b98": "2a6563",
"9170b9": "2f667c",
"c4cce1": "0d484a",
"e6d3e9": "9cead8",
"bfacc1": "5db6a9",
"515f70": "a34205",
"c5cee3": "f7af58",
"8e96aa": "073338",
"8b93a6": "d27e26",
"80737f": "2b736f",
"4b454f": "194f51",
"36404c": "842401",
"b791f2": "4a9699"
}
}

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "6706_2.png",
"format": "RGBA8888",
"size": {
"w": 82,
"h": 82
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 82,
"h": 72
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 82,
"h": 72
},
"frame": {
"x": 0,
"y": 0,
"w": 82,
"h": 72
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:02eb46aa66ac70df612e129b7801a85c:a77cca14b23f4f3aece64d1a82449a0f:d60cc2e5ae2bd18de8ee3ab0649593ee$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "6706_3.png",
"format": "RGBA8888",
"size": {
"w": 82,
"h": 82
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 82,
"h": 72
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 82,
"h": 72
},
"frame": {
"x": 0,
"y": 0,
"w": 82,
"h": 72
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:02eb46aa66ac70df612e129b7801a85c:a77cca14b23f4f3aece64d1a82449a0f:d60cc2e5ae2bd18de8ee3ab0649593ee$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -3721,8 +3721,8 @@
],
"6706": [
0,
2,
2
1,
1
],
"6713": [
0,
@ -7754,8 +7754,8 @@
],
"6706": [
0,
2,
2
1,
1
],
"6713": [
0,
@ -8493,8 +8493,8 @@
],
"705": [
0,
2,
2
1,
1
],
"706": [
0,
@ -9568,8 +9568,8 @@
],
"6706": [
0,
2,
2
1,
1
],
"female": {},
"back": {
@ -11095,8 +11095,8 @@
],
"6706": [
0,
2,
2
1,
1
],
"6713": [
0,

View File

@ -0,0 +1,38 @@
{
"1": {
"566678": "197497",
"8e96aa": "3b235c",
"929aad": "3aa8c4",
"625287": "4e4094",
"536273": "301848",
"988b98": "b24c86",
"36404c": "0c5474",
"c4cce1": "513981",
"e6d3e9": "f1a4c5",
"bfacc1": "d074a0",
"546475": "0e6296",
"c5cee3": "63cee1",
"80737f": "8a2166",
"4b454f": "6f1357",
"9170b9": "8b69c3",
"b791f2": "c7a1e5"
},
"2": {
"566678": "a34205",
"8e96aa": "073338",
"929aad": "d27e26",
"625287": "0e3f47",
"536273": "042329",
"988b98": "2b736f",
"36404c": "842401",
"c4cce1": "0d484a",
"e6d3e9": "9cead8",
"bfacc1": "5db6a9",
"546475": "8e480b",
"c5cee3": "f7af58",
"80737f": "194f51",
"4b454f": "274159",
"9170b9": "2f667c",
"b791f2": "4a9699"
}
}

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "6706_2.png",
"format": "RGBA8888",
"size": {
"w": 79,
"h": 79
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 79,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 79,
"h": 73
},
"frame": {
"x": 0,
"y": 0,
"w": 79,
"h": 73
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:64f7e6dfa489012922487e45ba53d557:4d24652b372939abe499497c4b6647b0:d60cc2e5ae2bd18de8ee3ab0649593ee$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,41 +0,0 @@
{
"textures": [
{
"image": "6706_3.png",
"format": "RGBA8888",
"size": {
"w": 79,
"h": 79
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 79,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 79,
"h": 73
},
"frame": {
"x": 0,
"y": 0,
"w": 79,
"h": 73
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:64f7e6dfa489012922487e45ba53d557:4d24652b372939abe499497c4b6647b0:d60cc2e5ae2bd18de8ee3ab0649593ee$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,40 @@
{
"1": {
"566678": "0e6296",
"e0e4f4": "513981",
"625287": "4e4094",
"536273": "1f1233",
"988b98": "b24c86",
"36404c": "0c5474",
"c4cce1": "3b235c",
"e6d3e9": "f1a4c5",
"bfacc1": "da75a5",
"515f70": "197497",
"c5cee3": "63cee1",
"b791f2": "c7a1e5",
"8b93a6": "3aa8c4",
"80737f": "8a2166",
"4b454f": "6f1357",
"9170b9": "8b69c3",
"8e96aa": "301848"
},
"2": {
"566678": "8e480b",
"e0e4f4": "176463",
"625287": "274159",
"536273": "02262c",
"988b98": "2a6563",
"36404c": "842401",
"c4cce1": "0d484a",
"e6d3e9": "9cead8",
"bfacc1": "5db6a9",
"515f70": "a34205",
"c5cee3": "f7af58",
"b791f2": "4a9699",
"8b93a6": "d27e26",
"80737f": "2b736f",
"4b454f": "194f51",
"9170b9": "2f667c",
"8e96aa": "073338"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,33 @@
{
"1": {
"101010":"101010",
"4d454d":"8a2166",
"807380":"b93f84",
"bfacbf":"e56ca6",
"f2daf2":"fbb3d2",
"665980":"4e4094",
"8f7db3":"8b69c3",
"b8a1e5":"c7a1e5",
"4d993d":"aa6a00",
"66cc52":"ffd047",
"4e9c3e":"0c5474",
"67cf53":"3aa8c4",
"b6f2aa":"63cee1"
},
"2": {
"101010":"101010",
"4d454d":"194f51",
"807380":"2b736f",
"bfacbf":"5db6a9",
"f2daf2":"9cead8",
"665980":"274159",
"8f7db3":"2f667c",
"b8a1e5":"4a9699",
"4d993d":"007d61",
"66cc52":"49ffbf",
"4e9c3e":"842401",
"67cf53":"a34205",
"b6f2aa":"d27e26"
}
}

View File

@ -1,272 +0,0 @@
{
"textures": [
{
"image": "705_2.png",
"format": "RGBA8888",
"size": {
"w": 154,
"h": 154
},
"scale": 1,
"frames": [
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 46,
"h": 58
},
"frame": {
"x": 0,
"y": 0,
"w": 46,
"h": 58
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 46,
"h": 58
},
"frame": {
"x": 0,
"y": 0,
"w": 46,
"h": 58
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 2,
"y": 0,
"w": 45,
"h": 58
},
"frame": {
"x": 46,
"y": 0,
"w": 45,
"h": 58
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 2,
"y": 0,
"w": 45,
"h": 58
},
"frame": {
"x": 46,
"y": 0,
"w": 45,
"h": 58
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 45,
"h": 58
},
"frame": {
"x": 91,
"y": 0,
"w": 45,
"h": 58
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 1,
"y": 0,
"w": 42,
"h": 58
},
"frame": {
"x": 0,
"y": 58,
"w": 42,
"h": 58
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 1,
"y": 0,
"w": 42,
"h": 58
},
"frame": {
"x": 0,
"y": 58,
"w": 42,
"h": 58
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 41,
"h": 58
},
"frame": {
"x": 42,
"y": 58,
"w": 41,
"h": 58
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 41,
"h": 58
},
"frame": {
"x": 42,
"y": 58,
"w": 41,
"h": 58
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 36,
"h": 58
},
"frame": {
"x": 83,
"y": 58,
"w": 36,
"h": 58
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 36,
"h": 58
},
"frame": {
"x": 83,
"y": 58,
"w": 36,
"h": 58
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 35,
"h": 58
},
"frame": {
"x": 119,
"y": 58,
"w": 35,
"h": 58
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4bf155254b23c88780e7eee282256589:82bb727988054c3064e203b6908ff464:6b57e983626c7fc9144ab67f30c66814$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1,272 +0,0 @@
{
"textures": [
{
"image": "705_3.png",
"format": "RGBA8888",
"size": {
"w": 154,
"h": 154
},
"scale": 1,
"frames": [
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 46,
"h": 58
},
"frame": {
"x": 0,
"y": 0,
"w": 46,
"h": 58
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 46,
"h": 58
},
"frame": {
"x": 0,
"y": 0,
"w": 46,
"h": 58
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 2,
"y": 0,
"w": 45,
"h": 58
},
"frame": {
"x": 46,
"y": 0,
"w": 45,
"h": 58
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 2,
"y": 0,
"w": 45,
"h": 58
},
"frame": {
"x": 46,
"y": 0,
"w": 45,
"h": 58
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 45,
"h": 58
},
"frame": {
"x": 91,
"y": 0,
"w": 45,
"h": 58
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 1,
"y": 0,
"w": 42,
"h": 58
},
"frame": {
"x": 0,
"y": 58,
"w": 42,
"h": 58
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 1,
"y": 0,
"w": 42,
"h": 58
},
"frame": {
"x": 0,
"y": 58,
"w": 42,
"h": 58
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 41,
"h": 58
},
"frame": {
"x": 42,
"y": 58,
"w": 41,
"h": 58
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 41,
"h": 58
},
"frame": {
"x": 42,
"y": 58,
"w": 41,
"h": 58
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 36,
"h": 58
},
"frame": {
"x": 83,
"y": 58,
"w": 36,
"h": 58
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 36,
"h": 58
},
"frame": {
"x": 83,
"y": 58,
"w": 36,
"h": 58
}
},
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 49,
"h": 58
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 35,
"h": 58
},
"frame": {
"x": 119,
"y": 58,
"w": 35,
"h": 58
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4bf155254b23c88780e7eee282256589:82bb727988054c3064e203b6908ff464:6b57e983626c7fc9144ab67f30c66814$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,38 @@
{
"1": {
"566678": "197497",
"8e96aa": "3b235c",
"929aad": "3aa8c4",
"625287": "4e4094",
"536273": "301848",
"988b98": "b24c86",
"36404c": "0c5474",
"c4cce1": "513981",
"e6d3e9": "f1a4c5",
"bfacc1": "d074a0",
"546475": "0e6296",
"c5cee3": "63cee1",
"80737f": "8a2166",
"4b454f": "6f1357",
"9170b9": "8b69c3",
"b791f2": "c7a1e5"
},
"2": {
"566678": "a34205",
"8e96aa": "073338",
"929aad": "d27e26",
"625287": "0e3f47",
"536273": "042329",
"988b98": "2b736f",
"36404c": "842401",
"c4cce1": "0d484a",
"e6d3e9": "9cead8",
"bfacc1": "5db6a9",
"546475": "8e480b",
"c5cee3": "f7af58",
"80737f": "194f51",
"4b454f": "274159",
"9170b9": "2f667c",
"b791f2": "4a9699"
}
}

View File

@ -1,776 +0,0 @@
{
"textures": [
{
"image": "6706_2.png",
"format": "RGBA8888",
"size": {
"w": 358,
"h": 358
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 4,
"w": 84,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 84,
"h": 69
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 4,
"w": 84,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 84,
"h": 69
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 1,
"w": 83,
"h": 72
},
"frame": {
"x": 84,
"y": 0,
"w": 83,
"h": 72
}
},
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 1,
"w": 83,
"h": 72
},
"frame": {
"x": 84,
"y": 0,
"w": 83,
"h": 72
}
},
{
"filename": "0034.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 1,
"w": 83,
"h": 72
},
"frame": {
"x": 0,
"y": 69,
"w": 83,
"h": 72
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 167,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 167,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0035.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 250,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0036.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 250,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 167,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 167,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0013.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 83,
"y": 72,
"w": 82,
"h": 73
}
},
{
"filename": "0014.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 83,
"y": 72,
"w": 82,
"h": 73
}
},
{
"filename": "0025.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 0,
"y": 141,
"w": 82,
"h": 73
}
},
{
"filename": "0026.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 0,
"y": 141,
"w": 82,
"h": 73
}
},
{
"filename": "0027.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 0,
"y": 141,
"w": 82,
"h": 73
}
},
{
"filename": "0032.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 249,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0033.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 249,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 0,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 0,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0017.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 0,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0018.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 0,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0028.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 81,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0029.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 81,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0021.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 81,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0022.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 81,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0015.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 165,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0016.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 165,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0023.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 247,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0024.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 247,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 162,
"y": 215,
"w": 80,
"h": 73
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 162,
"y": 215,
"w": 80,
"h": 73
}
},
{
"filename": "0019.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 3,
"w": 81,
"h": 70
},
"frame": {
"x": 162,
"y": 288,
"w": 81,
"h": 70
}
},
{
"filename": "0020.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 3,
"w": 81,
"h": 70
},
"frame": {
"x": 162,
"y": 288,
"w": 81,
"h": 70
}
},
{
"filename": "0030.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 242,
"y": 215,
"w": 80,
"h": 73
}
},
{
"filename": "0031.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 242,
"y": 215,
"w": 80,
"h": 73
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:5d65e2c5a6a97b7c7014a175ce3592af:3255e87f637a475d82734fc7d93baf71:d60cc2e5ae2bd18de8ee3ab0649593ee$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,776 +0,0 @@
{
"textures": [
{
"image": "6706_3.png",
"format": "RGBA8888",
"size": {
"w": 358,
"h": 358
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 4,
"w": 84,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 84,
"h": 69
}
},
{
"filename": "0002.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 4,
"w": 84,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 84,
"h": 69
}
},
{
"filename": "0005.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 1,
"w": 83,
"h": 72
},
"frame": {
"x": 84,
"y": 0,
"w": 83,
"h": 72
}
},
{
"filename": "0006.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 1,
"w": 83,
"h": 72
},
"frame": {
"x": 84,
"y": 0,
"w": 83,
"h": 72
}
},
{
"filename": "0034.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 1,
"w": 83,
"h": 72
},
"frame": {
"x": 0,
"y": 69,
"w": 83,
"h": 72
}
},
{
"filename": "0003.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 167,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0004.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 167,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0035.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 250,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0036.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 3,
"w": 83,
"h": 70
},
"frame": {
"x": 250,
"y": 0,
"w": 83,
"h": 70
}
},
{
"filename": "0007.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 167,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0008.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 167,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0013.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 83,
"y": 72,
"w": 82,
"h": 73
}
},
{
"filename": "0014.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 83,
"y": 72,
"w": 82,
"h": 73
}
},
{
"filename": "0025.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 0,
"y": 141,
"w": 82,
"h": 73
}
},
{
"filename": "0026.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 0,
"y": 141,
"w": 82,
"h": 73
}
},
{
"filename": "0027.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 3,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 0,
"y": 141,
"w": 82,
"h": 73
}
},
{
"filename": "0032.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 249,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0033.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 4,
"y": 0,
"w": 82,
"h": 73
},
"frame": {
"x": 249,
"y": 70,
"w": 82,
"h": 73
}
},
{
"filename": "0011.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 0,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0012.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 0,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0017.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 0,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0018.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 0,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0028.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 81,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0029.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 5,
"y": 0,
"w": 81,
"h": 73
},
"frame": {
"x": 81,
"y": 214,
"w": 81,
"h": 73
}
},
{
"filename": "0021.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 81,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0022.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 1,
"y": 2,
"w": 81,
"h": 71
},
"frame": {
"x": 81,
"y": 287,
"w": 81,
"h": 71
}
},
{
"filename": "0015.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 165,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0016.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 165,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0023.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 247,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0024.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 2,
"y": 1,
"w": 82,
"h": 72
},
"frame": {
"x": 247,
"y": 143,
"w": 82,
"h": 72
}
},
{
"filename": "0009.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 162,
"y": 215,
"w": 80,
"h": 73
}
},
{
"filename": "0010.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 162,
"y": 215,
"w": 80,
"h": 73
}
},
{
"filename": "0019.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 3,
"w": 81,
"h": 70
},
"frame": {
"x": 162,
"y": 288,
"w": 81,
"h": 70
}
},
{
"filename": "0020.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 0,
"y": 3,
"w": 81,
"h": 70
},
"frame": {
"x": 162,
"y": 288,
"w": 81,
"h": 70
}
},
{
"filename": "0030.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 242,
"y": 215,
"w": 80,
"h": 73
}
},
{
"filename": "0031.png",
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 86,
"h": 73
},
"spriteSourceSize": {
"x": 6,
"y": 0,
"w": 80,
"h": 73
},
"frame": {
"x": 242,
"y": 215,
"w": 80,
"h": 73
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:5d65e2c5a6a97b7c7014a175ce3592af:3255e87f637a475d82734fc7d93baf71:d60cc2e5ae2bd18de8ee3ab0649593ee$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -2316,7 +2316,10 @@ export default class BattleScene extends SceneBase {
}
}
this.currentPhase?.start();
if (this.currentPhase) {
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
this.currentPhase.start();
}
}
overridePhase(phase: Phase): boolean {
@ -2326,6 +2329,7 @@ export default class BattleScene extends SceneBase {
this.standbyPhase = this.currentPhase;
this.currentPhase = phase;
console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;");
phase.start();
return true;
@ -2676,7 +2680,7 @@ export default class BattleScene extends SceneBase {
}
/**
* Removes all modifiers from enemy of PersistentModifier type
* Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type
*/
clearEnemyModifiers(): void {
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier);
@ -2687,10 +2691,11 @@ export default class BattleScene extends SceneBase {
}
/**
* Removes all modifiers from enemy of PokemonHeldItemModifier type
* Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type
* @param pokemon - If specified, only removes held items from that {@linkcode Pokemon}
*/
clearEnemyHeldItemModifiers(): void {
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier);
clearEnemyHeldItemModifiers(pokemon?: Pokemon): void {
const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon(this) === pokemon));
for (const m of modifiersToRemove) {
this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1);
}

View File

@ -118,6 +118,14 @@ export class Ability implements Localizable {
this.nameAppend += " (N)";
return this;
}
/**
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
* @returns the ability
*/
edgeCase(): this {
return this;
}
}
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean | Promise<boolean>;
@ -634,15 +642,15 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
* Examples include: Absorb, Draining Kiss, Bitter Blade, etc.
* Also displays a message to show this ability was activated.
* @param pokemon {@linkcode Pokemon} with this ability
* @param passive N/A
* @param _passive N/A
* @param attacker {@linkcode Pokemon} that is attacking this Pokemon
* @param move {@linkcode PokemonMove} that is being used
* @param hitResult N/A
* @args N/A
* @param _hitResult N/A
* @param _args N/A
* @returns true if healing should be reversed on a healing move, false otherwise.
*/
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.hasAttr(HitHealAttr)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) {
pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) }));
}
@ -669,8 +677,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
this.allOthers = allOthers;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) {
return true;
}
@ -707,13 +715,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
this.selfTarget = selfTarget;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate);
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
const damageReceived = lastAttackReceived?.damage || 0;
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) {
if (!simulated ) {
if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages));
}
return true;
@ -734,8 +742,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag;
if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) {
if (!simulated) {
@ -758,8 +766,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
this.tagType = tagType;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
if (!pokemon.getTag(this.tagType) && !simulated) {
pokemon.addTag(this.tagType, undefined, undefined, pokemon.id);
pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }));
@ -771,8 +779,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
}
export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) {
return true;
}
@ -787,7 +795,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendTypeChange", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName,
@ -805,8 +813,8 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
this.terrainType = terrainType;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean {
if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) {
return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined);
} else {
@ -829,8 +837,9 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
this.effects = effects;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
if (simulated) {
return attacker.canSetStatus(effect, true, false, pokemon);
@ -869,8 +878,8 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
this.turnCount = turnCount;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) {
if (simulated) {
return attacker.canAddTag(this.tagType);
} else {
@ -893,7 +902,11 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
this.stages = stages;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.hitsSubstitute(attacker, pokemon)) {
return false;
}
if (!simulated) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
}
@ -901,7 +914,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
return true;
}
getCondition(): AbAttrCondition {
override getCondition(): AbAttrCondition {
return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical;
}
}
@ -915,8 +928,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
this.damageRatio = damageRatio;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER);
attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio));
return true;
@ -925,7 +939,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendContactDamage", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName
@ -948,8 +962,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
this.turns = turns;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) {
if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) {
return false;
} else {
@ -963,24 +977,24 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr {
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName });
}
}
export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
private weatherType: WeatherType;
protected condition: PokemonDefendCondition | null;
protected condition?: PokemonDefendCondition;
constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) {
super();
this.weatherType = weatherType;
this.condition = condition ?? null;
this.condition = condition;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (this.condition !== null && !this.condition(pokemon, attacker, move)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) {
return false;
}
if (!pokemon.scene.arena.weather?.isImmutable()) {
@ -999,8 +1013,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
super();
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)
&& !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) {
const tempAbilityId = attacker.getAbility().id;
attacker.summonData.ability = pokemon.getAbility().id;
@ -1012,7 +1027,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) });
}
}
@ -1025,8 +1040,9 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
this.ability = ability;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr)
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) {
attacker.summonData.ability = this.ability;
}
@ -1037,7 +1053,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return i18next.t("abilityTriggers:postDefendAbilityGive", {
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
abilityName
@ -1056,8 +1072,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
this.chance = chance;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
if (attacker.getTag(BattlerTagType.DISABLED) === null) {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean {
if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) {
if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) {
if (simulated) {
return true;
@ -1724,17 +1740,17 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr {
}
export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
private condition: PokemonDefendCondition | null;
private condition?: PokemonDefendCondition;
constructor(condition?: PokemonDefendCondition) {
super();
this.condition = condition ?? null;
this.condition = condition;
}
applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise<boolean> {
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise<boolean> {
return new Promise<boolean>(resolve => {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) {
if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) {
const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable);
if (heldItems.length) {
const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)];
@ -4476,7 +4492,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
private multiplier: number;
private tagType: BattlerTagType;
private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined;
private recoilDamageFunc?: ((pokemon: Pokemon) => number);
private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string;
constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) {
@ -4492,16 +4508,16 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
* Applies the pre-defense ability to the Pokémon.
* Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form.
*
* @param {Pokemon} pokemon The Pokémon with the ability.
* @param {boolean} passive n/a
* @param {Pokemon} attacker The attacking Pokémon.
* @param {PokemonMove} move The move being used.
* @param {Utils.BooleanHolder} cancelled n/a
* @param {any[]} args Additional arguments.
* @returns {boolean} Whether the immunity was applied.
* @param pokemon The Pokémon with the ability.
* @param _passive n/a
* @param attacker The attacking Pokémon.
* @param move The move being used.
* @param _cancelled n/a
* @param args Additional arguments.
* @returns `true` if the immunity was applied.
*/
applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move)) {
override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) {
if (!simulated) {
(args[0] as Utils.NumberHolder).value = this.multiplier;
pokemon.removeTag(this.tagType);
@ -4517,12 +4533,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
/**
* Gets the message triggered when the Pokémon avoids damage using the form-changing ability.
* @param {Pokemon} pokemon The Pokémon with the ability.
* @param {string} abilityName The name of the ability.
* @param {...any} args n/a
* @returns {string} The trigger message.
* @param pokemon The Pokémon with the ability.
* @param abilityName The name of the ability.
* @param _args n/a
* @returns The trigger message.
*/
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string {
return this.triggerMessageFunc(pokemon, abilityName);
}
}
@ -4898,7 +4914,7 @@ export function initAbilities() {
.ignorable(),
new Ability(Abilities.SHIELD_DUST, 3)
.attr(IgnoreMoveEffectsAbAttr)
.partial(),
.edgeCase(), // Does not work with secret power (unimplemented)
new Ability(Abilities.OWN_TEMPO, 3)
.attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED)
.attr(IntimidateImmunityAbAttr)
@ -4943,7 +4959,7 @@ export function initAbilities() {
.ignorable(),
new Ability(Abilities.SERENE_GRACE, 3)
.attr(MoveEffectChanceMultiplierAbAttr, 2)
.partial(),
.edgeCase(), // does not work with secret power (unimplemented)
new Ability(Abilities.SWIFT_SWIM, 3)
.attr(StatMultiplierAbAttr, Stat.SPD, 2)
.condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)),
@ -5227,7 +5243,8 @@ export function initAbilities() {
new Ability(Abilities.SHEER_FORCE, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
.attr(MoveEffectChanceMultiplierAbAttr, 0)
.partial(),
.edgeCase() // Should disable shell bell and Meloetta's relic song transformation
.edgeCase(), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
new Ability(Abilities.CONTRARY, 5)
.attr(StatStageChangeMultiplierAbAttr, -1)
.ignorable(),
@ -5270,7 +5287,7 @@ export function initAbilities() {
/** Rate is doubled when under sun {@link https://dex.pokemonshowdown.com/abilities/harvest} */
(pokemon) => 0.5 * (getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)(pokemon) ? 2 : 1)
)
.partial(),
.edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented)
new Ability(Abilities.TELEPATHY, 5)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove)
.ignorable(),
@ -5349,7 +5366,7 @@ export function initAbilities() {
.bypassFaint(),
new Ability(Abilities.VICTORY_STAR, 5)
.attr(StatMultiplierAbAttr, Stat.ACC, 1.1)
.partial(),
.partial(), // Does not boost ally's accuracy
new Ability(Abilities.TURBOBLAZE, 5)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(MoveAbilityBypassAbAttr),
@ -5460,7 +5477,7 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.partial(),
.partial(), // Meteor form should protect against status effects and yawn
new Ability(Abilities.STAKEOUT, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2),
new Ability(Abilities.WATER_BUBBLE, 7)
@ -5503,7 +5520,8 @@ export function initAbilities() {
.attr(NoFusionAbilityAbAttr)
// Add BattlerTagType.DISGUISE if the pokemon is in its disguised form
.conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false)
.attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
.attr(FormBlockDamageAbAttr,
(target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE,
(pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }),
(pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8))
.attr(PostBattleInitFormChangeAbAttr, () => 0)
@ -5529,9 +5547,9 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.CORROSION, 7) // TODO: Test Corrosion against Magic Bounce once it is implemented
new Ability(Abilities.CORROSION, 7)
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ])
.partial(),
.edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet)
new Ability(Abilities.COMATOSE, 7)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
@ -5667,7 +5685,8 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0)
// When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE
.attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW)
.attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
.attr(FormBlockDamageAbAttr,
(target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE,
(pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }))
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.bypassFaint()
@ -5685,7 +5704,7 @@ export function initAbilities() {
new Ability(Abilities.WANDERING_SPIRIT, 8)
.attr(PostDefendAbilitySwapAbAttr)
.bypassFaint()
.partial(),
.edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil
new Ability(Abilities.GORILLA_TACTICS, 8)
.attr(GorillaTacticsAbAttr),
new Ability(Abilities.NEUTRALIZING_GAS, 8)
@ -5694,7 +5713,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.partial(),
.partial(), // A bunch of weird interactions with other abilities being suppressed then unsuppressed
new Ability(Abilities.PASTEL_VEIL, 8)
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
@ -5799,7 +5818,7 @@ export function initAbilities() {
new Ability(Abilities.GOOD_AS_GOLD, 9)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS)
.ignorable()
.partial(),
.partial(), // Lots of weird interactions with moves and abilities such as negating status moves that target the field
new Ability(Abilities.VESSEL_OF_RUIN, 9)
.attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75)
.attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonVesselOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPATK)) }))
@ -5832,7 +5851,7 @@ export function initAbilities() {
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5),
new Ability(Abilities.SUPREME_OVERLORD, 9)
.attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 5))
.partial(),
.partial(), // Counter resets every wave
new Ability(Abilities.COSTAR, 9)
.attr(PostSummonCopyAllyStatsAbAttr),
new Ability(Abilities.TOXIC_DEBRIS, 9)
@ -5865,25 +5884,25 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.partial(),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPDEF ], 1, true)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.partial(),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.ATK ], 1, true)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.partial(),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.DEF ], 1, true)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.partial(),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.TERA_SHIFT, 9)
.attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1)
.attr(UncopiableAbilityAbAttr)

View File

@ -1,10 +1,10 @@
import { Arena } from "#app/field/arena";
import BattleScene from "#app/battle-scene";
import { Type } from "#app/data/type";
import * as Utils from "#app/utils";
import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils";
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move";
import { getPokemonNameWithAffix } from "#app/messages";
import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "#app/field/pokemon";
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
import { StatusEffect } from "#app/data/status-effect";
import { BattlerIndex } from "#app/battle";
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
@ -28,22 +28,15 @@ export enum ArenaTagSide {
}
export abstract class ArenaTag {
public tagType: ArenaTagType;
public turnCount: integer;
public sourceMove?: Moves;
public sourceId?: integer;
public side: ArenaTagSide;
constructor(
public tagType: ArenaTagType,
public turnCount: number,
public sourceMove?: Moves,
public sourceId?: number,
public side: ArenaTagSide = ArenaTagSide.BOTH
) {}
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
this.tagType = tagType;
this.turnCount = turnCount;
this.sourceMove = sourceMove;
this.sourceId = sourceId;
this.side = side;
}
apply(arena: Arena, args: any[]): boolean {
apply(arena: Arena, simulated: boolean, ...args: unknown[]): boolean {
return true;
}
@ -66,6 +59,44 @@ export abstract class ArenaTag {
? allMoves[this.sourceMove].name
: null;
}
/**
* When given a arena tag or json representing one, load the data for it.
* This is meant to be inherited from by any arena tag with custom attributes
* @param {ArenaTag | any} source An arena tag
*/
loadTag(source : ArenaTag | any) : void {
this.turnCount = source.turnCount;
this.sourceMove = source.sourceMove;
this.sourceId = source.sourceId;
this.side = source.side;
}
/**
* Helper function that retrieves the source Pokemon
* @param scene medium to retrieve the source Pokemon
* @returns The source {@linkcode Pokemon} or `null` if none is found
*/
public getSourcePokemon(scene: BattleScene): Pokemon | null {
return this.sourceId ? scene.getPokemonById(this.sourceId) : null;
}
/**
* Helper function that retrieves the Pokemon affected
* @param scene - medium to retrieve the involved Pokemon
* @returns list of PlayerPokemon or EnemyPokemon on the field
*/
public getAffectedPokemon(scene: BattleScene): Pokemon[] {
switch (this.side) {
case ArenaTagSide.PLAYER:
return scene.getPlayerField() ?? [];
case ArenaTagSide.ENEMY:
return scene.getEnemyField() ?? [];
case ArenaTagSide.BOTH:
default:
return scene.getField(true) ?? [];
}
}
}
/**
@ -73,7 +104,7 @@ export abstract class ArenaTag {
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
*/
export class MistTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
}
@ -91,10 +122,20 @@ export class MistTag extends ArenaTag {
}
}
apply(arena: Arena, args: any[]): boolean {
(args[0] as Utils.BooleanHolder).value = true;
/**
* Cancels the lowering of stats
* @param arena the {@linkcode Arena} containing this effect
* @param simulated `true` if the effect should be applied quietly
* @param cancelled a {@linkcode BooleanHolder} whose value is set to `true`
* to flag the stat reduction as cancelled
* @returns `true` if a stat reduction was cancelled; `false` otherwise
*/
override apply(arena: Arena, simulated: boolean, cancelled: BooleanHolder): boolean {
cancelled.value = true;
if (!simulated) {
arena.scene.queueMessage(i18next.t("arenaTag:mistApply"));
}
return true;
}
@ -117,7 +158,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
* @param side - The side (player or enemy) the tag affects.
* @param weakenedCategories - The categories of moves that are weakened by this tag.
*/
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
constructor(tagType: ArenaTagType, turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
super(tagType, turnCount, sourceMove, sourceId, side);
this.weakenedCategories = weakenedCategories;
@ -126,17 +167,15 @@ export class WeakenMoveScreenTag extends ArenaTag {
/**
* Applies the weakening effect to the move.
*
* @param arena - The arena where the move is applied.
* @param args - The arguments for the move application.
* @param args[0] - The category of the move.
* @param args[1] - A boolean indicating whether it is a double battle.
* @param args[2] - An object of type `Utils.NumberHolder` that holds the damage multiplier
*
* @returns True if the move was weakened, otherwise false.
* @param arena the {@linkcode Arena} where the move is applied.
* @param simulated n/a
* @param moveCategory the attacking move's {@linkcode MoveCategory}.
* @param damageMultiplier A {@linkcode NumberHolder} containing the damage multiplier
* @returns `true` if the attacking move was weakened; `false` otherwise.
*/
apply(arena: Arena, args: any[]): boolean {
if (this.weakenedCategories.includes((args[0] as MoveCategory))) {
(args[2] as Utils.NumberHolder).value = (args[1] as boolean) ? 2732 / 4096 : 0.5;
override apply(arena: Arena, simulated: boolean, moveCategory: MoveCategory, damageMultiplier: NumberHolder): boolean {
if (this.weakenedCategories.includes(moveCategory)) {
damageMultiplier.value = arena.scene.currentBattle.double ? 2732 / 4096 : 0.5;
return true;
}
return false;
@ -148,7 +187,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
* Used by {@linkcode Moves.REFLECT}
*/
class ReflectTag extends WeakenMoveScreenTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [ MoveCategory.PHYSICAL ]);
}
@ -164,7 +203,7 @@ class ReflectTag extends WeakenMoveScreenTag {
* Used by {@linkcode Moves.LIGHT_SCREEN}
*/
class LightScreenTag extends WeakenMoveScreenTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [ MoveCategory.SPECIAL ]);
}
@ -180,7 +219,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
* Used by {@linkcode Moves.AURORA_VEIL}
*/
class AuroraVeilTag extends WeakenMoveScreenTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ]);
}
@ -203,7 +242,7 @@ export class ConditionalProtectTag extends ArenaTag {
/** Does this apply to all moves, including those that ignore other forms of protection? */
protected ignoresBypass: boolean;
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
super(tagType, 1, sourceMove, sourceId, side);
this.protectConditionFunc = condition;
@ -218,39 +257,35 @@ export class ConditionalProtectTag extends ArenaTag {
onRemove(arena: Arena): void { }
/**
* apply(): Checks incoming moves against the condition function
* Checks incoming moves against the condition function
* and protects the target if conditions are met
* @param arena The arena containing this tag
* @param args\[0\] (Utils.BooleanHolder) Signals if the move is cancelled
* @param args\[1\] (Pokemon) The Pokemon using the move
* @param args\[2\] (Pokemon) The intended target of the move
* @param args\[3\] (Moves) The parameters to the condition function
* @param args\[4\] (Utils.BooleanHolder) Signals if the applied protection supercedes protection-ignoring effects
* @returns
* @param arena the {@linkcode Arena} containing this tag
* @param simulated `true` if the tag is applied quietly; `false` otherwise.
* @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against
* @param attacker the attacking {@linkcode Pokemon}
* @param defender the defending {@linkcode Pokemon}
* @param moveId the {@linkcode Moves | identifier} for the move being used
* @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection
* @returns `true` if this tag protected against the attack; `false` otherwise
*/
apply(arena: Arena, args: any[]): boolean {
const [ cancelled, user, target, moveId, ignoresBypass ] = args;
override apply(arena: Arena, simulated: boolean, isProtected: BooleanHolder, attacker: Pokemon, defender: Pokemon,
moveId: Moves, ignoresProtectBypass: BooleanHolder): boolean {
if (cancelled instanceof Utils.BooleanHolder
&& user instanceof Pokemon
&& target instanceof Pokemon
&& typeof moveId === "number"
&& ignoresBypass instanceof Utils.BooleanHolder) {
if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer()
if ((this.side === ArenaTagSide.PLAYER) === defender.isPlayer()
&& this.protectConditionFunc(arena, moveId)) {
if (!cancelled.value) {
cancelled.value = true;
user.stopMultiHit(target);
if (!isProtected.value) {
isProtected.value = true;
if (!simulated) {
attacker.stopMultiHit(defender);
new CommonBattleAnim(CommonAnim.PROTECT, target).play(arena.scene);
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(target) }));
new CommonBattleAnim(CommonAnim.PROTECT, defender).play(arena.scene);
arena.scene.queueMessage(i18next.t("arenaTag:conditionalProtectApply", { moveName: super.getMoveName(), pokemonNameWithAffix: getPokemonNameWithAffix(defender) }));
}
}
ignoresBypass.value = ignoresBypass.value || this.ignoresBypass;
ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass;
return true;
}
}
return false;
}
}
@ -265,7 +300,7 @@ export class ConditionalProtectTag extends ArenaTag {
*/
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
const move = allMoves[moveId];
const priority = new Utils.IntegerHolder(move.priority);
const priority = new NumberHolder(move.priority);
const effectPhase = arena.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
@ -281,7 +316,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
* Condition: The incoming move has increased priority.
*/
class QuickGuardTag extends ConditionalProtectTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
}
}
@ -312,7 +347,7 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =
* can be an ally or enemy.
*/
class WideGuardTag extends ConditionalProtectTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
}
}
@ -334,7 +369,7 @@ const MatBlockConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =>
* Condition: The incoming move is a Physical or Special attack move.
*/
class MatBlockTag extends ConditionalProtectTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
}
@ -372,7 +407,7 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (arena, moveId) => {
* not target all Pokemon or sides of the field.
*/
class CraftyShieldTag extends ConditionalProtectTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
}
}
@ -384,12 +419,12 @@ class CraftyShieldTag extends ConditionalProtectTag {
export class NoCritTag extends ArenaTag {
/**
* Constructor method for the NoCritTag class
* @param turnCount `integer` the number of turns this effect lasts
* @param turnCount `number` the number of turns this effect lasts
* @param sourceMove {@linkcode Moves} the move that created this effect
* @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect
* @param sourceId `number` the ID of the {@linkcode Pokemon} that created this effect
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
*/
constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
}
@ -419,7 +454,7 @@ class WishTag extends ArenaTag {
private triggerMessage: string;
private healHp: number;
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side);
}
@ -429,7 +464,7 @@ class WishTag extends ArenaTag {
if (user) {
this.battlerIndex = user.getBattlerIndex();
this.triggerMessage = i18next.t("arenaTag:wishTagOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(user) });
this.healHp = Utils.toDmgValue(user.getMaxHp() / 2);
this.healHp = toDmgValue(user.getMaxHp() / 2);
} else {
console.warn("Failed to get source for WishTag onAdd");
}
@ -460,18 +495,25 @@ export class WeakenMoveTypeTag extends ArenaTag {
* @param sourceMove - The move that created the tag.
* @param sourceId - The ID of the source of the tag.
*/
constructor(tagType: ArenaTagType, turnCount: integer, type: Type, sourceMove: Moves, sourceId: integer) {
constructor(tagType: ArenaTagType, turnCount: number, type: Type, sourceMove: Moves, sourceId: number) {
super(tagType, turnCount, sourceMove, sourceId);
this.weakenedType = type;
}
apply(arena: Arena, args: any[]): boolean {
if ((args[0] as Type) === this.weakenedType) {
(args[1] as Utils.NumberHolder).value *= 0.33;
/**
* Reduces an attack's power by 0.33x if it matches this tag's weakened type.
* @param arena n/a
* @param simulated n/a
* @param type the attack's {@linkcode Type}
* @param power a {@linkcode NumberHolder} containing the attack's power
* @returns `true` if the attack's power was reduced; `false` otherwise.
*/
override apply(arena: Arena, simulated: boolean, type: Type, power: NumberHolder): boolean {
if (type === this.weakenedType) {
power.value *= 0.33;
return true;
}
return false;
}
}
@ -481,7 +523,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
* Weakens Electric type moves for a set amount of turns, usually 5.
*/
class MudSportTag extends WeakenMoveTypeTag {
constructor(turnCount: integer, sourceId: integer) {
constructor(turnCount: number, sourceId: number) {
super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId);
}
@ -499,7 +541,7 @@ class MudSportTag extends WeakenMoveTypeTag {
* Weakens Fire type moves for a set amount of turns, usually 5.
*/
class WaterSportTag extends WeakenMoveTypeTag {
constructor(turnCount: integer, sourceId: integer) {
constructor(turnCount: number, sourceId: number) {
super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId);
}
@ -532,13 +574,12 @@ export class IonDelugeTag extends ArenaTag {
/**
* Converts Normal-type moves to Electric type
* @param arena n/a
* @param args
* - `[0]` {@linkcode Utils.NumberHolder} A container with a move's {@linkcode Type}
* @param simulated n/a
* @param moveType a {@linkcode NumberHolder} containing a move's {@linkcode Type}
* @returns `true` if the given move type changed; `false` otherwise.
*/
apply(arena: Arena, args: any[]): boolean {
const moveType = args[0];
if (moveType instanceof Utils.NumberHolder && moveType.value === Type.NORMAL) {
override apply(arena: Arena, simulated: boolean, moveType: NumberHolder): boolean {
if (moveType.value === Type.NORMAL) {
moveType.value = Type.ELECTRIC;
return true;
}
@ -550,8 +591,8 @@ export class IonDelugeTag extends ArenaTag {
* Abstract class to implement arena traps.
*/
export class ArenaTrapTag extends ArenaTag {
public layers: integer;
public maxLayers: integer;
public layers: number;
public maxLayers: number;
/**
* Creates a new instance of the ArenaTrapTag class.
@ -562,7 +603,7 @@ export class ArenaTrapTag extends ArenaTag {
* @param side - The side (player or enemy) the tag affects.
* @param maxLayers - The maximum amount of layers this tag can have.
*/
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) {
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, maxLayers: number) {
super(tagType, 0, sourceMove, sourceId, side);
this.layers = 1;
@ -577,22 +618,34 @@ export class ArenaTrapTag extends ArenaTag {
}
}
apply(arena: Arena, args: any[]): boolean {
const pokemon = args[0] as Pokemon;
/**
* Activates the hazard effect onto a Pokemon when it enters the field
* @param arena the {@linkcode Arena} containing this tag
* @param simulated if `true`, only checks if the hazard would activate.
* @param pokemon the {@linkcode Pokemon} triggering this hazard
* @returns `true` if this hazard affects the given Pokemon; `false` otherwise.
*/
override apply(arena: Arena, simulated: boolean, pokemon: Pokemon): boolean {
if (this.sourceId === pokemon.id || (this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) {
return false;
}
return this.activateTrap(pokemon);
return this.activateTrap(pokemon, simulated);
}
activateTrap(pokemon: Pokemon): boolean {
activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
return false;
}
getMatchupScoreMultiplier(pokemon: Pokemon): number {
return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
}
loadTag(source: any): void {
super.loadTag(source);
this.layers = source.layers;
this.maxLayers = source.maxLayers;
}
}
/**
@ -601,7 +654,7 @@ export class ArenaTrapTag extends ArenaTag {
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
*/
class SpikesTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
}
@ -614,14 +667,18 @@ class SpikesTag extends ArenaTrapTag {
}
}
activateTrap(pokemon: Pokemon): boolean {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (simulated) {
return !cancelled.value;
}
if (!cancelled.value) {
const damageHpRatio = 1 / (10 - 2 * this.layers);
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio);
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:spikesActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
@ -645,7 +702,7 @@ class SpikesTag extends ArenaTrapTag {
class ToxicSpikesTag extends ArenaTrapTag {
private neutralized: boolean;
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
this.neutralized = false;
}
@ -665,8 +722,11 @@ class ToxicSpikesTag extends ArenaTrapTag {
}
}
activateTrap(pokemon: Pokemon): boolean {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
if (simulated) {
return true;
}
if (pokemon.isOfType(Type.POISON)) {
this.neutralized = true;
if (pokemon.scene.arena.removeTag(this.tagType)) {
@ -703,7 +763,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
class DelayedAttackTag extends ArenaTag {
public targetIndex: BattlerIndex;
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) {
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex) {
super(tagType, 3, sourceMove, sourceId);
this.targetIndex = targetIndex;
@ -728,7 +788,7 @@ class DelayedAttackTag extends ArenaTag {
* who is summoned into the trap, based on the Rock type's type effectiveness.
*/
class StealthRockTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
}
@ -770,8 +830,8 @@ class StealthRockTag extends ArenaTrapTag {
return damageHpRatio;
}
activateTrap(pokemon: Pokemon): boolean {
const cancelled = new Utils.BooleanHolder(false);
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, pokemon, cancelled);
if (cancelled.value) {
@ -781,12 +841,16 @@ class StealthRockTag extends ArenaTrapTag {
const damageHpRatio = this.getDamageHpRatio(pokemon);
if (damageHpRatio) {
const damage = Utils.toDmgValue(pokemon.getMaxHp() * damageHpRatio);
if (simulated) {
return true;
}
const damage = toDmgValue(pokemon.getMaxHp() * damageHpRatio);
pokemon.scene.queueMessage(i18next.t("arenaTag:stealthRockActivateTrap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.damageAndUpdate(damage, HitResult.OTHER);
if (pokemon.turnData) {
pokemon.turnData.damageTaken += damage;
}
return true;
}
return false;
@ -804,7 +868,7 @@ class StealthRockTag extends ArenaTrapTag {
* to any Pokémon who is summoned into this trap.
*/
class StickyWebTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
}
@ -816,14 +880,20 @@ class StickyWebTag extends ArenaTrapTag {
}
}
activateTrap(pokemon: Pokemon): boolean {
override activateTrap(pokemon: Pokemon, simulated: boolean): boolean {
if (pokemon.isGrounded()) {
const cancelled = new Utils.BooleanHolder(false);
const cancelled = new BooleanHolder(false);
applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled);
if (simulated) {
return !cancelled.value;
}
if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
const stages = new Utils.NumberHolder(-1);
const stages = new NumberHolder(-1);
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value));
return true;
}
}
@ -838,12 +908,19 @@ class StickyWebTag extends ArenaTrapTag {
* also reversing the turn order for all Pokémon on the field as well.
*/
export class TrickRoomTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer) {
constructor(turnCount: number, sourceId: number) {
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
}
apply(arena: Arena, args: any[]): boolean {
const speedReversed = args[0] as Utils.BooleanHolder;
/**
* Reverses Speed-based turn order for all Pokemon on the field
* @param arena n/a
* @param simulated n/a
* @param speedReversed a {@linkcode BooleanHolder} used to flag if Speed-based
* turn order should be reversed.
* @returns `true` if turn order is successfully reversed; `false` otherwise
*/
override apply(arena: Arena, simulated: boolean, speedReversed: BooleanHolder): boolean {
speedReversed.value = !speedReversed.value;
return true;
}
@ -866,7 +943,7 @@ export class TrickRoomTag extends ArenaTag {
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
*/
export class GravityTag extends ArenaTag {
constructor(turnCount: integer) {
constructor(turnCount: number) {
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
}
@ -874,7 +951,8 @@ export class GravityTag extends ArenaTag {
arena.scene.queueMessage(i18next.t("arenaTag:gravityOnAdd"));
arena.scene.getField(true).forEach((pokemon) => {
if (pokemon !== null) {
pokemon.removeTag(BattlerTagType.MAGNET_RISEN);
pokemon.removeTag(BattlerTagType.FLOATING);
pokemon.removeTag(BattlerTagType.TELEKINESIS);
}
});
}
@ -890,7 +968,7 @@ export class GravityTag extends ArenaTag {
* Applies this arena tag for 4 turns (including the turn the move was used).
*/
class TailwindTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side);
}
@ -928,7 +1006,7 @@ class TailwindTag extends ArenaTag {
* Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}.
*/
class HappyHourTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side);
}
@ -942,7 +1020,7 @@ class HappyHourTag extends ArenaTag {
}
class SafeguardTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
}
@ -955,42 +1033,35 @@ class SafeguardTag extends ArenaTag {
}
}
class NoneTag extends ArenaTag {
constructor() {
super(ArenaTagType.NONE, 0);
}
}
/**
* This arena tag facilitates the application of the move Imprison
* Imprison remains in effect as long as the source Pokemon is active and present on the field.
* Imprison will apply to any opposing Pokemon that switch onto the field as well.
*/
class ImprisonTag extends ArenaTrapTag {
private source: Pokemon;
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1);
}
/**
* Helper function that retrieves the Pokemon affected
* @param {BattleScene} scene medium to retrieve the involved Pokemon
* @returns list of PlayerPokemon or EnemyPokemon on the field
*/
private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] {
if (!this.source.isPlayer()) {
return scene.getPlayerField() ?? [];
}
return scene.getEnemyField() ?? [];
}
/**
* This function applies the effects of Imprison to the opposing Pokemon already present on the field.
* @param arena
*/
override onAdd({ scene }: Arena) {
this.source = scene.getPokemonById(this.sourceId!)!;
if (this.source) {
const party = this.retrieveField(scene);
party?.forEach((p: PlayerPokemon | EnemyPokemon ) => {
const source = this.getSourcePokemon(scene);
if (source) {
const party = this.getAffectedPokemon(scene);
party?.forEach((p: Pokemon ) => {
if (p.isAllowedInBattle()) {
p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
}
});
scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(this.source) }));
scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) }));
}
}
@ -999,8 +1070,9 @@ class ImprisonTag extends ArenaTrapTag {
* @param _arena
* @returns `true` if the source of the tag is still active on the field | `false` if not
*/
override lapse(_arena: Arena): boolean {
return this.source.isActive(true);
override lapse({ scene }: Arena): boolean {
const source = this.getSourcePokemon(scene);
return source ? source.isActive(true) : false;
}
/**
@ -1009,7 +1081,8 @@ class ImprisonTag extends ArenaTrapTag {
* @returns `true`
*/
override activateTrap(pokemon: Pokemon): boolean {
if (this.source.isActive(true)) {
const source = this.getSourcePokemon(pokemon.scene);
if (source && source.isActive(true) && pokemon.isAllowedInBattle()) {
pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
}
return true;
@ -1020,8 +1093,8 @@ class ImprisonTag extends ArenaTrapTag {
* @param arena
*/
override onRemove({ scene }: Arena): void {
const party = this.retrieveField(scene);
party?.forEach((p: PlayerPokemon | EnemyPokemon) => {
const party = this.getAffectedPokemon(scene);
party?.forEach((p: Pokemon) => {
p.removeTag(BattlerTagType.IMPRISON);
});
}
@ -1054,7 +1127,7 @@ class FireGrassPledgeTag extends ArenaTag {
pokemon.scene.queueMessage(i18next.t("arenaTag:fireGrassPledgeLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
// TODO: Replace this with a proper animation
pokemon.scene.unshiftPhase(new CommonAnimPhase(pokemon.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.MAGMA_STORM));
pokemon.damageAndUpdate(Utils.toDmgValue(pokemon.getMaxHp() / 8));
pokemon.damageAndUpdate(toDmgValue(pokemon.getMaxHp() / 8));
});
return super.lapse(arena);
@ -1078,8 +1151,15 @@ class WaterFirePledgeTag extends ArenaTag {
arena.scene.queueMessage(i18next.t(`arenaTag:waterFirePledgeOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : this.side === ArenaTagSide.ENEMY ? "Enemy" : ""}`));
}
override apply(arena: Arena, args: any[]): boolean {
const moveChance = args[0] as Utils.NumberHolder;
/**
* Doubles the chance for the given move's secondary effect(s) to trigger
* @param arena the {@linkcode Arena} containing this tag
* @param simulated n/a
* @param moveChance a {@linkcode NumberHolder} containing
* the move's current effect chance
* @returns `true` if the move's effect chance was doubled (currently always `true`)
*/
override apply(arena: Arena, simulated: boolean, moveChance: NumberHolder): boolean {
moveChance.value *= 2;
return true;
}
@ -1102,7 +1182,8 @@ class GrassWaterPledgeTag extends ArenaTag {
}
}
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter
export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
switch (tagType) {
case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side);
@ -1163,3 +1244,16 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return null;
}
}
/**
* When given a battler tag or json representing one, creates an actual ArenaTag object with the same data.
* @param {ArenaTag | any} source An arena tag
* @return {ArenaTag} The valid arena tag
*/
export function loadArenaTag(source: ArenaTag | any): ArenaTag {
const tag = getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.targetIndex, source.side)
?? new NoneTag();
tag.loadTag(source);
return tag;
}

View File

@ -0,0 +1,603 @@
import { Species } from "#enums/species";
import { EggTier } from "#enums/egg-type";
/**
* Map of all starters and their respective {@linkcode EggTier}, which determines the type of egg the starter hatches from.
*/
export const speciesEggTiers = {
[Species.BULBASAUR]: EggTier.COMMON,
[Species.CHARMANDER]: EggTier.COMMON,
[Species.SQUIRTLE]: EggTier.COMMON,
[Species.CATERPIE]: EggTier.COMMON,
[Species.WEEDLE]: EggTier.COMMON,
[Species.PIDGEY]: EggTier.COMMON,
[Species.RATTATA]: EggTier.COMMON,
[Species.SPEAROW]: EggTier.COMMON,
[Species.EKANS]: EggTier.COMMON,
[Species.PIKACHU]: EggTier.COMMON,
[Species.SANDSHREW]: EggTier.COMMON,
[Species.NIDORAN_F]: EggTier.COMMON,
[Species.NIDORAN_M]: EggTier.COMMON,
[Species.CLEFAIRY]: EggTier.COMMON,
[Species.VULPIX]: EggTier.COMMON,
[Species.JIGGLYPUFF]: EggTier.COMMON,
[Species.ZUBAT]: EggTier.COMMON,
[Species.ODDISH]: EggTier.COMMON,
[Species.PARAS]: EggTier.COMMON,
[Species.VENONAT]: EggTier.COMMON,
[Species.DIGLETT]: EggTier.COMMON,
[Species.MEOWTH]: EggTier.COMMON,
[Species.PSYDUCK]: EggTier.COMMON,
[Species.MANKEY]: EggTier.RARE,
[Species.GROWLITHE]: EggTier.RARE,
[Species.POLIWAG]: EggTier.COMMON,
[Species.ABRA]: EggTier.RARE,
[Species.MACHOP]: EggTier.COMMON,
[Species.BELLSPROUT]: EggTier.COMMON,
[Species.TENTACOOL]: EggTier.COMMON,
[Species.GEODUDE]: EggTier.COMMON,
[Species.PONYTA]: EggTier.COMMON,
[Species.SLOWPOKE]: EggTier.COMMON,
[Species.MAGNEMITE]: EggTier.RARE,
[Species.FARFETCHD]: EggTier.COMMON,
[Species.DODUO]: EggTier.COMMON,
[Species.SEEL]: EggTier.COMMON,
[Species.GRIMER]: EggTier.COMMON,
[Species.SHELLDER]: EggTier.RARE,
[Species.GASTLY]: EggTier.RARE,
[Species.ONIX]: EggTier.COMMON,
[Species.DROWZEE]: EggTier.COMMON,
[Species.KRABBY]: EggTier.COMMON,
[Species.VOLTORB]: EggTier.COMMON,
[Species.EXEGGCUTE]: EggTier.COMMON,
[Species.CUBONE]: EggTier.COMMON,
[Species.HITMONLEE]: EggTier.RARE,
[Species.HITMONCHAN]: EggTier.RARE,
[Species.LICKITUNG]: EggTier.COMMON,
[Species.KOFFING]: EggTier.COMMON,
[Species.RHYHORN]: EggTier.COMMON,
[Species.CHANSEY]: EggTier.COMMON,
[Species.TANGELA]: EggTier.COMMON,
[Species.KANGASKHAN]: EggTier.RARE,
[Species.HORSEA]: EggTier.COMMON,
[Species.GOLDEEN]: EggTier.COMMON,
[Species.STARYU]: EggTier.COMMON,
[Species.MR_MIME]: EggTier.COMMON,
[Species.SCYTHER]: EggTier.RARE,
[Species.JYNX]: EggTier.RARE,
[Species.ELECTABUZZ]: EggTier.RARE,
[Species.MAGMAR]: EggTier.RARE,
[Species.PINSIR]: EggTier.RARE,
[Species.TAUROS]: EggTier.RARE,
[Species.MAGIKARP]: EggTier.RARE,
[Species.LAPRAS]: EggTier.RARE,
[Species.DITTO]: EggTier.COMMON,
[Species.EEVEE]: EggTier.COMMON,
[Species.PORYGON]: EggTier.RARE,
[Species.OMANYTE]: EggTier.COMMON,
[Species.KABUTO]: EggTier.COMMON,
[Species.AERODACTYL]: EggTier.RARE,
[Species.SNORLAX]: EggTier.RARE,
[Species.ARTICUNO]: EggTier.EPIC,
[Species.ZAPDOS]: EggTier.EPIC,
[Species.MOLTRES]: EggTier.EPIC,
[Species.DRATINI]: EggTier.RARE,
[Species.MEWTWO]: EggTier.LEGENDARY,
[Species.MEW]: EggTier.EPIC,
[Species.CHIKORITA]: EggTier.COMMON,
[Species.CYNDAQUIL]: EggTier.COMMON,
[Species.TOTODILE]: EggTier.COMMON,
[Species.SENTRET]: EggTier.COMMON,
[Species.HOOTHOOT]: EggTier.COMMON,
[Species.LEDYBA]: EggTier.COMMON,
[Species.SPINARAK]: EggTier.COMMON,
[Species.CHINCHOU]: EggTier.COMMON,
[Species.PICHU]: EggTier.COMMON,
[Species.CLEFFA]: EggTier.COMMON,
[Species.IGGLYBUFF]: EggTier.COMMON,
[Species.TOGEPI]: EggTier.COMMON,
[Species.NATU]: EggTier.COMMON,
[Species.MAREEP]: EggTier.COMMON,
[Species.MARILL]: EggTier.RARE,
[Species.SUDOWOODO]: EggTier.COMMON,
[Species.HOPPIP]: EggTier.COMMON,
[Species.AIPOM]: EggTier.COMMON,
[Species.SUNKERN]: EggTier.COMMON,
[Species.YANMA]: EggTier.COMMON,
[Species.WOOPER]: EggTier.COMMON,
[Species.MURKROW]: EggTier.COMMON,
[Species.MISDREAVUS]: EggTier.COMMON,
[Species.UNOWN]: EggTier.COMMON,
[Species.WOBBUFFET]: EggTier.COMMON,
[Species.GIRAFARIG]: EggTier.COMMON,
[Species.PINECO]: EggTier.COMMON,
[Species.DUNSPARCE]: EggTier.COMMON,
[Species.GLIGAR]: EggTier.COMMON,
[Species.SNUBBULL]: EggTier.COMMON,
[Species.QWILFISH]: EggTier.COMMON,
[Species.SHUCKLE]: EggTier.COMMON,
[Species.HERACROSS]: EggTier.RARE,
[Species.SNEASEL]: EggTier.RARE,
[Species.TEDDIURSA]: EggTier.RARE,
[Species.SLUGMA]: EggTier.COMMON,
[Species.SWINUB]: EggTier.COMMON,
[Species.CORSOLA]: EggTier.COMMON,
[Species.REMORAID]: EggTier.COMMON,
[Species.DELIBIRD]: EggTier.COMMON,
[Species.MANTINE]: EggTier.COMMON,
[Species.SKARMORY]: EggTier.RARE,
[Species.HOUNDOUR]: EggTier.COMMON,
[Species.PHANPY]: EggTier.COMMON,
[Species.STANTLER]: EggTier.COMMON,
[Species.SMEARGLE]: EggTier.COMMON,
[Species.TYROGUE]: EggTier.COMMON,
[Species.SMOOCHUM]: EggTier.COMMON,
[Species.ELEKID]: EggTier.COMMON,
[Species.MAGBY]: EggTier.COMMON,
[Species.MILTANK]: EggTier.RARE,
[Species.RAIKOU]: EggTier.EPIC,
[Species.ENTEI]: EggTier.EPIC,
[Species.SUICUNE]: EggTier.EPIC,
[Species.LARVITAR]: EggTier.RARE,
[Species.LUGIA]: EggTier.LEGENDARY,
[Species.HO_OH]: EggTier.LEGENDARY,
[Species.CELEBI]: EggTier.EPIC,
[Species.TREECKO]: EggTier.COMMON,
[Species.TORCHIC]: EggTier.RARE,
[Species.MUDKIP]: EggTier.COMMON,
[Species.POOCHYENA]: EggTier.COMMON,
[Species.ZIGZAGOON]: EggTier.COMMON,
[Species.WURMPLE]: EggTier.COMMON,
[Species.LOTAD]: EggTier.COMMON,
[Species.SEEDOT]: EggTier.COMMON,
[Species.TAILLOW]: EggTier.COMMON,
[Species.WINGULL]: EggTier.COMMON,
[Species.RALTS]: EggTier.COMMON,
[Species.SURSKIT]: EggTier.COMMON,
[Species.SHROOMISH]: EggTier.COMMON,
[Species.SLAKOTH]: EggTier.RARE,
[Species.NINCADA]: EggTier.RARE,
[Species.WHISMUR]: EggTier.COMMON,
[Species.MAKUHITA]: EggTier.COMMON,
[Species.AZURILL]: EggTier.RARE,
[Species.NOSEPASS]: EggTier.COMMON,
[Species.SKITTY]: EggTier.COMMON,
[Species.SABLEYE]: EggTier.COMMON,
[Species.MAWILE]: EggTier.COMMON,
[Species.ARON]: EggTier.COMMON,
[Species.MEDITITE]: EggTier.COMMON,
[Species.ELECTRIKE]: EggTier.COMMON,
[Species.PLUSLE]: EggTier.COMMON,
[Species.MINUN]: EggTier.COMMON,
[Species.VOLBEAT]: EggTier.COMMON,
[Species.ILLUMISE]: EggTier.COMMON,
[Species.ROSELIA]: EggTier.COMMON,
[Species.GULPIN]: EggTier.COMMON,
[Species.CARVANHA]: EggTier.COMMON,
[Species.WAILMER]: EggTier.COMMON,
[Species.NUMEL]: EggTier.COMMON,
[Species.TORKOAL]: EggTier.COMMON,
[Species.SPOINK]: EggTier.COMMON,
[Species.SPINDA]: EggTier.COMMON,
[Species.TRAPINCH]: EggTier.COMMON,
[Species.CACNEA]: EggTier.COMMON,
[Species.SWABLU]: EggTier.COMMON,
[Species.ZANGOOSE]: EggTier.RARE,
[Species.SEVIPER]: EggTier.COMMON,
[Species.LUNATONE]: EggTier.COMMON,
[Species.SOLROCK]: EggTier.COMMON,
[Species.BARBOACH]: EggTier.COMMON,
[Species.CORPHISH]: EggTier.COMMON,
[Species.BALTOY]: EggTier.COMMON,
[Species.LILEEP]: EggTier.COMMON,
[Species.ANORITH]: EggTier.COMMON,
[Species.FEEBAS]: EggTier.RARE,
[Species.CASTFORM]: EggTier.COMMON,
[Species.KECLEON]: EggTier.COMMON,
[Species.SHUPPET]: EggTier.COMMON,
[Species.DUSKULL]: EggTier.COMMON,
[Species.TROPIUS]: EggTier.COMMON,
[Species.CHIMECHO]: EggTier.COMMON,
[Species.ABSOL]: EggTier.RARE,
[Species.WYNAUT]: EggTier.COMMON,
[Species.SNORUNT]: EggTier.COMMON,
[Species.SPHEAL]: EggTier.COMMON,
[Species.CLAMPERL]: EggTier.COMMON,
[Species.RELICANTH]: EggTier.COMMON,
[Species.LUVDISC]: EggTier.COMMON,
[Species.BAGON]: EggTier.RARE,
[Species.BELDUM]: EggTier.RARE,
[Species.REGIROCK]: EggTier.EPIC,
[Species.REGICE]: EggTier.EPIC,
[Species.REGISTEEL]: EggTier.EPIC,
[Species.LATIAS]: EggTier.EPIC,
[Species.LATIOS]: EggTier.EPIC,
[Species.KYOGRE]: EggTier.LEGENDARY,
[Species.GROUDON]: EggTier.LEGENDARY,
[Species.RAYQUAZA]: EggTier.LEGENDARY,
[Species.JIRACHI]: EggTier.EPIC,
[Species.DEOXYS]: EggTier.EPIC,
[Species.TURTWIG]: EggTier.COMMON,
[Species.CHIMCHAR]: EggTier.COMMON,
[Species.PIPLUP]: EggTier.COMMON,
[Species.STARLY]: EggTier.COMMON,
[Species.BIDOOF]: EggTier.COMMON,
[Species.KRICKETOT]: EggTier.COMMON,
[Species.SHINX]: EggTier.COMMON,
[Species.BUDEW]: EggTier.COMMON,
[Species.CRANIDOS]: EggTier.COMMON,
[Species.SHIELDON]: EggTier.COMMON,
[Species.BURMY]: EggTier.COMMON,
[Species.COMBEE]: EggTier.COMMON,
[Species.PACHIRISU]: EggTier.COMMON,
[Species.BUIZEL]: EggTier.COMMON,
[Species.CHERUBI]: EggTier.COMMON,
[Species.SHELLOS]: EggTier.COMMON,
[Species.DRIFLOON]: EggTier.COMMON,
[Species.BUNEARY]: EggTier.COMMON,
[Species.GLAMEOW]: EggTier.COMMON,
[Species.CHINGLING]: EggTier.COMMON,
[Species.STUNKY]: EggTier.COMMON,
[Species.BRONZOR]: EggTier.COMMON,
[Species.BONSLY]: EggTier.COMMON,
[Species.MIME_JR]: EggTier.COMMON,
[Species.HAPPINY]: EggTier.COMMON,
[Species.CHATOT]: EggTier.COMMON,
[Species.SPIRITOMB]: EggTier.RARE,
[Species.GIBLE]: EggTier.RARE,
[Species.MUNCHLAX]: EggTier.RARE,
[Species.RIOLU]: EggTier.COMMON,
[Species.HIPPOPOTAS]: EggTier.COMMON,
[Species.SKORUPI]: EggTier.COMMON,
[Species.CROAGUNK]: EggTier.COMMON,
[Species.CARNIVINE]: EggTier.COMMON,
[Species.FINNEON]: EggTier.COMMON,
[Species.MANTYKE]: EggTier.COMMON,
[Species.SNOVER]: EggTier.COMMON,
[Species.ROTOM]: EggTier.RARE,
[Species.UXIE]: EggTier.EPIC,
[Species.MESPRIT]: EggTier.EPIC,
[Species.AZELF]: EggTier.EPIC,
[Species.DIALGA]: EggTier.LEGENDARY,
[Species.PALKIA]: EggTier.LEGENDARY,
[Species.HEATRAN]: EggTier.EPIC,
[Species.REGIGIGAS]: EggTier.EPIC,
[Species.GIRATINA]: EggTier.LEGENDARY,
[Species.CRESSELIA]: EggTier.EPIC,
[Species.PHIONE]: EggTier.RARE,
[Species.MANAPHY]: EggTier.EPIC,
[Species.DARKRAI]: EggTier.EPIC,
[Species.SHAYMIN]: EggTier.EPIC,
[Species.ARCEUS]: EggTier.LEGENDARY,
[Species.VICTINI]: EggTier.EPIC,
[Species.SNIVY]: EggTier.COMMON,
[Species.TEPIG]: EggTier.COMMON,
[Species.OSHAWOTT]: EggTier.COMMON,
[Species.PATRAT]: EggTier.COMMON,
[Species.LILLIPUP]: EggTier.COMMON,
[Species.PURRLOIN]: EggTier.COMMON,
[Species.PANSAGE]: EggTier.COMMON,
[Species.PANSEAR]: EggTier.COMMON,
[Species.PANPOUR]: EggTier.COMMON,
[Species.MUNNA]: EggTier.COMMON,
[Species.PIDOVE]: EggTier.COMMON,
[Species.BLITZLE]: EggTier.COMMON,
[Species.ROGGENROLA]: EggTier.COMMON,
[Species.WOOBAT]: EggTier.COMMON,
[Species.DRILBUR]: EggTier.RARE,
[Species.AUDINO]: EggTier.COMMON,
[Species.TIMBURR]: EggTier.RARE,
[Species.TYMPOLE]: EggTier.COMMON,
[Species.THROH]: EggTier.RARE,
[Species.SAWK]: EggTier.RARE,
[Species.SEWADDLE]: EggTier.COMMON,
[Species.VENIPEDE]: EggTier.COMMON,
[Species.COTTONEE]: EggTier.COMMON,
[Species.PETILIL]: EggTier.COMMON,
[Species.BASCULIN]: EggTier.RARE,
[Species.SANDILE]: EggTier.RARE,
[Species.DARUMAKA]: EggTier.RARE,
[Species.MARACTUS]: EggTier.COMMON,
[Species.DWEBBLE]: EggTier.COMMON,
[Species.SCRAGGY]: EggTier.COMMON,
[Species.SIGILYPH]: EggTier.RARE,
[Species.YAMASK]: EggTier.COMMON,
[Species.TIRTOUGA]: EggTier.COMMON,
[Species.ARCHEN]: EggTier.COMMON,
[Species.TRUBBISH]: EggTier.COMMON,
[Species.ZORUA]: EggTier.COMMON,
[Species.MINCCINO]: EggTier.COMMON,
[Species.GOTHITA]: EggTier.COMMON,
[Species.SOLOSIS]: EggTier.COMMON,
[Species.DUCKLETT]: EggTier.COMMON,
[Species.VANILLITE]: EggTier.COMMON,
[Species.DEERLING]: EggTier.COMMON,
[Species.EMOLGA]: EggTier.COMMON,
[Species.KARRABLAST]: EggTier.COMMON,
[Species.FOONGUS]: EggTier.COMMON,
[Species.FRILLISH]: EggTier.COMMON,
[Species.ALOMOMOLA]: EggTier.RARE,
[Species.JOLTIK]: EggTier.COMMON,
[Species.FERROSEED]: EggTier.COMMON,
[Species.KLINK]: EggTier.COMMON,
[Species.TYNAMO]: EggTier.COMMON,
[Species.ELGYEM]: EggTier.COMMON,
[Species.LITWICK]: EggTier.COMMON,
[Species.AXEW]: EggTier.RARE,
[Species.CUBCHOO]: EggTier.COMMON,
[Species.CRYOGONAL]: EggTier.RARE,
[Species.SHELMET]: EggTier.COMMON,
[Species.STUNFISK]: EggTier.COMMON,
[Species.MIENFOO]: EggTier.COMMON,
[Species.DRUDDIGON]: EggTier.RARE,
[Species.GOLETT]: EggTier.COMMON,
[Species.PAWNIARD]: EggTier.RARE,
[Species.BOUFFALANT]: EggTier.RARE,
[Species.RUFFLET]: EggTier.COMMON,
[Species.VULLABY]: EggTier.COMMON,
[Species.HEATMOR]: EggTier.COMMON,
[Species.DURANT]: EggTier.RARE,
[Species.DEINO]: EggTier.RARE,
[Species.LARVESTA]: EggTier.RARE,
[Species.COBALION]: EggTier.EPIC,
[Species.TERRAKION]: EggTier.EPIC,
[Species.VIRIZION]: EggTier.EPIC,
[Species.TORNADUS]: EggTier.EPIC,
[Species.THUNDURUS]: EggTier.EPIC,
[Species.RESHIRAM]: EggTier.LEGENDARY,
[Species.ZEKROM]: EggTier.LEGENDARY,
[Species.LANDORUS]: EggTier.EPIC,
[Species.KYUREM]: EggTier.LEGENDARY,
[Species.KELDEO]: EggTier.EPIC,
[Species.MELOETTA]: EggTier.EPIC,
[Species.GENESECT]: EggTier.EPIC,
[Species.CHESPIN]: EggTier.COMMON,
[Species.FENNEKIN]: EggTier.COMMON,
[Species.FROAKIE]: EggTier.RARE,
[Species.BUNNELBY]: EggTier.COMMON,
[Species.FLETCHLING]: EggTier.COMMON,
[Species.SCATTERBUG]: EggTier.COMMON,
[Species.LITLEO]: EggTier.COMMON,
[Species.FLABEBE]: EggTier.COMMON,
[Species.SKIDDO]: EggTier.COMMON,
[Species.PANCHAM]: EggTier.COMMON,
[Species.FURFROU]: EggTier.COMMON,
[Species.ESPURR]: EggTier.COMMON,
[Species.HONEDGE]: EggTier.RARE,
[Species.SPRITZEE]: EggTier.COMMON,
[Species.SWIRLIX]: EggTier.COMMON,
[Species.INKAY]: EggTier.COMMON,
[Species.BINACLE]: EggTier.COMMON,
[Species.SKRELP]: EggTier.COMMON,
[Species.CLAUNCHER]: EggTier.COMMON,
[Species.HELIOPTILE]: EggTier.COMMON,
[Species.TYRUNT]: EggTier.COMMON,
[Species.AMAURA]: EggTier.COMMON,
[Species.HAWLUCHA]: EggTier.RARE,
[Species.DEDENNE]: EggTier.COMMON,
[Species.CARBINK]: EggTier.COMMON,
[Species.GOOMY]: EggTier.RARE,
[Species.KLEFKI]: EggTier.COMMON,
[Species.PHANTUMP]: EggTier.COMMON,
[Species.PUMPKABOO]: EggTier.COMMON,
[Species.BERGMITE]: EggTier.COMMON,
[Species.NOIBAT]: EggTier.COMMON,
[Species.XERNEAS]: EggTier.LEGENDARY,
[Species.YVELTAL]: EggTier.LEGENDARY,
[Species.ZYGARDE]: EggTier.LEGENDARY,
[Species.DIANCIE]: EggTier.EPIC,
[Species.HOOPA]: EggTier.EPIC,
[Species.VOLCANION]: EggTier.EPIC,
[Species.ETERNAL_FLOETTE]: EggTier.RARE,
[Species.ROWLET]: EggTier.COMMON,
[Species.LITTEN]: EggTier.COMMON,
[Species.POPPLIO]: EggTier.RARE,
[Species.PIKIPEK]: EggTier.COMMON,
[Species.YUNGOOS]: EggTier.COMMON,
[Species.GRUBBIN]: EggTier.COMMON,
[Species.CRABRAWLER]: EggTier.COMMON,
[Species.ORICORIO]: EggTier.COMMON,
[Species.CUTIEFLY]: EggTier.COMMON,
[Species.ROCKRUFF]: EggTier.COMMON,
[Species.WISHIWASHI]: EggTier.COMMON,
[Species.MAREANIE]: EggTier.COMMON,
[Species.MUDBRAY]: EggTier.COMMON,
[Species.DEWPIDER]: EggTier.COMMON,
[Species.FOMANTIS]: EggTier.COMMON,
[Species.MORELULL]: EggTier.COMMON,
[Species.SALANDIT]: EggTier.COMMON,
[Species.STUFFUL]: EggTier.COMMON,
[Species.BOUNSWEET]: EggTier.COMMON,
[Species.COMFEY]: EggTier.RARE,
[Species.ORANGURU]: EggTier.RARE,
[Species.PASSIMIAN]: EggTier.RARE,
[Species.WIMPOD]: EggTier.COMMON,
[Species.SANDYGAST]: EggTier.COMMON,
[Species.PYUKUMUKU]: EggTier.COMMON,
[Species.TYPE_NULL]: EggTier.RARE,
[Species.MINIOR]: EggTier.RARE,
[Species.KOMALA]: EggTier.COMMON,
[Species.TURTONATOR]: EggTier.RARE,
[Species.TOGEDEMARU]: EggTier.COMMON,
[Species.MIMIKYU]: EggTier.RARE,
[Species.BRUXISH]: EggTier.RARE,
[Species.DRAMPA]: EggTier.RARE,
[Species.DHELMISE]: EggTier.RARE,
[Species.JANGMO_O]: EggTier.RARE,
[Species.TAPU_KOKO]: EggTier.EPIC,
[Species.TAPU_LELE]: EggTier.EPIC,
[Species.TAPU_BULU]: EggTier.EPIC,
[Species.TAPU_FINI]: EggTier.EPIC,
[Species.COSMOG]: EggTier.EPIC,
[Species.NIHILEGO]: EggTier.EPIC,
[Species.BUZZWOLE]: EggTier.EPIC,
[Species.PHEROMOSA]: EggTier.EPIC,
[Species.XURKITREE]: EggTier.EPIC,
[Species.CELESTEELA]: EggTier.EPIC,
[Species.KARTANA]: EggTier.EPIC,
[Species.GUZZLORD]: EggTier.EPIC,
[Species.NECROZMA]: EggTier.LEGENDARY,
[Species.MAGEARNA]: EggTier.EPIC,
[Species.MARSHADOW]: EggTier.EPIC,
[Species.POIPOLE]: EggTier.EPIC,
[Species.STAKATAKA]: EggTier.EPIC,
[Species.BLACEPHALON]: EggTier.EPIC,
[Species.ZERAORA]: EggTier.EPIC,
[Species.MELTAN]: EggTier.EPIC,
[Species.ALOLA_RATTATA]: EggTier.COMMON,
[Species.ALOLA_SANDSHREW]: EggTier.COMMON,
[Species.ALOLA_VULPIX]: EggTier.COMMON,
[Species.ALOLA_DIGLETT]: EggTier.COMMON,
[Species.ALOLA_MEOWTH]: EggTier.COMMON,
[Species.ALOLA_GEODUDE]: EggTier.COMMON,
[Species.ALOLA_GRIMER]: EggTier.COMMON,
[Species.GROOKEY]: EggTier.COMMON,
[Species.SCORBUNNY]: EggTier.RARE,
[Species.SOBBLE]: EggTier.COMMON,
[Species.SKWOVET]: EggTier.COMMON,
[Species.ROOKIDEE]: EggTier.COMMON,
[Species.BLIPBUG]: EggTier.COMMON,
[Species.NICKIT]: EggTier.COMMON,
[Species.GOSSIFLEUR]: EggTier.COMMON,
[Species.WOOLOO]: EggTier.COMMON,
[Species.CHEWTLE]: EggTier.COMMON,
[Species.YAMPER]: EggTier.COMMON,
[Species.ROLYCOLY]: EggTier.COMMON,
[Species.APPLIN]: EggTier.COMMON,
[Species.SILICOBRA]: EggTier.COMMON,
[Species.CRAMORANT]: EggTier.COMMON,
[Species.ARROKUDA]: EggTier.COMMON,
[Species.TOXEL]: EggTier.COMMON,
[Species.SIZZLIPEDE]: EggTier.COMMON,
[Species.CLOBBOPUS]: EggTier.COMMON,
[Species.SINISTEA]: EggTier.COMMON,
[Species.HATENNA]: EggTier.COMMON,
[Species.IMPIDIMP]: EggTier.COMMON,
[Species.MILCERY]: EggTier.COMMON,
[Species.FALINKS]: EggTier.RARE,
[Species.PINCURCHIN]: EggTier.COMMON,
[Species.SNOM]: EggTier.COMMON,
[Species.STONJOURNER]: EggTier.COMMON,
[Species.EISCUE]: EggTier.COMMON,
[Species.INDEEDEE]: EggTier.RARE,
[Species.MORPEKO]: EggTier.COMMON,
[Species.CUFANT]: EggTier.COMMON,
[Species.DRACOZOLT]: EggTier.RARE,
[Species.ARCTOZOLT]: EggTier.RARE,
[Species.DRACOVISH]: EggTier.RARE,
[Species.ARCTOVISH]: EggTier.RARE,
[Species.DURALUDON]: EggTier.RARE,
[Species.DREEPY]: EggTier.RARE,
[Species.ZACIAN]: EggTier.LEGENDARY,
[Species.ZAMAZENTA]: EggTier.LEGENDARY,
[Species.ETERNATUS]: EggTier.COMMON,
[Species.KUBFU]: EggTier.EPIC,
[Species.ZARUDE]: EggTier.EPIC,
[Species.REGIELEKI]: EggTier.EPIC,
[Species.REGIDRAGO]: EggTier.EPIC,
[Species.GLASTRIER]: EggTier.EPIC,
[Species.SPECTRIER]: EggTier.EPIC,
[Species.CALYREX]: EggTier.LEGENDARY,
[Species.GALAR_MEOWTH]: EggTier.COMMON,
[Species.GALAR_PONYTA]: EggTier.COMMON,
[Species.GALAR_SLOWPOKE]: EggTier.COMMON,
[Species.GALAR_FARFETCHD]: EggTier.COMMON,
[Species.GALAR_CORSOLA]: EggTier.COMMON,
[Species.GALAR_ZIGZAGOON]: EggTier.COMMON,
[Species.GALAR_DARUMAKA]: EggTier.RARE,
[Species.GALAR_YAMASK]: EggTier.COMMON,
[Species.GALAR_STUNFISK]: EggTier.COMMON,
[Species.GALAR_MR_MIME]: EggTier.COMMON,
[Species.GALAR_ARTICUNO]: EggTier.EPIC,
[Species.GALAR_ZAPDOS]: EggTier.EPIC,
[Species.GALAR_MOLTRES]: EggTier.EPIC,
[Species.HISUI_GROWLITHE]: EggTier.RARE,
[Species.HISUI_VOLTORB]: EggTier.COMMON,
[Species.HISUI_QWILFISH]: EggTier.RARE,
[Species.HISUI_SNEASEL]: EggTier.RARE,
[Species.HISUI_ZORUA]: EggTier.COMMON,
[Species.ENAMORUS]: EggTier.EPIC,
[Species.SPRIGATITO]: EggTier.RARE,
[Species.FUECOCO]: EggTier.RARE,
[Species.QUAXLY]: EggTier.RARE,
[Species.LECHONK]: EggTier.COMMON,
[Species.TAROUNTULA]: EggTier.COMMON,
[Species.NYMBLE]: EggTier.COMMON,
[Species.PAWMI]: EggTier.COMMON,
[Species.TANDEMAUS]: EggTier.RARE,
[Species.FIDOUGH]: EggTier.COMMON,
[Species.SMOLIV]: EggTier.COMMON,
[Species.SQUAWKABILLY]: EggTier.COMMON,
[Species.NACLI]: EggTier.RARE,
[Species.CHARCADET]: EggTier.RARE,
[Species.TADBULB]: EggTier.COMMON,
[Species.WATTREL]: EggTier.COMMON,
[Species.MASCHIFF]: EggTier.COMMON,
[Species.SHROODLE]: EggTier.COMMON,
[Species.BRAMBLIN]: EggTier.COMMON,
[Species.TOEDSCOOL]: EggTier.COMMON,
[Species.KLAWF]: EggTier.COMMON,
[Species.CAPSAKID]: EggTier.COMMON,
[Species.RELLOR]: EggTier.COMMON,
[Species.FLITTLE]: EggTier.COMMON,
[Species.TINKATINK]: EggTier.RARE,
[Species.WIGLETT]: EggTier.COMMON,
[Species.BOMBIRDIER]: EggTier.COMMON,
[Species.FINIZEN]: EggTier.COMMON,
[Species.VAROOM]: EggTier.RARE,
[Species.CYCLIZAR]: EggTier.RARE,
[Species.ORTHWORM]: EggTier.RARE,
[Species.GLIMMET]: EggTier.RARE,
[Species.GREAVARD]: EggTier.COMMON,
[Species.FLAMIGO]: EggTier.RARE,
[Species.CETODDLE]: EggTier.COMMON,
[Species.VELUZA]: EggTier.RARE,
[Species.DONDOZO]: EggTier.RARE,
[Species.TATSUGIRI]: EggTier.RARE,
[Species.GREAT_TUSK]: EggTier.EPIC,
[Species.SCREAM_TAIL]: EggTier.EPIC,
[Species.BRUTE_BONNET]: EggTier.EPIC,
[Species.FLUTTER_MANE]: EggTier.EPIC,
[Species.SLITHER_WING]: EggTier.EPIC,
[Species.SANDY_SHOCKS]: EggTier.EPIC,
[Species.IRON_TREADS]: EggTier.EPIC,
[Species.IRON_BUNDLE]: EggTier.EPIC,
[Species.IRON_HANDS]: EggTier.EPIC,
[Species.IRON_JUGULIS]: EggTier.EPIC,
[Species.IRON_MOTH]: EggTier.EPIC,
[Species.IRON_THORNS]: EggTier.EPIC,
[Species.FRIGIBAX]: EggTier.RARE,
[Species.GIMMIGHOUL]: EggTier.RARE,
[Species.WO_CHIEN]: EggTier.EPIC,
[Species.CHIEN_PAO]: EggTier.EPIC,
[Species.TING_LU]: EggTier.EPIC,
[Species.CHI_YU]: EggTier.EPIC,
[Species.ROARING_MOON]: EggTier.EPIC,
[Species.IRON_VALIANT]: EggTier.EPIC,
[Species.KORAIDON]: EggTier.LEGENDARY,
[Species.MIRAIDON]: EggTier.LEGENDARY,
[Species.WALKING_WAKE]: EggTier.EPIC,
[Species.IRON_LEAVES]: EggTier.EPIC,
[Species.POLTCHAGEIST]: EggTier.RARE,
[Species.OKIDOGI]: EggTier.EPIC,
[Species.MUNKIDORI]: EggTier.EPIC,
[Species.FEZANDIPITI]: EggTier.EPIC,
[Species.OGERPON]: EggTier.EPIC,
[Species.GOUGING_FIRE]: EggTier.EPIC,
[Species.RAGING_BOLT]: EggTier.EPIC,
[Species.IRON_BOULDER]: EggTier.EPIC,
[Species.IRON_CROWN]: EggTier.EPIC,
[Species.TERAPAGOS]: EggTier.LEGENDARY,
[Species.PECHARUNT]: EggTier.EPIC,
[Species.PALDEA_TAUROS]: EggTier.RARE,
[Species.PALDEA_WOOPER]: EggTier.COMMON,
[Species.BLOODMOON_URSALUNA]: EggTier.EPIC,
};

View File

@ -23,6 +23,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import { PokemonAnimType } from "#app/enums/pokemon-anim-type";
import BattleScene from "#app/battle-scene";
export enum BattlerTagLapseType {
FAINT,
@ -90,6 +91,15 @@ export class BattlerTag {
this.sourceMove = source.sourceMove;
this.sourceId = source.sourceId;
}
/**
* Helper function that retrieves the source Pokemon object
* @param scene medium to retrieve the source Pokemon
* @returns The source {@linkcode Pokemon} or `null` if none is found
*/
public getSourcePokemon(scene: BattleScene): Pokemon | null {
return this.sourceId ? scene.getPokemonById(this.sourceId) : null;
}
}
export interface WeatherBattlerTag {
@ -120,7 +130,7 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
const phase = pokemon.scene.getCurrentPhase() as MovePhase;
const move = phase.move;
if (this.isMoveRestricted(move.moveId)) {
if (this.isMoveRestricted(move.moveId, pokemon)) {
if (this.interruptedText(pokemon, move.moveId)) {
pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId));
}
@ -136,10 +146,11 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag {
/**
* Gets whether this tag is restricting a move.
*
* @param {Moves} move {@linkcode Moves} ID to check restriction for.
* @returns {boolean} `true` if the move is restricted by this tag, otherwise `false`.
* @param move - {@linkcode Moves} ID to check restriction for.
* @param user - The {@linkcode Pokemon} involved
* @returns `true` if the move is restricted by this tag, otherwise `false`.
*/
abstract isMoveRestricted(move: Moves): boolean;
public abstract isMoveRestricted(move: Moves, user?: Pokemon): boolean;
/**
* Checks if this tag is restricting a move based on a user's decisions during the target selection phase
@ -327,6 +338,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag {
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false);
}
/**
* Loads the Gorilla Tactics Battler Tag along with its unique class variable moveId
* @override
* @param source Gorilla Tactics' {@linkcode BattlerTag} information
*/
public override loadTag(source: BattlerTag | any): void {
super.loadTag(source);
this.moveId = source.moveId;
}
/**
*
* @override
@ -1376,7 +1397,7 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels));
}
}
@ -1713,7 +1734,12 @@ export class TypeImmuneTag extends BattlerTag {
}
}
export class MagnetRisenTag extends TypeImmuneTag {
/**
* Battler Tag that lifts the affected Pokemon into the air and provides immunity to Ground type moves.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Magnet_Rise_(move) | Moves.MAGNET_RISE}
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS}
*/
export class FloatingTag extends TypeImmuneTag {
constructor(tagType: BattlerTagType, sourceMove: Moves) {
super(tagType, sourceMove, Type.GROUND, 5);
}
@ -1721,14 +1747,18 @@ export class MagnetRisenTag extends TypeImmuneTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
if (this.sourceMove === Moves.MAGNET_RISE) {
pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
onRemove(pokemon: Pokemon): void {
super.onRemove(pokemon);
if (this.sourceMove === Moves.MAGNET_RISE) {
pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
}
export class TypeBoostTag extends BattlerTag {
@ -2139,6 +2169,10 @@ export class GulpMissileTag extends BattlerTag {
return false;
}
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) {
return true;
}
const cancelled = new Utils.BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled);
@ -2497,8 +2531,6 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
* Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied
*/
export class TormentTag extends MoveRestrictionBattlerTag {
private target: Pokemon;
constructor(sourceId: number) {
super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId);
}
@ -2510,7 +2542,6 @@ export class TormentTag extends MoveRestrictionBattlerTag {
*/
override onAdd(pokemon: Pokemon) {
super.onAdd(pokemon);
this.target = pokemon;
pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500);
}
@ -2529,15 +2560,18 @@ export class TormentTag extends MoveRestrictionBattlerTag {
* @param {Moves} move the move under investigation
* @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other
*/
override isMoveRestricted(move: Moves): boolean {
const lastMove = this.target.getLastXMoves(1)[0];
public override isMoveRestricted(move: Moves, user: Pokemon): boolean {
if (!user) {
return false;
}
const lastMove = user.getLastXMoves(1)[0];
if ( !lastMove ) {
return false;
}
// This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY
// Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts
const moveObj = allMoves[lastMove.move];
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || this.target.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr);
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr);
const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS);
if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) {
return true;
@ -2589,37 +2623,39 @@ export class TauntTag extends MoveRestrictionBattlerTag {
* The tag is only removed when the source-user is removed from the field.
*/
export class ImprisonTag extends MoveRestrictionBattlerTag {
private source: Pokemon | null;
constructor(sourceId: number) {
super(BattlerTagType.IMPRISON, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE ], 1, Moves.IMPRISON, sourceId);
}
override onAdd(pokemon: Pokemon) {
if (this.sourceId) {
this.source = pokemon.scene.getPokemonById(this.sourceId);
}
}
/**
* Checks if the source of Imprison is still active
* @param _pokemon
* @param _lapseType
* @override
* @param pokemon The pokemon this tag is attached to
* @returns `true` if the source is still active
*/
override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
return this.source?.isActive(true) ?? false;
public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const source = this.getSourcePokemon(pokemon.scene);
if (source) {
if (lapseType === BattlerTagLapseType.PRE_MOVE) {
return super.lapse(pokemon, lapseType) && source.isActive(true);
} else {
return source.isActive(true);
}
}
return false;
}
/**
* Checks if the source of the tag has the parameter move in its moveset and that the source is still active
* @override
* @param {Moves} move the move under investigation
* @returns `false` if either condition is not met
*/
override isMoveRestricted(move: Moves): boolean {
if (this.source) {
const sourceMoveset = this.source.getMoveset().map(m => m!.moveId);
return sourceMoveset?.includes(move) && this.source.isActive(true);
public override isMoveRestricted(move: Moves, user: Pokemon): boolean {
const source = this.getSourcePokemon(user.scene);
if (source) {
const sourceMoveset = source.getMoveset().map(m => m!.moveId);
return sourceMoveset?.includes(move) && source.isActive(true);
}
return false;
}
@ -2636,16 +2672,16 @@ export class ImprisonTag extends MoveRestrictionBattlerTag {
/**
* Battler Tag that applies the effects of Syrup Bomb to the target Pokemon.
* For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1.
* The tag can also expire by taking the target Pokemon off the field.
* The tag can also expire by taking the target Pokemon off the field, or the Pokemon that originally used the move.
*/
export class SyrupBombTag extends BattlerTag {
constructor() {
super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB);
constructor(sourceId: number) {
super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB, sourceId);
}
/**
* Adds the Syrup Bomb battler tag to the target Pokemon.
* @param {Pokemon} pokemon the target Pokemon
* @param pokemon - The target {@linkcode Pokemon}
*/
override onAdd(pokemon: Pokemon) {
super.onAdd(pokemon);
@ -2654,15 +2690,16 @@ export class SyrupBombTag extends BattlerTag {
/**
* Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count
* @param {Pokemon} pokemon the target Pokemon
* @param {BattlerTagLapseType} _lapseType
* @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field
* @param pokemon - The target {@linkcode Pokemon}
* @param _lapseType - N/A
* @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field
*/
override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
if (!pokemon.isActive(true)) {
if (this.sourceId && !pokemon.scene.getPokemonById(this.sourceId)?.isActive(true)) {
return false;
}
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline
// Custom message in lieu of an animation in mainline
pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
pokemon.scene.unshiftPhase(new StatStageChangePhase(
pokemon.scene, pokemon.getBattlerIndex(), true,
[ Stat.SPD ], -1, true, false, true
@ -2671,14 +2708,64 @@ export class SyrupBombTag extends BattlerTag {
}
}
/**
* Telekinesis raises the target into the air for three turns and causes all moves used against the target (aside from OHKO moves) to hit the target unless the target is in a semi-invulnerable state from Fly/Dig.
* The first effect is provided by {@linkcode FloatingTag}, the accuracy-bypass effect is provided by TelekinesisTag
* The effects of Telekinesis can be baton passed to a teammate. Unlike the mainline games, Telekinesis can be baton-passed to Mega Gengar.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS}
*/
export class TelekinesisTag extends BattlerTag {
constructor(sourceMove: Moves) {
super(BattlerTagType.TELEKINESIS, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE ], 3, sourceMove, undefined, true);
}
override onAdd(pokemon: Pokemon) {
pokemon.scene.queueMessage(i18next.t("battlerTags:telekinesisOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
}
/**
* Tag that swaps the user's base ATK stat with its base DEF stat.
* @extends BattlerTag
*/
export class PowerTrickTag extends BattlerTag {
constructor(sourceMove: Moves, sourceId: number) {
super(BattlerTagType.POWER_TRICK, BattlerTagLapseType.CUSTOM, 0, sourceMove, sourceId, true);
}
onAdd(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
onRemove(pokemon: Pokemon): void {
this.swapStat(pokemon);
pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
}
/**
* Removes the Power Trick tag and reverts any stat changes if the tag is already applied.
* @param {Pokemon} pokemon The {@linkcode Pokemon} that already has the Power Trick tag.
*/
onOverlap(pokemon: Pokemon): void {
pokemon.removeTag(this.tagType);
}
/**
* Swaps the user's base ATK stat with its base DEF stat.
* @param {Pokemon} pokemon The {@linkcode Pokemon} whose stats will be swapped.
*/
swapStat(pokemon: Pokemon): void {
const temp = pokemon.getStat(Stat.ATK, false);
pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.DEF, false), false);
pokemon.setStat(Stat.DEF, temp, false);
}
}
/**
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
*
* @param {BattlerTagType} tagType the type of the {@linkcode BattlerTagType}.
* @param turnCount the turn count.
* @param {Moves} sourceMove the source {@linkcode Moves}.
* @param sourceId the source ID.
* @returns {BattlerTag} the corresponding {@linkcode BattlerTag} object.
* @param sourceId - The ID of the pokemon adding the tag
* @returns The corresponding {@linkcode BattlerTag} object.
*/
export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag {
switch (tagType) {
@ -2801,8 +2888,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
return new CursedTag(sourceId);
case BattlerTagType.CHARGED:
return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true);
case BattlerTagType.MAGNET_RISEN:
return new MagnetRisenTag(tagType, sourceMove);
case BattlerTagType.FLOATING:
return new FloatingTag(tagType, sourceMove);
case BattlerTagType.MINIMIZED:
return new MinimizeTag();
case BattlerTagType.DESTINY_BOND:
@ -2847,7 +2934,11 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.IMPRISON:
return new ImprisonTag(sourceId);
case BattlerTagType.SYRUP_BOMB:
return new SyrupBombTag();
return new SyrupBombTag(sourceId);
case BattlerTagType.TELEKINESIS:
return new TelekinesisTag(sourceMove);
case BattlerTagType.POWER_TRICK:
return new PowerTrickTag(sourceMove, sourceId);
case BattlerTagType.NONE:
default:
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);

View File

@ -11,6 +11,7 @@ import { EggTier } from "#enums/egg-type";
import { Species } from "#enums/species";
import { EggSourceType } from "#enums/egg-source-types";
import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates";
import { speciesEggTiers } from "#app/data/balance/species-egg-tiers";
export const EGG_SEED = 1073741824;
@ -160,7 +161,7 @@ export class Egg {
// Override egg tier and hatchwaves if species was given
if (eggOptions?.species) {
this._tier = this.getEggTierFromSpeciesStarterValue();
this._tier = this.getEggTier();
this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves();
}
// If species has no variant, set variantTier to common. This needs to
@ -261,11 +262,11 @@ export class Egg {
return "Manaphy";
}
switch (this.tier) {
case EggTier.GREAT:
case EggTier.RARE:
return i18next.t("egg:greatTier");
case EggTier.ULTRA:
case EggTier.EPIC:
return i18next.t("egg:ultraTier");
case EggTier.MASTER:
case EggTier.LEGENDARY:
return i18next.t("egg:masterTier");
default:
return i18next.t("egg:defaultTier");
@ -336,9 +337,9 @@ export class Egg {
switch (eggTier ?? this._tier) {
case EggTier.COMMON:
return HATCH_WAVES_COMMON_EGG;
case EggTier.GREAT:
case EggTier.RARE:
return HATCH_WAVES_RARE_EGG;
case EggTier.ULTRA:
case EggTier.EPIC:
return HATCH_WAVES_EPIC_EGG;
}
return HATCH_WAVES_LEGENDARY_EGG;
@ -347,7 +348,7 @@ export class Egg {
private rollEggTier(): EggTier {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
const tierValue = Utils.randInt(256);
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER;
return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY;
}
private rollSpecies(scene: BattleScene): Species | null {
@ -367,7 +368,7 @@ export class Egg {
*/
const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1);
return rand ? Species.PHIONE : Species.MANAPHY;
} else if (this.tier === EggTier.MASTER
} else if (this.tier === EggTier.LEGENDARY
&& this._sourceType === EggSourceType.GACHA_LEGENDARY) {
if (!Utils.randSeedInt(2)) {
return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp);
@ -378,15 +379,15 @@ export class Egg {
let maxStarterValue: integer;
switch (this.tier) {
case EggTier.GREAT:
case EggTier.RARE:
minStarterValue = 4;
maxStarterValue = 5;
break;
case EggTier.ULTRA:
case EggTier.EPIC:
minStarterValue = 6;
maxStarterValue = 7;
break;
case EggTier.MASTER:
case EggTier.LEGENDARY:
minStarterValue = 8;
maxStarterValue = 9;
break;
@ -398,8 +399,8 @@ export class Egg {
const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ];
let speciesPool = Object.keys(speciesStarterCosts)
.filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue)
let speciesPool = Object.keys(speciesEggTiers)
.filter(s => speciesEggTiers[s] === this.tier)
.map(s => parseInt(s) as Species)
.filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1);
@ -430,7 +431,9 @@ export class Egg {
let totalWeight = 0;
const speciesWeights : number[] = [];
for (const speciesId of speciesPool) {
let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
// Accounts for species that have starter costs outside of the normal range for their EggTier
const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue);
let weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100);
const species = getPokemonSpecies(speciesId);
if (species.isRegional()) {
weight = Math.floor(weight / 2);
@ -498,16 +501,16 @@ export class Egg {
private checkForPityTierOverrides(scene: BattleScene): void {
const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0;
scene.gameData.eggPity[EggTier.GREAT] += 1;
scene.gameData.eggPity[EggTier.ULTRA] += 1;
scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset;
scene.gameData.eggPity[EggTier.RARE] += 1;
scene.gameData.eggPity[EggTier.EPIC] += 1;
scene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset;
// These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered.
if (scene.gameData.eggPity[EggTier.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.MASTER;
} else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.ULTRA;
} else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.GREAT;
if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.LEGENDARY;
} else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.EPIC;
} else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) {
this._tier = EggTier.RARE;
}
scene.gameData.eggPity[this._tier] = 0;
}
@ -516,38 +519,24 @@ export class Egg {
scene.gameData.gameStats.eggsPulled++;
if (this.isManaphyEgg()) {
scene.gameData.gameStats.manaphyEggsPulled++;
this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.ULTRA);
this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC);
return;
}
switch (this.tier) {
case EggTier.GREAT:
case EggTier.RARE:
scene.gameData.gameStats.rareEggsPulled++;
break;
case EggTier.ULTRA:
case EggTier.EPIC:
scene.gameData.gameStats.epicEggsPulled++;
break;
case EggTier.MASTER:
case EggTier.LEGENDARY:
scene.gameData.gameStats.legendaryEggsPulled++;
break;
}
}
private getEggTierFromSpeciesStarterValue(): EggTier {
const speciesStartValue = speciesStarterCosts[this.species];
if (speciesStartValue >= 1 && speciesStartValue <= 3) {
return EggTier.COMMON;
}
if (speciesStartValue >= 4 && speciesStartValue <= 5) {
return EggTier.GREAT;
}
if (speciesStartValue >= 6 && speciesStartValue <= 7) {
return EggTier.ULTRA;
}
if (speciesStartValue >= 8) {
return EggTier.MASTER;
}
return EggTier.COMMON;
private getEggTier(): EggTier {
return speciesEggTiers[this.species];
}
////
@ -556,8 +545,8 @@ export class Egg {
}
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
const legendarySpecies = Object.entries(speciesStarterCosts)
.filter(s => s[1] >= 8 && s[1] <= 9)
const legendarySpecies = Object.entries(speciesEggTiers)
.filter(s => s[1] === EggTier.LEGENDARY)
.map(s => parseInt(s[0]))
.filter(s => getPokemonSpecies(s).isObtainable());
@ -579,17 +568,9 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta
/**
* Check for a given species EggTier Value
* @param species - Species for wich we will check the egg tier it belongs to
* @param pokemonSpecies - Species for wich we will check the egg tier it belongs to
* @returns The egg tier of a given pokemon species
*/
export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier {
const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()];
if (speciesBaseValue <= 3) {
return EggTier.COMMON;
} else if (speciesBaseValue <= 5) {
return EggTier.GREAT;
} else if (speciesBaseValue <= 7) {
return EggTier.ULTRA;
}
return EggTier.MASTER;
return speciesEggTiers[pokemonSpecies.getRootSpeciesId()];
}

View File

@ -365,6 +365,14 @@ export default class Move implements Localizable {
return this;
}
/**
* Internal dev flag for documenting edge cases. When using this, please document the known edge case.
* @returns the called object {@linkcode Move}
*/
edgeCase(): this {
return this;
}
/**
* Marks the move as "partial": appends texts to the move name
* @returns the called object {@linkcode Move}
@ -800,7 +808,7 @@ export default class Move implements Localizable {
source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
if (!this.hasAttr(TypelessAttr)) {
source.scene.arena.applyTags(WeakenMoveTypeTag, this.type, power);
source.scene.arena.applyTags(WeakenMoveTypeTag, simulated, this.type, power);
source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power);
}
@ -1018,7 +1026,7 @@ export class MoveEffectAttr extends MoveAttr {
if (!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, moveChance);
user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance);
}
if (!selfEffect) {
@ -1938,12 +1946,21 @@ export class IncrementMovePriorityAttr extends MoveAttr {
* @see {@linkcode apply}
*/
export class MultiHitAttr extends MoveAttr {
/** This move's intrinsic multi-hit type. It should never be modified. */
private readonly intrinsicMultiHitType: MultiHitType;
/** This move's current multi-hit type. It may be temporarily modified by abilities (e.g., Battle Bond). */
private multiHitType: MultiHitType;
constructor(multiHitType?: MultiHitType) {
super();
this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
this.intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
this.multiHitType = this.intrinsicMultiHitType;
}
// Currently used by `battle_bond.test.ts`
getMultiHitType(): MultiHitType {
return this.multiHitType;
}
/**
@ -1957,7 +1974,7 @@ export class MultiHitAttr extends MoveAttr {
* @returns True
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const hitType = new Utils.NumberHolder(this.multiHitType);
const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType);
applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType);
this.multiHitType = hitType.value;
@ -4107,11 +4124,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
* @param user {@linkcode Pokemon} using the move
* @param target {@linkcode Pokemon} target of the move
* @param move {@linkcode Move} with this attribute
* @param args [0] {@linkcode Utils.IntegerHolder} The category of the move
* @param args [0] {@linkcode Utils.NumberHolder} The category of the move
* @returns true if the function succeeds
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as Utils.IntegerHolder);
const category = (args[0] as Utils.NumberHolder);
if (user.getAlly() === target) {
category.value = MoveCategory.STATUS;
@ -4547,19 +4564,20 @@ export class WaterSuperEffectTypeMultiplierAttr extends VariableMoveTypeMultipli
export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr {
/**
* Checks to see if the Target is Ice-Type or not. If so, the move will have no effect.
* @param {Pokemon} user N/A
* @param {Pokemon} target Pokemon that is being checked whether Ice-Type or not.
* @param {Move} move N/A
* @param {any[]} args Sets to false if the target is Ice-Type, so it should do no damage/no effect.
* @returns {boolean} Returns true if move is successful, false if Ice-Type.
* @param user n/a
* @param target The {@linkcode Pokemon} targeted by the move
* @param move n/a
* @param args `[0]` a {@linkcode Utils.NumberHolder | NumberHolder} containing a type effectiveness multiplier
* @returns `true` if this Ice-type immunity applies; `false` otherwise
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const multiplier = args[0] as Utils.NumberHolder;
if (target.isOfType(Type.ICE)) {
(args[0] as Utils.BooleanHolder).value = false;
return false;
}
multiplier.value = 0;
return true;
}
return false;
}
}
export class FlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr {
@ -4835,14 +4853,14 @@ export class GulpMissileTagAttr extends MoveEffectAttr {
/**
* Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio.
* @param {Pokemon} user The Pokemon using the move.
* @param {Pokemon} target The Pokemon being targeted by the move.
* @param {Move} move The move being used.
* @param {any[]} args Additional arguments, if any.
* @param user The Pokemon using the move.
* @param _target N/A
* @param move The move being used.
* @param _args N/A
* @returns Whether the BattlerTag is applied.
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
if (!super.apply(user, target, move, args)) {
apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean {
if (!super.apply(user, _target, move, _args)) {
return false;
}
@ -5466,37 +5484,38 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
*/
const switchOutTarget = this.selfSwitch ? user : target;
if (switchOutTarget instanceof PlayerPokemon) {
// Switch out logic for the player's Pokemon
if (switchOutTarget.scene.getParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
return false;
}
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
if (switchOutTarget.hp > 0) {
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
user.scene.prependToPhase(new SwitchPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase);
return true;
}
return false;
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
// Switch out logic for trainer battles
if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) {
return false;
}
// Switch out logic for trainer battles
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
if (switchOutTarget.hp > 0) {
// for opponent switching out
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(),
(user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
false, false), MoveEndPhase);
}
} else {
// Switch out logic for everything else (eg: WILD battles)
if (user.scene.currentBattle.waveIndex % 10 === 0) {
return false;
}
// Switch out logic for everything else (eg: WILD battles)
switchOutTarget.leaveField(false);
if (switchOutTarget.hp) {
if (switchOutTarget.hp > 0) {
switchOutTarget.leaveField(false);
user.scene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500);
// in double battles redirect potential moves off fled pokemon
@ -6411,6 +6430,9 @@ export class TransformAttr extends MoveEffectAttr {
user.summonData.gender = target.getGender();
user.summonData.fusionGender = target.getFusionGender();
// Power Trick's effect will not preserved after using Transform
user.removeTag(BattlerTagType.POWER_TRICK);
// Copy all stats (except HP)
for (const s of EFFECTIVE_STATS) {
user.setStat(s, target.getStat(s, false), false);
@ -7074,7 +7096,8 @@ export function initMoves() {
.attr(ForceSwitchOutAttr)
.ignoresSubstitute()
.hidesTarget()
.windMove(),
.windMove()
.partial(), // Should force random switches
new AttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1)
.attr(ChargeAttr, ChargeAnim.FLY_CHARGING, i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" }), BattlerTagType.FLYING)
.condition(failOnGravityCondition)
@ -7151,7 +7174,8 @@ export function initMoves() {
new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1)
.attr(ForceSwitchOutAttr)
.soundBased()
.hidesTarget(),
.hidesTarget()
.partial(), // Should force random switching
new StatusMove(Moves.SING, Type.NORMAL, 55, 15, -1, 0, 1)
.attr(StatusEffectAttr, StatusEffect.SLEEP)
.soundBased(),
@ -7292,7 +7316,7 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true),
new AttackMove(Moves.QUICK_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1),
new AttackMove(Moves.RAGE, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1)
.partial(),
.partial(), // No effect implemented
new SelfStatusMove(Moves.TELEPORT, Type.PSYCHIC, -1, 20, -1, -6, 1)
.attr(ForceSwitchOutAttr, true)
.hidesUser(),
@ -7607,7 +7631,7 @@ export function initMoves() {
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
.attr(StatStageChangeAttr, [ Stat.ATK ], -2),
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
.partial()
.partial() // Does not lock the user, also does not increase damage properly
.attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL),
new AttackMove(Moves.FALSE_SWIPE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 2)
.attr(SurviveDamageAttr),
@ -7678,7 +7702,7 @@ export function initMoves() {
.ignoresSubstitute()
.condition((user, target, move) => new EncoreTag(user.id).canAdd(target)),
new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2)
.partial(),
.partial(), // No effect implemented
new AttackMove(Moves.RAPID_SPIN, Type.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, 100, 0, 2)
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true)
.attr(RemoveBattlerTagAttr, [
@ -7743,7 +7767,7 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -1)
.ballBombMove(),
new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2)
.partial()
.partial() // Complete buggy mess
.attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", { pokemonName: "{USER}" })),
new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2)
.attr(StatStageChangeAttr, [ Stat.DEF ], -1),
@ -7761,7 +7785,7 @@ export function initMoves() {
.ignoresVirtual()
.soundBased()
.target(MoveTarget.RANDOM_NEAR_ENEMY)
.partial(),
.partial(), // Does not lock the user, does not stop Pokemon from sleeping
new SelfStatusMove(Moves.STOCKPILE, Type.NORMAL, -1, 20, -1, 0, 3)
.condition(user => (user.getTag(StockpilingTag)?.stockpiledCount ?? 0) < 3)
.attr(AddBattlerTagAttr, BattlerTagType.STOCKPILING, true),
@ -7784,7 +7808,7 @@ export function initMoves() {
.target(MoveTarget.BOTH_SIDES),
new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3)
.ignoresSubstitute()
.partial() // Incomplete implementation because of Uproar's partial implementation
.edgeCase() // Incomplete implementation because of Uproar's partial implementation
.attr(AddBattlerTagAttr, BattlerTagType.TORMENT, false, true, 1),
new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3)
.attr(StatStageChangeAttr, [ Stat.SPATK ], 1)
@ -7833,7 +7857,9 @@ export function initMoves() {
.attr(RandomMovesetMoveAttr, true)
.ignoresVirtual(),
new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true),
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, true)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLOATING ], true),
new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], -1, true),
new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3)
@ -7861,7 +7887,8 @@ export function initMoves() {
.attr(SwitchAbilitiesAttr),
new StatusMove(Moves.IMPRISON, Type.PSYCHIC, 100, 10, -1, 0, 3)
.ignoresSubstitute()
.attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false),
.attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false)
.target(MoveTarget.ENEMY_SIDE),
new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3)
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN)
.condition((user, target, move) => !!user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)),
@ -7871,7 +7898,7 @@ export function initMoves() {
.unimplemented(),
new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3)
.makesContact(false)
.partial(),
.partial(), // No effect implemented
new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3)
.attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" }), BattlerTagType.UNDERWATER, true)
.attr(GulpMissileTagAttr)
@ -7902,7 +7929,7 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.MUD_SPORT, 5)
.target(MoveTarget.BOTH_SIDES),
new AttackMove(Moves.ICE_BALL, Type.ICE, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 3)
.partial()
.partial() // Does not lock the user properly, does not increase damage correctly
.attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL)
.ballBombMove(),
new AttackMove(Moves.NEEDLE_ARM, Type.GRASS, MoveCategory.PHYSICAL, 60, 100, 15, 30, 0, 3)
@ -8045,7 +8072,7 @@ export function initMoves() {
.attr(ConfuseAttr)
.pulseMove(),
new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3)
.partial()
.partial() // Complete buggy mess
.attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", { pokemonName: "{USER}" })),
new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3)
.attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true),
@ -8129,7 +8156,7 @@ export function initMoves() {
.attr(OpponentHighHpPowerAttr, 120)
.makesContact(),
new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4)
.unimplemented(),
.attr(AddBattlerTagAttr, BattlerTagType.POWER_TRICK, true),
new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4)
.attr(SuppressAbilitiesAttr),
new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4)
@ -8167,8 +8194,8 @@ export function initMoves() {
new SelfStatusMove(Moves.AQUA_RING, Type.WATER, -1, 20, -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true),
new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.MAGNET_RISEN, true, true)
.condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.MAGNET_RISEN, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))),
.attr(AddBattlerTagAttr, BattlerTagType.FLOATING, true, true)
.condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.FLOATING, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))),
new AttackMove(Moves.FLARE_BLITZ, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4)
.attr(RecoilAttr, false, 0.33)
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
@ -8393,7 +8420,11 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
new StatusMove(Moves.TELEKINESIS, Type.PSYCHIC, -1, 15, -1, 0, 5)
.condition(failOnGravityCondition)
.unimplemented(),
.condition((_user, target, _move) => ![ Species.DIGLETT, Species.DUGTRIO, Species.ALOLA_DIGLETT, Species.ALOLA_DUGTRIO, Species.SANDYGAST, Species.PALOSSAND, Species.WIGLETT, Species.WUGTRIO ].includes(target.species.speciesId))
.condition((_user, target, _move) => !(target.species.speciesId === Species.GENGAR && target.getFormKey() === "mega"))
.condition((_user, target, _move) => Utils.isNullOrUndefined(target.getTag(BattlerTagType.INGRAIN)) && Utils.isNullOrUndefined(target.getTag(BattlerTagType.IGNORE_FLYING)))
.attr(AddBattlerTagAttr, BattlerTagType.TELEKINESIS, false, true, 3)
.attr(AddBattlerTagAttr, BattlerTagType.FLOATING, false, true, 3),
new StatusMove(Moves.MAGIC_ROOM, Type.PSYCHIC, -1, 10, -1, 0, 5)
.ignoresProtect()
.target(MoveTarget.BOTH_SIDES)
@ -8401,7 +8432,7 @@ export function initMoves() {
new AttackMove(Moves.SMACK_DOWN, Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN ])
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ])
.attr(HitsTagAttr, BattlerTagType.FLYING)
.makesContact(false),
new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
@ -8451,7 +8482,7 @@ export function initMoves() {
.attr(AfterYouAttr),
new AttackMove(Moves.ROUND, Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
.soundBased()
.partial(),
.partial(), // No effect implemented
new AttackMove(Moves.ECHOED_VOICE, Type.NORMAL, MoveCategory.SPECIAL, 40, 100, 15, -1, 0, 5)
.attr(ConsecutiveUseMultiBasePowerAttr, 5, false)
.soundBased(),
@ -8493,7 +8524,8 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.ATK ], 1, true)
.attr(StatStageChangeAttr, [ Stat.SPD ], 2, true),
new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
.attr(ForceSwitchOutAttr),
.attr(ForceSwitchOutAttr)
.partial(), // Should force random switches
new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.attr(RemoveHeldItemAttr, true),
@ -8561,7 +8593,8 @@ export function initMoves() {
.attr(CritOnlyAttr),
new AttackMove(Moves.DRAGON_TAIL, Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
.attr(ForceSwitchOutAttr)
.hidesTarget(),
.hidesTarget()
.partial(), // Should force random switches
new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true),
new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5)
@ -8689,7 +8722,7 @@ export function initMoves() {
.ignoresVirtual(),
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
.attr(AddTypeAttr, Type.GHOST)
.partial(),
.edgeCase(), // Weird interaction with Forest's Curse, reflect type, burn up
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1)
.soundBased(),
@ -8702,7 +8735,7 @@ export function initMoves() {
.triageMove(),
new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6)
.attr(AddTypeAttr, Type.GRASS)
.partial(),
.edgeCase(), // Weird interaction with Trick or Treat, reflect type, burn up
new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6)
.windMove()
.makesContact(false)
@ -8710,7 +8743,7 @@ export function initMoves() {
new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6)
.attr(StatusEffectAttr, StatusEffect.FREEZE)
.attr(WaterSuperEffectTypeMultiplierAttr)
.partial(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell.
.edgeCase(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell.
new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES),
@ -8834,9 +8867,9 @@ export function initMoves() {
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
.attr(HitsTagAttr, BattlerTagType.FLYING)
.attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN)
.attr(HitsTagAttr, BattlerTagType.FLOATING)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN ])
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ])
.makesContact(false)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.THOUSAND_WAVES, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
@ -9088,15 +9121,15 @@ export function initMoves() {
/* Unused */
new AttackMove(Moves.SINISTER_ARROW_RAID, Type.GHOST, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
.makesContact(false)
.partial()
.edgeCase() // I assume it's because the user needs spirit shackle and decidueye
.ignoresVirtual(),
new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
.partial()
.edgeCase() // I assume it's because it needs darkest lariat and incineroar
.ignoresVirtual(),
new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
.partial()
.edgeCase() // I assume it's because it needs sparkling aria and primarina
.ignoresVirtual(),
new AttackMove(Moves.GUARDIAN_OF_ALOLA, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
.unimplemented()
@ -9105,10 +9138,10 @@ export function initMoves() {
.unimplemented()
.ignoresVirtual(),
new AttackMove(Moves.STOKED_SPARKSURFER, Type.ELECTRIC, MoveCategory.SPECIAL, 175, -1, 1, 100, 0, 7)
.partial()
.edgeCase() // I assume it's because it needs thunderbolt and Alola Raichu
.ignoresVirtual(),
new AttackMove(Moves.PULVERIZING_PANCAKE, Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7)
.partial()
.edgeCase() // I assume it's because it needs giga impact and snorlax
.ignoresVirtual(),
new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true)
@ -9139,13 +9172,13 @@ export function initMoves() {
.attr(RechargeAttr),
new AttackMove(Moves.SPECTRAL_THIEF, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7)
.ignoresSubstitute()
.partial(),
.partial(), // Does not steal stats
new AttackMove(Moves.SUNSTEEL_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7)
.ignoresAbilities()
.partial(),
.edgeCase(), // Should not ignore abilities when called virtually (metronome)
new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
.ignoresAbilities()
.partial(),
.edgeCase(), // Should not ignore abilities when called virtually (metronome)
new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1),
new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7)
@ -9156,7 +9189,7 @@ export function initMoves() {
.attr(FormChangeItemTypeAttr),
/* Unused */
new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
.partial()
.edgeCase() // I assume it's because it needs thunderbolt and pikachu in a cap
.ignoresVirtual(),
/* End Unused */
new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7)
@ -9169,7 +9202,7 @@ export function initMoves() {
new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
.attr(PhotonGeyserCategoryAttr)
.ignoresAbilities()
.partial(),
.edgeCase(), // Should not ignore abilities when called virtually (metronome)
/* Unused */
new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7)
.attr(PhotonGeyserCategoryAttr)
@ -9182,7 +9215,7 @@ export function initMoves() {
.ignoresAbilities()
.ignoresVirtual(),
new AttackMove(Moves.LETS_SNUGGLE_FOREVER, Type.FAIRY, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7)
.partial()
.edgeCase() // I assume it needs play rough and mimikyu
.ignoresVirtual(),
new AttackMove(Moves.SPLINTERED_STORMSHARDS, Type.ROCK, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7)
.attr(ClearTerrainAttr)
@ -9192,7 +9225,7 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, undefined, undefined, undefined, undefined, true)
.soundBased()
.target(MoveTarget.ALL_NEAR_ENEMIES)
.partial()
.edgeCase() // I assume it needs clanging scales and Kommo-O
.ignoresVirtual(),
/* End Unused */
new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) //LGPE Implementation
@ -9255,14 +9288,14 @@ export function initMoves() {
new AttackMove(Moves.JAW_LOCK, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8)
.attr(JawLockAttr)
.bitingMove(),
new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8)
.attr(EatBerryAttr)
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
.condition((user) => {
const userBerries = user.scene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
return userBerries.length > 0;
})
.partial(),
.edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true)
.attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, false)
@ -9276,7 +9309,7 @@ export function initMoves() {
new AttackMove(Moves.DRAGON_DARTS, Type.DRAGON, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 8)
.attr(MultiHitAttr, MultiHitType._2)
.makesContact(false)
.partial(),
.partial(), // smart targetting is unimplemented
new StatusMove(Moves.TEATIME, Type.NORMAL, -1, 10, -1, 0, 8)
.attr(EatBerryAttr)
.target(MoveTarget.ALL),
@ -9715,7 +9748,7 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -2),
new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
.makesContact(false)
.partial(),
.partial(), // No effect implemented (requires Commander)
new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
.punchingMove(),
new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, -1, 0, 9)
@ -9927,8 +9960,7 @@ export function initMoves() {
new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
.attr(FlinchAttr)
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].priority > 0 ) // TODO: is this bang correct?
//TODO: Should also apply when target move priority increased by ability ex. gale wings
.partial(),
.partial(), // Should also apply when target move priority increased by ability ex. gale wings
new AttackMove(Moves.MALIGNANT_CHAIN, Type.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9)
.attr(StatusEffectAttr, StatusEffect.TOXIC)
);

View File

@ -150,7 +150,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.ULTRA
tier: EggTier.EPIC
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`));
setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]);
@ -172,7 +172,7 @@ export const ATrainersTestEncounter: MysteryEncounter =
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: encounter.misc.trainerEggDescription,
tier: EggTier.GREAT
tier: EggTier.RARE
};
encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`));
setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]);

View File

@ -494,7 +494,7 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number)
pulled: false,
sourceType: EggSourceType.EVENT,
eggDescriptor: eggDescription,
tier: EggTier.GREAT
tier: EggTier.RARE
});
}
}

View File

@ -976,7 +976,7 @@ export function initSpecies() {
new PokemonSpecies(Species.VENUSAUR, 1, false, false, false, "Seed Pokémon", Type.GRASS, Type.POISON, 2, 100, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, GrowthRate.MEDIUM_SLOW, 87.5, true, true,
new PokemonForm("Normal", "", Type.GRASS, Type.POISON, 2, 100, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, true, null, true),
new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.GRASS, Type.POISON, 2.4, 155.5, Abilities.THICK_FAT, Abilities.THICK_FAT, Abilities.THICK_FAT, 625, 80, 100, 123, 122, 120, 80, 45, 50, 263, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, Type.POISON, 24, 999.9, Abilities.EFFECT_SPORE, Abilities.EFFECT_SPORE, Abilities.EFFECT_SPORE, 625, 120, 82, 98, 130, 115, 80, 45, 50, 263, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, Type.POISON, 24, 999.9, Abilities.EFFECT_SPORE, Abilities.NONE, Abilities.EFFECT_SPORE, 625, 120, 82, 98, 130, 115, 80, 45, 50, 263, true),
),
new PokemonSpecies(Species.CHARMANDER, 1, false, false, false, "Lizard Pokémon", Type.FIRE, null, 0.6, 8.5, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 309, 39, 52, 43, 60, 50, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.CHARMELEON, 1, false, false, false, "Flame Pokémon", Type.FIRE, null, 1.1, 19, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 405, 58, 64, 58, 80, 65, 80, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false),
@ -984,20 +984,20 @@ export function initSpecies() {
new PokemonForm("Normal", "", Type.FIRE, Type.FLYING, 1.7, 90.5, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 534, 78, 84, 78, 109, 85, 100, 45, 50, 267, false, null, true),
new PokemonForm("Mega X", SpeciesFormKey.MEGA_X, Type.FIRE, Type.DRAGON, 1.7, 110.5, Abilities.TOUGH_CLAWS, Abilities.NONE, Abilities.TOUGH_CLAWS, 634, 78, 130, 111, 130, 85, 100, 45, 50, 267),
new PokemonForm("Mega Y", SpeciesFormKey.MEGA_Y, Type.FIRE, Type.FLYING, 1.7, 100.5, Abilities.DROUGHT, Abilities.NONE, Abilities.DROUGHT, 634, 78, 104, 78, 159, 115, 100, 45, 50, 267),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, Type.FLYING, 28, 999.9, Abilities.BERSERK, Abilities.BERSERK, Abilities.BERSERK, 634, 118, 84, 93, 139, 110, 100, 45, 50, 267),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, Type.FLYING, 28, 999.9, Abilities.BERSERK, Abilities.NONE, Abilities.BERSERK, 634, 118, 84, 93, 139, 100, 100, 45, 50, 267),
),
new PokemonSpecies(Species.SQUIRTLE, 1, false, false, false, "Tiny Turtle Pokémon", Type.WATER, null, 0.5, 9, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 314, 44, 48, 65, 50, 64, 43, 45, 50, 63, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.WARTORTLE, 1, false, false, false, "Turtle Pokémon", Type.WATER, null, 1, 22.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 405, 59, 63, 80, 65, 80, 58, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.BLASTOISE, 1, false, false, false, "Shellfish Pokémon", Type.WATER, null, 1.6, 85.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true,
new PokemonForm("Normal", "", Type.WATER, null, 1.6, 85.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, false, null, true),
new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.WATER, null, 1.6, 101.1, Abilities.MEGA_LAUNCHER, Abilities.NONE, Abilities.MEGA_LAUNCHER, 630, 79, 103, 120, 135, 115, 78, 45, 50, 265),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, Type.STEEL, 25, 999.9, Abilities.SHELL_ARMOR, Abilities.SHELL_ARMOR, Abilities.SHELL_ARMOR, 630, 119, 83, 135, 115, 110, 68, 45, 50, 265),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, Type.STEEL, 25, 999.9, Abilities.SHELL_ARMOR, Abilities.NONE, Abilities.SHELL_ARMOR, 630, 119, 83, 135, 115, 110, 68, 45, 50, 265),
),
new PokemonSpecies(Species.CATERPIE, 1, false, false, false, "Worm Pokémon", Type.BUG, null, 0.3, 2.9, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 45, 30, 35, 20, 20, 45, 255, 50, 39, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.METAPOD, 1, false, false, false, "Cocoon Pokémon", Type.BUG, null, 0.7, 9.9, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 50, 20, 55, 25, 25, 30, 120, 50, 72, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.BUTTERFREE, 1, false, false, false, "Butterfly Pokémon", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, GrowthRate.MEDIUM_FAST, 50, true, true,
new PokemonForm("Normal", "", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, true, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 999.9, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 999.9, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.COMPOUND_EYES, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true),
),
new PokemonSpecies(Species.WEEDLE, 1, false, false, false, "Hairy Bug Pokémon", Type.BUG, Type.POISON, 0.3, 3.2, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 40, 35, 30, 20, 20, 50, 255, 70, 39, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.KAKUNA, 1, false, false, false, "Cocoon Pokémon", Type.BUG, Type.POISON, 0.6, 10, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 45, 25, 50, 25, 25, 35, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false),
@ -1026,7 +1026,7 @@ export function initSpecies() {
new PokemonForm("Cute Cosplay", "cute-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom
new PokemonForm("Smart Cosplay", "smart-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom
new PokemonForm("Tough Cosplay", "tough-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.ELECTRIC, null, 21, 999.9, Abilities.LIGHTNING_ROD, Abilities.LIGHTNING_ROD, Abilities.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112), //+100 BST from Partner Form
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.ELECTRIC, null, 21, 999.9, Abilities.LIGHTNING_ROD, Abilities.NONE, Abilities.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112), //+100 BST from Partner Form
),
new PokemonSpecies(Species.RAICHU, 1, false, false, false, "Mouse Pokémon", Type.ELECTRIC, null, 0.8, 30, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 485, 60, 90, 55, 90, 80, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, true),
new PokemonSpecies(Species.SANDSHREW, 1, false, false, false, "Mouse Pokémon", Type.GROUND, null, 0.6, 12, Abilities.SAND_VEIL, Abilities.NONE, Abilities.SAND_RUSH, 300, 50, 75, 85, 20, 30, 40, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false),
@ -1111,7 +1111,7 @@ export function initSpecies() {
new PokemonSpecies(Species.GENGAR, 1, false, false, false, "Shadow Pokémon", Type.GHOST, Type.POISON, 1.5, 40.5, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false, true,
new PokemonForm("Normal", "", Type.GHOST, Type.POISON, 1.5, 40.5, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, false, null, true),
new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.GHOST, Type.POISON, 1.4, 40.5, Abilities.SHADOW_TAG, Abilities.NONE, Abilities.NONE, 600, 60, 65, 80, 170, 95, 130, 45, 50, 250),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GHOST, Type.POISON, 20, 999.9, Abilities.CURSED_BODY, Abilities.CURSED_BODY, Abilities.CURSED_BODY, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GHOST, Type.POISON, 20, 999.9, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250),
),
new PokemonSpecies(Species.ONIX, 1, false, false, false, "Rock Snake Pokémon", Type.ROCK, Type.GROUND, 8.8, 210, Abilities.ROCK_HEAD, Abilities.STURDY, Abilities.WEAK_ARMOR, 385, 35, 45, 160, 30, 45, 70, 45, 50, 77, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.DROWZEE, 1, false, false, false, "Hypnosis Pokémon", Type.PSYCHIC, null, 1, 32.4, Abilities.INSOMNIA, Abilities.FOREWARN, Abilities.INNER_FOCUS, 328, 60, 48, 45, 43, 90, 42, 190, 70, 66, GrowthRate.MEDIUM_FAST, 50, false),
@ -1119,7 +1119,7 @@ export function initSpecies() {
new PokemonSpecies(Species.KRABBY, 1, false, false, false, "River Crab Pokémon", Type.WATER, null, 0.4, 6.5, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 325, 30, 105, 90, 25, 25, 50, 225, 50, 65, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.KINGLER, 1, false, false, false, "Pincer Pokémon", Type.WATER, null, 1.3, 60, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true,
new PokemonForm("Normal", "", Type.WATER, null, 1.3, 60, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, false, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 19, 999.9, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, 575, 90, 155, 140, 50, 80, 70, 60, 50, 166),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 19, 999.9, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, 575, 90, 155, 140, 50, 70, 70, 60, 50, 166),
),
new PokemonSpecies(Species.VOLTORB, 1, false, false, false, "Ball Pokémon", Type.ELECTRIC, null, 0.5, 10.4, Abilities.SOUNDPROOF, Abilities.STATIC, Abilities.AFTERMATH, 330, 40, 30, 50, 55, 55, 100, 190, 70, 66, GrowthRate.MEDIUM_FAST, null, false),
new PokemonSpecies(Species.ELECTRODE, 1, false, false, false, "Ball Pokémon", Type.ELECTRIC, null, 1.2, 66.6, Abilities.SOUNDPROOF, Abilities.STATIC, Abilities.AFTERMATH, 490, 60, 50, 70, 80, 80, 150, 60, 70, 172, GrowthRate.MEDIUM_FAST, null, false),
@ -2300,25 +2300,25 @@ export function initSpecies() {
new PokemonSpecies(Species.MELTAN, 7, false, false, true, "Hex Nut Pokémon", Type.STEEL, null, 0.2, 8, Abilities.MAGNET_PULL, Abilities.NONE, Abilities.NONE, 300, 46, 65, 65, 55, 35, 34, 3, 0, 150, GrowthRate.SLOW, null, false),
new PokemonSpecies(Species.MELMETAL, 7, false, false, true, "Hex Nut Pokémon", Type.STEEL, null, 2.5, 800, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, GrowthRate.SLOW, null, false, true,
new PokemonForm("Normal", "", Type.STEEL, null, 2.5, 800, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, false, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, null, 25, 999.9, Abilities.IRON_FIST, Abilities.IRON_FIST, Abilities.IRON_FIST, 700, 175, 165, 155, 85, 75, 45, 3, 0, 300),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, null, 25, 999.9, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 700, 175, 165, 155, 85, 75, 45, 3, 0, 300),
),
new PokemonSpecies(Species.GROOKEY, 8, false, false, false, "Chimp Pokémon", Type.GRASS, null, 0.3, 5, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 310, 50, 65, 50, 40, 40, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.THWACKEY, 8, false, false, false, "Beat Pokémon", Type.GRASS, null, 0.7, 14, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 420, 70, 85, 70, 55, 60, 80, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.RILLABOOM, 8, false, false, false, "Drummer Pokémon", Type.GRASS, null, 2.1, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true,
new PokemonForm("Normal", "", Type.GRASS, null, 2.1, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, false, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, null, 28, 999.9, Abilities.GRASSY_SURGE, Abilities.GRASSY_SURGE, Abilities.GRASSY_SURGE, 630, 125, 150, 105, 85, 85, 80, 45, 50, 265),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, null, 28, 999.9, Abilities.GRASSY_SURGE, Abilities.NONE, Abilities.GRASSY_SURGE, 630, 125, 150, 105, 85, 85, 80, 45, 50, 265),
),
new PokemonSpecies(Species.SCORBUNNY, 8, false, false, false, "Rabbit Pokémon", Type.FIRE, null, 0.3, 4.5, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 310, 50, 71, 40, 40, 40, 69, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.RABOOT, 8, false, false, false, "Rabbit Pokémon", Type.FIRE, null, 0.6, 9, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 420, 65, 86, 60, 55, 60, 94, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.CINDERACE, 8, false, false, false, "Striker Pokémon", Type.FIRE, null, 1.4, 33, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true,
new PokemonForm("Normal", "", Type.FIRE, null, 1.4, 33, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, false, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, null, 27, 999.9, Abilities.LIBERO, Abilities.LIBERO, Abilities.LIBERO, 630, 100, 146, 80, 90, 80, 134, 45, 50, 265),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, null, 27, 999.9, Abilities.LIBERO, Abilities.NONE, Abilities.LIBERO, 630, 100, 146, 80, 90, 80, 134, 45, 50, 265),
),
new PokemonSpecies(Species.SOBBLE, 8, false, false, false, "Water Lizard Pokémon", Type.WATER, null, 0.3, 4, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 310, 50, 40, 40, 70, 40, 70, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.DRIZZILE, 8, false, false, false, "Water Lizard Pokémon", Type.WATER, null, 0.7, 11.5, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 420, 65, 60, 55, 95, 55, 90, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false),
new PokemonSpecies(Species.INTELEON, 8, false, false, false, "Secret Agent Pokémon", Type.WATER, null, 1.9, 45.2, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true,
new PokemonForm("Normal", "", Type.WATER, null, 1.9, 45.2, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, false, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 40, 999.9, Abilities.SNIPER, Abilities.SNIPER, Abilities.SNIPER, 630, 95, 97, 77, 147, 77, 137, 45, 50, 265),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 40, 999.9, Abilities.SNIPER, Abilities.NONE, Abilities.SNIPER, 630, 95, 97, 77, 147, 77, 137, 45, 50, 265),
),
new PokemonSpecies(Species.SKWOVET, 8, false, false, false, "Cheeky Pokémon", Type.NORMAL, null, 0.3, 2.5, Abilities.CHEEK_POUCH, Abilities.NONE, Abilities.GLUTTONY, 275, 70, 55, 55, 35, 35, 25, 255, 50, 55, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.GREEDENT, 8, false, false, false, "Greedy Pokémon", Type.NORMAL, null, 0.6, 6, Abilities.CHEEK_POUCH, Abilities.NONE, Abilities.GLUTTONY, 460, 120, 95, 95, 55, 75, 20, 90, 50, 161, GrowthRate.MEDIUM_FAST, 50, false),
@ -2424,7 +2424,7 @@ export function initSpecies() {
new PokemonForm("Ruby Swirl", "ruby-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true),
new PokemonForm("Caramel Swirl", "caramel-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true),
new PokemonForm("Rainbow Swirl", "rainbow-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FAIRY, null, 30, 999.9, Abilities.MISTY_SURGE, Abilities.MISTY_SURGE, Abilities.MISTY_SURGE, 595, 135, 60, 75, 130, 131, 64, 100, 50, 173),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FAIRY, null, 30, 999.9, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 595, 135, 60, 75, 130, 131, 64, 100, 50, 173),
),
new PokemonSpecies(Species.FALINKS, 8, false, false, false, "Formation Pokémon", Type.FIGHTING, null, 3, 62, Abilities.BATTLE_ARMOR, Abilities.NONE, Abilities.DEFIANT, 470, 65, 100, 100, 70, 60, 75, 45, 50, 165, GrowthRate.MEDIUM_FAST, null, false),
new PokemonSpecies(Species.PINCURCHIN, 8, false, false, false, "Sea Urchin Pokémon", Type.ELECTRIC, null, 0.3, 1, Abilities.LIGHTNING_ROD, Abilities.NONE, Abilities.ELECTRIC_SURGE, 435, 48, 101, 95, 91, 85, 15, 75, 50, 152, GrowthRate.MEDIUM_FAST, 50, false),
@ -2446,7 +2446,7 @@ export function initSpecies() {
new PokemonSpecies(Species.CUFANT, 8, false, false, false, "Copperderm Pokémon", Type.STEEL, null, 1.2, 100, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 330, 72, 80, 49, 40, 49, 40, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false),
new PokemonSpecies(Species.COPPERAJAH, 8, false, false, false, "Copperderm Pokémon", Type.STEEL, null, 3, 650, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, true,
new PokemonForm("Normal", "", Type.STEEL, null, 3, 650, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, false, null, true),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, Type.GROUND, 23, 999.9, Abilities.MOLD_BREAKER, Abilities.MOLD_BREAKER, Abilities.MOLD_BREAKER, 600, 167, 155, 89, 80, 89, 20, 90, 50, 175),
new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, Type.GROUND, 23, 999.9, Abilities.MOLD_BREAKER, Abilities.NONE, Abilities.MOLD_BREAKER, 600, 167, 155, 89, 80, 89, 20, 90, 50, 175),
),
new PokemonSpecies(Species.DRACOZOLT, 8, false, false, false, "Fossil Pokémon", Type.ELECTRIC, Type.DRAGON, 1.8, 190, Abilities.VOLT_ABSORB, Abilities.HUSTLE, Abilities.SAND_RUSH, 505, 90, 100, 90, 80, 70, 75, 45, 50, 177, GrowthRate.SLOW, null, false),
new PokemonSpecies(Species.ARCTOZOLT, 8, false, false, false, "Fossil Pokémon", Type.ELECTRIC, Type.ICE, 2.3, 150, Abilities.VOLT_ABSORB, Abilities.STATIC, Abilities.SLUSH_RUSH, 505, 90, 100, 90, 90, 80, 55, 45, 50, 177, GrowthRate.SLOW, null, false),

View File

@ -574,13 +574,13 @@ export class TrainerConfig {
case "magma": {
return {
[TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.SLUGMA, Species.SOLROCK, Species.HIPPOPOTAS, Species.BALTOY, Species.ROLYCOLY, Species.GLIGAR, Species.TORKOAL, Species.HOUNDOUR, Species.MAGBY ],
[TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.BARBOACH ],
[TrainerPoolTier.UNCOMMON]: [ Species.TRAPINCH, Species.SILICOBRA, Species.RHYHORN, Species.ANORITH, Species.LILEEP, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ],
[TrainerPoolTier.RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
};
}
case "aqua": {
return {
[TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID ],
[TrainerPoolTier.COMMON]: [ Species.CORPHISH, Species.SPHEAL, Species.CLAMPERL, Species.CHINCHOU, Species.WOOPER, Species.WINGULL, Species.TENTACOOL, Species.AZURILL, Species.LOTAD, Species.WAILMER, Species.REMORAID, Species.BARBOACH ],
[TrainerPoolTier.UNCOMMON]: [ Species.MANTYKE, Species.HISUI_QWILFISH, Species.ARROKUDA, Species.DHELMISE, Species.CLOBBOPUS, Species.FEEBAS, Species.PALDEA_WOOPER, Species.HORSEA, Species.SKRELP ],
[TrainerPoolTier.RARE]: [ Species.DONDOZO, Species.BASCULEGION ]
};
@ -601,9 +601,9 @@ export class TrainerConfig {
}
case "flare": {
return {
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ],
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.INKAY, Species.FOONGUS, Species.HELIOPTILE, Species.ELECTRIKE, Species.SKORUPI, Species.PURRLOIN, Species.CLAWITZER, Species.PANCHAM, Species.ESPURR, Species.BUNNELBY ],
[TrainerPoolTier.UNCOMMON]: [ Species.LITWICK, Species.SNEASEL, Species.PUMPKABOO, Species.PHANTUMP, Species.HONEDGE, Species.BINACLE, Species.HOUNDOUR, Species.SKRELP, Species.SLIGGOO ],
[TrainerPoolTier.RARE]: [ Species.NOIVERN, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ]
[TrainerPoolTier.RARE]: [ Species.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ]
};
}
case "aether": {
@ -1504,7 +1504,7 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.SLUGMA, Species.POOCHYENA, Species.NUMEL, Species.ZIGZAGOON, Species.DIGLETT, Species.MAGBY, Species.TORKOAL, Species.GROWLITHE, Species.BALTOY ],
[TrainerPoolTier.UNCOMMON]: [ Species.SOLROCK, Species.HIPPOPOTAS, Species.SANDACONDA, Species.PHANPY, Species.ROLYCOLY, Species.GLIGAR, Species.RHYHORN, Species.HEATMOR ],
[TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON ],
[TrainerPoolTier.RARE]: [ Species.TRAPINCH, Species.LILEEP, Species.ANORITH, Species.HISUI_GROWLITHE, Species.TURTONATOR, Species.ARON, Species.TOEDSCOOL ],
[TrainerPoolTier.SUPER_RARE]: [ Species.CAPSAKID, Species.CHARCADET ]
}),
[TrainerType.TABITHA]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("magma_admin", "magma", [ Species.CAMERUPT ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_aqua_magma_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1540,9 +1540,9 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerType.FLARE_GRUNT]: new TrainerConfig(++t).setHasGenders("Flare Grunt Female").setHasDouble("Flare Grunts").setMoneyMultiplier(1.0).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene))
.setSpeciesPools({
[TrainerPoolTier.COMMON]: [ Species.FLETCHLING, Species.LITLEO, Species.PONYTA, Species.INKAY, Species.HOUNDOUR, Species.SKORUPI, Species.SCRAFTY, Species.CROAGUNK, Species.SCATTERBUG, Species.ESPURR ],
[TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP ],
[TrainerPoolTier.UNCOMMON]: [ Species.HELIOPTILE, Species.ELECTRIKE, Species.SKRELP, Species.PANCHAM, Species.PURRLOIN, Species.POOCHYENA, Species.BINACLE, Species.CLAUNCHER, Species.PUMPKABOO, Species.PHANTUMP, Species.FOONGUS ],
[TrainerPoolTier.RARE]: [ Species.LITWICK, Species.SNEASEL, Species.PAWNIARD, Species.SLIGGOO ],
[TrainerPoolTier.SUPER_RARE]: [ Species.NOIVERN, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ]
[TrainerPoolTier.SUPER_RARE]: [ Species.NOIBAT, Species.HISUI_SLIGGOO, Species.HISUI_AVALUGG ]
}),
[TrainerType.BRYONY]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin_female", "flare", [ Species.LIEPARD ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
[TrainerType.XEROSIC]: new TrainerConfig(++t).setMoneyMultiplier(1.5).initForEvilTeamAdmin("flare_admin", "flare", [ Species.MALAMAR ]).setEncounterBgm(TrainerType.PLASMA_GRUNT).setBattleBgm("battle_plasma_grunt").setMixedBattleBgm("battle_flare_grunt").setVictoryBgm("victory_team_plasma").setPartyTemplateFunc(scene => getEvilGruntPartyTemplate(scene)),
@ -1893,7 +1893,10 @@ export const trainerConfigs: TrainerConfigs = {
}),
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ]))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.DUGTRIO, Species.ALOLA_DUGTRIO ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.HONCHKROW ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.NIDOKING, Species.NIDOQUEEN ]))
@ -1945,6 +1948,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Camerupt
p.generateName();
p.gender = Gender.MALE;
})),
[TrainerType.MAXIE_2]: new TrainerConfig(++t).setName("Maxie").initForEvilTeamLeader("Magma Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SOLROCK, Species.TYPHLOSION ], TrainerSlot.TRAINER, true, p => {
@ -1967,6 +1971,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Camerupt
p.generateName();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GROUDON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -1985,6 +1990,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Sharpedo
p.generateName();
p.gender = Gender.MALE;
})),
[TrainerType.ARCHIE_2]: new TrainerConfig(++t).setName("Archie").initForEvilTeamLeader("Aqua Boss", [], true).setMixedBattleBgm("battle_aqua_magma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.EMPOLEON, Species.LUDICOLO ], TrainerSlot.TRAINER, true, p => {
@ -2010,6 +2016,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Sharpedo
p.generateName();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYOGRE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2031,6 +2038,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
})),
[TrainerType.CYRUS_2]: new TrainerConfig(++t).setName("Cyrus").initForEvilTeamLeader("Galactic Boss", [], true).setMixedBattleBgm("battle_galactic_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.AZELF, Species.UXIE, Species.MESPRIT ], TrainerSlot.TRAINER, true, p => {
@ -2049,6 +2057,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.DARKRAI ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2065,6 +2074,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.MALE;
})),
[TrainerType.GHETSIS_2]: new TrainerConfig(++t).setName("Ghetsis").initForEvilTeamLeader("Plasma Boss", [], true).setMixedBattleBgm("battle_plasma_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.GENESECT ], TrainerSlot.TRAINER, true, p => {
@ -2084,6 +2094,11 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
if (p.species.speciesId === Species.HYDREIGON) {
p.gender = Gender.MALE;
} else if (p.species.speciesId === Species.IRON_JUGULIS) {
p.gender = Gender.GENDERLESS;
}
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.KYUREM ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2105,6 +2120,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Gyarados
p.generateName();
p.gender = Gender.MALE;
})),
[TrainerType.LYSANDRE_2]: new TrainerConfig(++t).setName("Lysandre").initForEvilTeamLeader("Flare Boss", [], true).setMixedBattleBgm("battle_flare_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SCREAM_TAIL, Species.FLUTTER_MANE ], TrainerSlot.TRAINER, true, p => {
@ -2124,6 +2140,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL;
p.formIndex = 1; // Mega Gyardos
p.generateName();
p.gender = Gender.MALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.YVELTAL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2131,7 +2148,10 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL;
})),
[TrainerType.LUSAMINE]: new TrainerConfig(++t).setName("Lusamine").initForEvilTeamLeader("Aether Boss", []).setMixedBattleBgm("battle_aether_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ]))
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.LILLIGANT, Species.HISUI_LILLIGANT ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.GALAR_SLOWBRO, Species.GALAR_SLOWKING ]))
@ -2148,7 +2168,10 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ROGUE_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.MILOTIC, Species.PRIMARINA ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ]))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.CLEFABLE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.STAKATAKA, Species.CELESTEELA, Species.GUZZLORD ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ROGUE_BALL;
@ -2191,6 +2214,7 @@ export const trainerConfigs: TrainerConfigs = {
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.GOLISOPOD ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL;
})),
[TrainerType.GUZMA_2]: new TrainerConfig(++t).setName("Guzma").initForEvilTeamLeader("Skull Boss", [], true).setMixedBattleBgm("battle_skull_boss").setVictoryBgm("victory_team_plasma")
@ -2198,6 +2222,7 @@ export const trainerConfigs: TrainerConfigs = {
p.setBoss(true, 2);
p.generateAndPopulateMoveset();
p.abilityIndex = 2; //Anticipation
p.gender = Gender.MALE;
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.SCIZOR, Species.KLEAVOR ], TrainerSlot.TRAINER, true, p => {
@ -2239,6 +2264,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // G-Max Copperajah
p.generateName();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
})),
[TrainerType.ROSE_2]: new TrainerConfig(++t).setName("Rose").initForEvilTeamLeader("Macro Boss", [], true).setMixedBattleBgm("battle_macro_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.ARCHALUDON ], TrainerSlot.TRAINER, true, p => {
@ -2262,6 +2288,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // G-Max Copperajah
p.generateName();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
})),
[TrainerType.PENNY]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", []).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VAPOREON, Species.JOLTEON, Species.FLAREON ]))
@ -2275,8 +2302,9 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = Utils.randSeedInt(5, 1); // Heat, Wash, Frost, Fan, or Mow
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Pixilate
p.generateAndPopulateMoveset();
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(5, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2290,20 +2318,21 @@ export const trainerConfigs: TrainerConfigs = {
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form
p.abilityIndex = 2; // Pixilate
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
p.gender = Gender.FEMALE;
}))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.ENTEI, Species.RAIKOU, Species.SUICUNE ], TrainerSlot.TRAINER, true, p => {
p.generateAndPopulateMoveset();
p.pokeball = PokeballType.ULTRA_BALL;
}))
.setPartyMemberFunc(2, getRandomPartyMemberFunc([ Species.WALKING_WAKE, Species.GOUGING_FIRE, Species.RAGING_BOLT ]))
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
.setPartyMemberFunc(3, getRandomPartyMemberFunc([ Species.REVAVROOM ], TrainerSlot.TRAINER, true, p => {
p.formIndex = Utils.randSeedInt(5, 1); //Random Starmobile form
p.generateAndPopulateMoveset();
p.abilityIndex = 2; // Pixilate
p.pokeball = PokeballType.ROGUE_BALL;
}))
.setPartyMemberFunc(4, getRandomPartyMemberFunc([ Species.EEVEE ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2);
@ -2318,7 +2347,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.MASTER_BALL;
}))
.setGenModifiersFunc(party => {
const teraPokemon = party[3];
const teraPokemon = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true)

View File

@ -54,7 +54,7 @@ export enum BattlerTagType {
CURSED = "CURSED",
CHARGED = "CHARGED",
ROOSTED = "ROOSTED",
MAGNET_RISEN = "MAGNET_RISEN",
FLOATING = "FLOATING",
MINIMIZED = "MINIMIZED",
DESTINY_BOND = "DESTINY_BOND",
CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION",
@ -80,10 +80,12 @@ export enum BattlerTagType {
DOUBLE_SHOCKED = "DOUBLE_SHOCKED",
AUTOTOMIZED = "AUTOTOMIZED",
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
POWER_TRICK = "POWER_TRICK",
HEAL_BLOCK = "HEAL_BLOCK",
TORMENT = "TORMENT",
TAUNT = "TAUNT",
IMPRISON = "IMPRISON",
SYRUP_BOMB = "SYRUP_BOMB",
ELECTRIFIED = "ELECTRIFIED",
TELEKINESIS = "TELEKINESIS"
}

View File

@ -1,6 +1,6 @@
export enum EggTier {
COMMON,
GREAT,
ULTRA,
MASTER
RARE,
EPIC,
LEGENDARY
}

View File

@ -50,7 +50,7 @@ export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boo
return isIncrease ? "battle:statRose" : "battle:statFell";
} else if (stages === 2) {
return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell";
} else if (stages <= 6) {
} else if (stages > 2 && stages <= 6) {
return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell";
}
return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower";

View File

@ -579,26 +579,28 @@ export class Arena {
* Applies each `ArenaTag` in this Arena, based on which side (self, enemy, or both) is passed in as a parameter
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param side {@linkcode ArenaTagSide} which side's arena tags to apply
* @param simulated if `true`, this applies arena tags without changing game state
* @param args array of parameters that the called upon tags may need
*/
applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, ...args: unknown[]): void {
applyTagsForSide(tagType: ArenaTagType | Constructor<ArenaTag>, side: ArenaTagSide, simulated: boolean, ...args: unknown[]): void {
let tags = typeof tagType === "string"
? this.tags.filter(t => t.tagType === tagType)
: this.tags.filter(t => t instanceof tagType);
if (side !== ArenaTagSide.BOTH) {
tags = tags.filter(t => t.side === side);
}
tags.forEach(t => t.apply(this, args));
tags.forEach(t => t.apply(this, simulated, ...args));
}
/**
* Applies the specified tag to both sides (ie: both user and trainer's tag that match the Tag specified)
* by calling {@linkcode applyTagsForSide()}
* @param tagType Either an {@linkcode ArenaTagType} string, or an actual {@linkcode ArenaTag} class to filter which ones to apply
* @param simulated if `true`, this applies arena tags without changing game state
* @param args array of parameters that the called upon tags may need
*/
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, ...args: unknown[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args);
applyTags(tagType: ArenaTagType | Constructor<ArenaTag>, simulated: boolean, ...args: unknown[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args);
}
/**

View File

@ -19,7 +19,7 @@ import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags";
import { WeatherType } from "#app/data/weather";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability";
@ -749,9 +749,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const relX = newOffset[0] - initialOffset[0];
const relY = newOffset[1] - initialOffset[1];
const subTag = this.getTag(SubstituteTag);
if (duration) {
// TODO: can this use stricter typing?
const targets: any[] = [ this ];
if (subTag?.sprite) {
targets.push(subTag.sprite);
}
this.scene.tweens.add({
targets: this,
targets: targets,
x: (_target, _key, value: number) => value + relX,
y: (_target, _key, value: number) => value + relY,
duration: duration,
@ -761,6 +768,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} else {
this.x += relX;
this.y += relY;
if (subTag?.sprite) {
subTag.sprite.x += relX;
subTag.sprite.y += relY;
}
}
});
}
@ -983,7 +994,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder);
}
statHolder.value = Utils.clampInt(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER);
this.setStat(s, statHolder.value);
}
@ -1417,10 +1428,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns {boolean} Whether the ability is present and active
*/
hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean {
if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) {
if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) {
return true;
}
if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) {
if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) {
return true;
}
return false;
@ -1488,7 +1499,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
isGrounded(): boolean {
return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN) && !this.getTag(SemiInvulnerableTag));
return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.FLOATING) && !this.getTag(SemiInvulnerableTag));
}
/**
@ -1527,7 +1538,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, moveTypeHolder);
this.scene.arena.applyTags(ArenaTagType.ION_DELUGE, simulated, moveTypeHolder);
if (this.getTag(BattlerTagType.ELECTRIFIED)) {
moveTypeHolder.value = Type.ELECTRIC;
}
@ -2594,7 +2605,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
/** Reduces damage if this Pokemon has a relevant screen (e.g. Light Screen for special attacks) */
const screenMultiplier = new Utils.NumberHolder(1);
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier);
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, simulated, moveCategory, screenMultiplier);
/**
* For each {@linkcode HitsTagAttr} the move has, doubles the damage of the move if:
@ -2684,7 +2695,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/
apply(source: Pokemon, move: Move): HitResult {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (move.category === MoveCategory.STATUS) {
const moveCategory = new Utils.NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory);
if (moveCategory.value === MoveCategory.STATUS) {
const cancelled = new Utils.BooleanHolder(false);
const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled);
@ -3035,6 +3048,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
continue;
}
if (tag instanceof PowerTrickTag) {
tag.swapStat(this);
}
this.summonData.tags.push(tag);
}
@ -3049,8 +3066,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*
* @see {@linkcode MoveRestrictionBattlerTag}
*/
isMoveRestricted(moveId: Moves): boolean {
return this.getRestrictingTag(moveId) !== null;
public isMoveRestricted(moveId: Moves, pokemon?: Pokemon): boolean {
return this.getRestrictingTag(moveId, pokemon) !== null;
}
/**
@ -3083,7 +3100,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/
getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null {
for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) {
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) {
if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId, user)) {
return tag as MoveRestrictionBattlerTag;
} else if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) {
return tag as MoveRestrictionBattlerTag;
@ -5126,7 +5143,7 @@ export class PokemonMove {
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
*/
isUsable(pokemon: Pokemon, ignorePp: boolean = false, ignoreRestrictionTags: boolean = false): boolean {
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId)) {
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) {
return false;
}

View File

@ -383,7 +383,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
const battle = this.scene.currentBattle;
const template = this.getPartyTemplate();
let species: PokemonSpecies;
let baseSpecies: PokemonSpecies;
if (this.config.speciesPools) {
const tierValue = Utils.randSeedInt(512);
let tier = tierValue >= 156 ? TrainerPoolTier.COMMON : tierValue >= 32 ? TrainerPoolTier.UNCOMMON : tierValue >= 6 ? TrainerPoolTier.RARE : tierValue >= 1 ? TrainerPoolTier.SUPER_RARE : TrainerPoolTier.ULTRA_RARE;
@ -393,17 +393,17 @@ export default class Trainer extends Phaser.GameObjects.Container {
tier--;
}
const tierPool = this.config.speciesPools[tier];
species = getPokemonSpecies(Utils.randSeedItem(tierPool));
baseSpecies = getPokemonSpecies(Utils.randSeedItem(tierPool));
} else {
species = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter);
baseSpecies = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter);
}
let ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex));
let ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex));
let retry = false;
console.log(ret.getName());
if (pokemonPrevolutions.hasOwnProperty(species.speciesId) && ret.speciesId !== species.speciesId) {
if (pokemonPrevolutions.hasOwnProperty(baseSpecies.speciesId) && ret.speciesId !== baseSpecies.speciesId) {
retry = true;
} else if (template.isBalanced(battle.enemyParty.length)) {
const partyMemberTypes = battle.enemyParty.map(p => p.getTypes(true)).flat();
@ -417,7 +417,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
console.log("Attempting reroll of species evolution to fit specialty type...");
let evoAttempt = 0;
while (retry && evoAttempt++ < 10) {
ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex));
ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex));
console.log(ret.name);
if (this.config.specialtyTypes.find(t => ret.isOfType(t))) {
retry = false;
@ -426,7 +426,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
}
// Prompts reroll of party member species if species already present in the enemy party
if (this.checkDuplicateSpecies(ret)) {
if (this.checkDuplicateSpecies(ret, baseSpecies)) {
console.log("Duplicate species detected, prompting reroll...");
retry = true;
}
@ -442,13 +442,16 @@ export default class Trainer extends Phaser.GameObjects.Container {
/**
* Checks if the enemy trainer already has the Pokemon species in their party
* @param {PokemonSpecies} species {@linkcode PokemonSpecies}
* @param {PokemonSpecies} baseSpecies {@linkcode PokemonSpecies} - baseSpecies of the Pokemon if species is forced to evolve
* @returns `true` if the species is already present in the party
*/
checkDuplicateSpecies(species: PokemonSpecies): boolean {
checkDuplicateSpecies(species: PokemonSpecies, baseSpecies: PokemonSpecies): boolean {
const staticPartyPokemon = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1);
const currentPartySpecies = this.scene.getEnemyParty().map(p => {
return p.species.speciesId;
});
return currentPartySpecies.includes(species.speciesId);
return currentPartySpecies.includes(species.speciesId) || staticPartyPokemon.includes(baseSpecies.speciesId);
}
getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] {

View File

@ -3635,7 +3635,7 @@ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer
}
if (!isPlayer) {
scene.clearEnemyHeldItemModifiers();
scene.clearEnemyHeldItemModifiers(pokemon);
}
heldItemsOverride.forEach(item => {

View File

@ -8,7 +8,6 @@ export class Phase {
}
start() {
console.log(`%cStart Phase ${this.constructor.name}`, "color:green;");
if (this.scene.abilityBar.shown) {
this.scene.abilityBar.resetAutoHideTimer();
}

View File

@ -0,0 +1,23 @@
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
import { Phase } from "#app/phase";
import { BattlerIndex } from "#app/battle";
import BattleScene from "#app/battle-scene";
export class CheckStatusEffectPhase extends Phase {
private order : BattlerIndex[];
constructor(scene : BattleScene, order : BattlerIndex[]) {
super(scene);
this.scene = scene;
this.order = order;
}
start() {
const field = this.scene.getField();
for (const o of this.order) {
if (field[o].status && field[o].status.isPostTurn()) {
this.scene.unshiftPhase(new PostTurnStatusEffectPhase(this.scene, o));
}
}
this.end();
}
}

View File

@ -114,8 +114,8 @@ export class CommandPhase extends FieldPhase {
// Decides between a Disabled, Not Implemented, or No PP translation message
const errorMessage =
playerPokemon.isMoveRestricted(move.moveId)
? playerPokemon.getRestrictingTag(move.moveId)!.selectionDeniedText(playerPokemon, move.moveId)
playerPokemon.isMoveRestricted(move.moveId, playerPokemon)
? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId)
: move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP";
const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator

View File

@ -33,7 +33,7 @@ export class EggLapsePhase extends Phase {
if (eggsToHatchCount > 0) {
if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) {
this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => {
// show prompt for skip
// show prompt for skip, blocking inputs for 1 second
this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0);
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
this.hatchEggsSkipped(eggsToHatch);
@ -41,7 +41,8 @@ export class EggLapsePhase extends Phase {
}, () => {
this.hatchEggsRegular(eggsToHatch);
this.end();
}
},
null, null, null, 1000, true
);
}, 100, true);
} else if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 2) {

View File

@ -1,7 +1,6 @@
import BattleScene from "#app/battle-scene";
import { Phase } from "#app/phase";
import { Mode } from "#app/ui/ui";
import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler";
import { EggHatchData } from "#app/data/egg-hatch-data";
/**
@ -11,7 +10,6 @@ import { EggHatchData } from "#app/data/egg-hatch-data";
*/
export class EggSummaryPhase extends Phase {
private eggHatchData: EggHatchData[];
private eggHatchHandler: EggHatchSceneHandler;
constructor(scene: BattleScene, eggHatchData: EggHatchData[]) {
super(scene);
@ -26,7 +24,6 @@ export class EggSummaryPhase extends Phase {
if (i >= this.eggHatchData.length) {
this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => {
this.scene.fadeOutBgm(undefined, false);
this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler;
});
} else {

View File

@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr,
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag";
import { MoveAnim } from "#app/data/battle-anims";
import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags";
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move";
import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, OneHitKOAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move";
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { Moves } from "#app/enums/moves";
@ -99,8 +99,9 @@ export class MoveEffectPhase extends PokemonPhase {
const targetHitChecks = Object.fromEntries(targets.map(p => [ p.getBattlerIndex(), this.hitCheck(p) ]));
const hasActiveTargets = targets.some(t => t.isActive(true));
/** Check if the target is immune via ability to the attacking move */
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
/** Check if the target is immune via ability to the attacking move, and NOT in semi invulnerable state */
const isImmune = targets[0].hasAbilityWithAttr(TypeImmunityAbAttr) && (targets[0].getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
&& !targets[0].getTag(SemiInvulnerableTag);
/**
* If no targets are left for the move to hit (FAIL), or the invoked move is single-target
@ -140,7 +141,7 @@ export class MoveEffectPhase extends PokemonPhase {
const bypassIgnoreProtect = new Utils.BooleanHolder(false);
/** If the move is not targeting a Pokemon on the user's side, try to apply conditional protection effects */
if (!this.move.getMove().isAllyTarget()) {
this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect);
this.scene.arena.applyTagsForSide(ConditionalProtectTag, targetSide, false, hasConditionalProtectApplied, user, target, move.id, bypassIgnoreProtect);
}
/** Is the target protected by Protect, etc. or a relevant conditional protection effect? */
@ -148,8 +149,9 @@ export class MoveEffectPhase extends PokemonPhase {
&& (hasConditionalProtectApplied.value || (!target.findTags(t => t instanceof DamageProtectedTag).length && target.findTags(t => t instanceof ProtectedTag).find(t => target.lapseTag(t.tagType)))
|| (this.move.getMove().category !== MoveCategory.STATUS && target.findTags(t => t instanceof DamageProtectedTag).find(t => target.lapseTag(t.tagType))));
/** Is the pokemon immune due to an ablility? */
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move));
/** Is the pokemon immune due to an ablility, and also not in a semi invulnerable state? */
const isImmune = target.hasAbilityWithAttr(TypeImmunityAbAttr) && (target.getAbility()?.getAttrs(TypeImmunityAbAttr)?.[0]?.getImmuneType() === user.getMoveType(move))
&& !target.getTag(SemiInvulnerableTag);
/**
* If the move missed a target, stop all future hits against that target
@ -404,6 +406,10 @@ export class MoveEffectPhase extends PokemonPhase {
return true;
}
if (target.getTag(BattlerTagType.TELEKINESIS) && !target.getTag(SemiInvulnerableTag) && !this.move.getMove().hasAttr(OneHitKOAttr)) {
return true;
}
const semiInvulnerableTag = target.getTag(SemiInvulnerableTag);
if (semiInvulnerableTag
&& !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType)

View File

@ -8,10 +8,6 @@ import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
import { Type } from "#app/data/type";
import { getTerrainBlockMessage } from "#app/data/weather";
import { Abilities } from "#app/enums/abilities";
import { BattlerTagType } from "#app/enums/battler-tag-type";
import { Moves } from "#app/enums/moves";
import { StatusEffect } from "#app/enums/status-effect";
import { MoveUsedEvent } from "#app/events/battle-scene";
import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
@ -20,7 +16,11 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import * as Utils from "#app/utils";
import { BooleanHolder, NumberHolder } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next";
export class MovePhase extends BattlePhase {
@ -89,7 +89,7 @@ export class MovePhase extends BattlePhase {
this.cancelled = true;
}
public start() {
public start(): void {
super.start();
console.log(Moves[this.move.moveId]);
@ -128,7 +128,9 @@ export class MovePhase extends BattlePhase {
this.lapsePreMoveAndMoveTags();
if (!(this.failed || this.cancelled)) {
this.resolveFinalPreMoveCancellationChecks();
}
if (this.cancelled || this.failed) {
this.handlePreMoveFailures();
@ -140,24 +142,25 @@ export class MovePhase extends BattlePhase {
}
/** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */
protected resolveFinalPreMoveCancellationChecks() {
protected resolveFinalPreMoveCancellationChecks(): void {
const targets = this.getActiveTargetPokemon();
const moveQueue = this.pokemon.getMoveQueue();
if (targets.length === 0 || (moveQueue.length && moveQueue[0].move === Moves.NONE)) {
this.showMoveText();
this.showFailedText();
this.cancelled = true;
this.cancel();
}
}
public getActiveTargetPokemon() {
public getActiveTargetPokemon(): Pokemon[] {
return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex()));
}
/**
* Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects.
*/
protected resolvePreMoveStatusEffects() {
protected resolvePreMoveStatusEffects(): void {
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
this.pokemon.status.incrementTurn();
let activated = false;
@ -198,7 +201,7 @@ export class MovePhase extends BattlePhase {
* Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed.
* Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful.
*/
protected lapsePreMoveAndMoveTags() {
protected lapsePreMoveAndMoveTags(): void {
this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE);
// TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked?
@ -207,7 +210,7 @@ export class MovePhase extends BattlePhase {
}
}
protected useMove() {
protected useMove(): void {
const targets = this.getActiveTargetPokemon();
const moveQueue = this.pokemon.getMoveQueue();
@ -217,7 +220,8 @@ export class MovePhase extends BattlePhase {
this.showMoveText();
// TODO: Clean up implementation of two-turn moves.
if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used
if (moveQueue.length > 0) {
// Using .shift here clears out two turn moves once they've been used
this.ignorePp = moveQueue.shift()?.ignorePP ?? false;
}
@ -226,7 +230,7 @@ export class MovePhase extends BattlePhase {
const ppUsed = 1 + this.getPpIncreaseFromPressure(targets);
this.move.usePp(ppUsed);
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed));
}
// Update the battle's "last move" pointer, unless we're currently mimicking a move.
@ -275,7 +279,7 @@ export class MovePhase extends BattlePhase {
this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual });
let failedText: string | undefined;
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false));
const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false));
if (failureMessage) {
failedText = failureMessage;
@ -299,7 +303,7 @@ export class MovePhase extends BattlePhase {
* Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`,
* then ends the phase.
*/
public end() {
public end(): void {
if (!this.followUp && this.canMove()) {
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex()));
}
@ -313,7 +317,7 @@ export class MovePhase extends BattlePhase {
*
* TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability.
*/
public getPpIncreaseFromPressure(targets: Pokemon[]) {
public getPpIncreaseFromPressure(targets: Pokemon[]): number {
const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr));
return foesWithPressure.length;
}
@ -323,10 +327,10 @@ export class MovePhase extends BattlePhase {
* - Move redirection abilities, effects, etc.
* - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`).
*/
protected resolveRedirectTarget() {
protected resolveRedirectTarget(): void {
if (this.targets.length === 1) {
const currentTarget = this.targets[0];
const redirectTarget = new Utils.NumberHolder(currentTarget);
const redirectTarget = new NumberHolder(currentTarget);
// check move redirection abilities of every pokemon *except* the user.
this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget));
@ -372,7 +376,7 @@ export class MovePhase extends BattlePhase {
* If there is no last attacker, or they are no longer on the field, a message is displayed and the
* move is marked for failure.
*/
protected resolveCounterAttackTarget() {
protected resolveCounterAttackTarget(): void {
if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) {
if (this.pokemon.turnData.attacksReceived.length) {
this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex;
@ -411,7 +415,7 @@ export class MovePhase extends BattlePhase {
*
* TODO: handle charge moves more gracefully
*/
protected handlePreMoveFailures() {
protected handlePreMoveFailures(): void {
if (this.cancelled || this.failed) {
if (this.failed) {
const ppUsed = this.ignorePp ? 0 : 1;

View File

@ -6,7 +6,6 @@ import { StatusEffect } from "#app/enums/status-effect";
import Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonPhase } from "./pokemon-phase";
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
export class ObtainStatusEffectPhase extends PokemonPhase {
private statusEffect?: StatusEffect | undefined;
@ -33,9 +32,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined));
if (pokemon.status?.isPostTurn()) {
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex));
}
this.end();
});
return;

View File

@ -20,7 +20,7 @@ export class PostSummonPhase extends PokemonPhase {
if (pokemon.status?.effect === StatusEffect.TOXIC) {
pokemon.status.turnCount = 0;
}
this.scene.arena.applyTags(ArenaTrapTag, pokemon);
this.scene.arena.applyTags(ArenaTrapTag, false, pokemon);
// If this is mystery encounter and has post summon phase tag, apply post summon effects
if (this.scene.currentBattle.isBattleMysteryEncounter() && pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag).length > 0) {

View File

@ -36,6 +36,16 @@ export class StatStageChangePhase extends PokemonPhase {
}
start() {
// Check if multiple stats are being changed at the same time, then run SSCPhase for each of them
if (this.stats.length > 1) {
for (let i = 0; i < this.stats.length; i++) {
const stat = [ this.stats[i] ];
this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange));
}
return this.end();
}
const pokemon = this.getPokemon();
if (!pokemon.isActive(true)) {
@ -54,8 +64,7 @@ export class StatStageChangePhase extends PokemonPhase {
const cancelled = new BooleanHolder(false);
if (!this.selfTarget && stages.value < 0) {
// TODO: Include simulate boolean when tag applications can be simulated
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, cancelled);
}
if (!cancelled.value && !this.selfTarget && stages.value < 0) {

View File

@ -13,10 +13,10 @@ import { BerryPhase } from "./berry-phase";
import { FieldPhase } from "./field-phase";
import { MoveHeaderPhase } from "./move-header-phase";
import { MovePhase } from "./move-phase";
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
import { SwitchSummonPhase } from "./switch-summon-phase";
import { TurnEndPhase } from "./turn-end-phase";
import { WeatherEffectPhase } from "./weather-effect-phase";
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import { BattlerIndex } from "#app/battle";
import { TrickRoomTag } from "#app/data/arena-tag";
import { SwitchType } from "#enums/switch-type";
@ -45,7 +45,7 @@ export class TurnStartPhase extends FieldPhase {
// Next, a check for Trick Room is applied to determine sort order.
const speedReversed = new Utils.BooleanHolder(false);
this.scene.arena.applyTags(TrickRoomTag, speedReversed);
this.scene.arena.applyTags(TrickRoomTag, false, speedReversed);
// Adjust the sort function based on whether Trick Room is active.
orderedTargets.sort((a: Pokemon, b: Pokemon) => {
@ -158,7 +158,7 @@ export class TurnStartPhase extends FieldPhase {
if (!queuedMove) {
continue;
}
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move);
const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move);
if (move.getMove().hasAttr(MoveHeaderAttr)) {
this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move));
}
@ -206,11 +206,8 @@ export class TurnStartPhase extends FieldPhase {
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
for (const o of moveOrder) {
if (field[o].status && field[o].status.isPostTurn()) {
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
}
}
/** Add a new phase to check who should be taking status damage */
this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder));
this.scene.pushPhase(new BerryPhase(this.scene));
this.scene.pushPhase(new TurnEndPhase(this.scene));

View File

@ -1,5 +1,5 @@
import { Arena } from "../field/arena";
import { ArenaTag } from "../data/arena-tag";
import { ArenaTag, loadArenaTag } from "../data/arena-tag";
import { Biome } from "#enums/biome";
import { Weather } from "../data/weather";
import { Terrain } from "#app/data/terrain";
@ -15,6 +15,10 @@ export default class ArenaData {
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
this.tags = sourceArena ? sourceArena.tags : [];
this.tags = [];
if (source.tags) {
this.tags = source.tags.map(t => loadArenaTag(t));
}
}
}

View File

@ -31,7 +31,7 @@ import { TrainerVariant } from "#app/field/trainer";
import { Variant } from "#app/data/variant";
import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad";
import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard";
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
import * as Modifier from "#app/modifier/modifier";
import { StatusEffect } from "#app/data/status-effect";
import ChallengeData from "#app/system/challenge-data";
@ -50,6 +50,7 @@ import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatch
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
import { ArenaTrapTag } from "#app/data/arena-tag";
export const defaultStarterSpecies: Species[] = [
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
@ -1085,8 +1086,18 @@ export class GameData {
scene.arena.terrain = sessionData.arena.terrain;
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
// TODO
//scene.arena.tags = sessionData.arena.tags;
scene.arena.tags = sessionData.arena.tags;
if (scene.arena.tags) {
for (const tag of scene.arena.tags) {
if (tag instanceof ArenaTrapTag) {
const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag;
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers));
} else {
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount));
}
}
}
for (const modifierData of sessionData.modifiers) {
const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]);

View File

@ -1,17 +1,19 @@
import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move";
import { Status, StatusEffect } from "#app/data/status-effect";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Abilities - BATTLE BOND", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const baseForm = 1;
const ashForm = 2;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
@ -24,40 +26,68 @@ describe("Abilities - BATTLE BOND", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
game.override.battleType("single");
game.override.ability(Abilities.BATTLE_BOND);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
game.override.battleType("single")
.startingWave(4) // Leads to arena reset on Wave 5 trainer battle
.ability(Abilities.BATTLE_BOND)
.starterForms({ [Species.GRENINJA]: ashForm, })
.moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ])
.enemySpecies(Species.BULBASAUR)
.enemyMoveset(Moves.SPLASH)
.startingLevel(100) // Avoid levelling up
.enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()`
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 1;
const ashForm = 2;
game.override.startingWave(4);
game.override.starterForms({
[Species.GRENINJA]: ashForm,
});
it("check if fainted pokemon switches to base form on arena reset", async () => {
await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]);
await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]);
const greninja = game.scene.getParty()[1];
expect(greninja.formIndex).toBe(ashForm);
const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA);
expect(greninja).toBeDefined();
expect(greninja!.formIndex).toBe(ashForm);
greninja!.hp = 0;
greninja!.status = new Status(StatusEffect.FAINT);
expect(greninja!.isFainted()).toBe(true);
greninja.hp = 0;
greninja.status = new Status(StatusEffect.FAINT);
expect(greninja.isFainted()).toBe(true);
game.move.select(Moves.SPLASH);
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
await game.phaseInterceptor.to("TurnEndPhase");
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
await game.phaseInterceptor.to("QuietFormChangePhase");
expect(greninja!.formIndex).toBe(baseForm);
},
);
expect(greninja.formIndex).toBe(baseForm);
});
it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => {
await game.classicMode.startBattle([ Species.GRENINJA ]);
const waterShuriken = allMoves[Moves.WATER_SHURIKEN];
vi.spyOn(waterShuriken, "calculateBattlePower");
let actualMultiHitType: MultiHitType | null = null;
const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0];
vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => {
actualMultiHitType = multiHitAttr.getMultiHitType();
return 3;
});
// Wave 4: Use Water Shuriken in Ash form
let expectedBattlePower = 20;
let expectedMultiHitType = MultiHitType._3;
game.move.select(Moves.WATER_SHURIKEN);
await game.phaseInterceptor.to("BerryPhase", false);
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
expect(actualMultiHitType).toBe(expectedMultiHitType);
await game.doKillOpponents();
await game.toNextWave();
// Wave 5: Use Water Shuriken in base form
expectedBattlePower = 15;
expectedMultiHitType = MultiHitType._2_TO_5;
game.move.select(Moves.WATER_SHURIKEN);
await game.phaseInterceptor.to("BerryPhase", false);
expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower);
expect(actualMultiHitType).toBe(expectedMultiHitType);
});
});

View File

@ -0,0 +1,72 @@
import { Stat } from "#enums/stat";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Competitive", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleType("single")
.enemySpecies(Species.BEEDRILL)
.enemyMoveset(Moves.TICKLE)
.startingLevel(1)
.moveset([ Moves.SPLASH, Moves.CLOSE_COMBAT ])
.ability(Abilities.COMPETITIVE);
});
it("lower atk and def by 1 via tickle, then increase spatk by 4 via competitive", async () => {
await game.classicMode.startBattle([ Species.FLYGON ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnInitPhase);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4);
});
it("lowering your own stats should not trigger competitive", async () => {
game.override.enemyMoveset(Moves.SPLASH);
await game.classicMode.startBattle([ Species.FLYGON ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.CLOSE_COMBAT);
await game.phaseInterceptor.to(TurnInitPhase);
expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);
});
it("white herb should remove only the negative effects", async () => {
game.override.startingHeldItems([{ name: "WHITE_HERB" }]);
await game.classicMode.startBattle([ Species.FLYGON ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnInitPhase);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4);
});
});

View File

@ -0,0 +1,70 @@
import { Stat } from "#enums/stat";
import { TurnInitPhase } from "#app/phases/turn-init-phase";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Defiant", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override.battleType("single")
.enemySpecies(Species.BEEDRILL)
.enemyMoveset(Moves.TICKLE)
.startingLevel(1)
.moveset([ Moves.SPLASH, Moves.CLOSE_COMBAT ])
.ability(Abilities.DEFIANT);
});
it("lower atk and def by 1 via tickle, then increase atk by 4 via defiant", async () => {
await game.classicMode.startBattle([ Species.FLYGON ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnInitPhase);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3);
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1);
});
it("lowering your own stats should not trigger defiant", async () => {
game.override.enemyMoveset(Moves.SPLASH);
await game.classicMode.startBattle([ Species.FLYGON ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.CLOSE_COMBAT);
await game.phaseInterceptor.to(TurnInitPhase);
expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
});
it("white herb should remove only the negative effects", async () => {
game.override.startingHeldItems([{ name: "WHITE_HERB" }]);
await game.classicMode.startBattle([ Species.FLYGON ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to(TurnInitPhase);
expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3);
});
});

View File

@ -1,8 +1,9 @@
import { BattlerIndex } from "#app/battle";
import { StatusEffect } from "#app/data/status-effect";
import { toDmgValue } from "#app/utils";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { StatusEffect } from "#app/data/status-effect";
import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -222,4 +223,17 @@ describe("Abilities - Disguise", () => {
expect(mimikyu.formIndex).toBe(bustedForm);
expect(mimikyu.hp).toBe(maxHp - disguiseDamage);
});
it("doesn't trigger if user is behind a substitute", async () => {
game.override
.enemyMoveset(Moves.SUBSTITUTE)
.moveset(Moves.POWER_TRIP);
await game.classicMode.startBattle();
game.move.select(Moves.POWER_TRIP);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(disguisedForm);
});
});

View File

@ -1,13 +1,14 @@
import { BattlerTagType } from "#enums/battler-tag-type";
import { StatusEffect } from "#enums/status-effect";
import { BattlerIndex } from "#app/battle";
import Pokemon from "#app/field/pokemon";
import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { Stat } from "#enums/stat";
describe("Abilities - Gulp Missile", () => {
let phaserGame: Phaser.Game;
@ -40,8 +41,9 @@ describe("Abilities - Gulp Missile", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.disableCrits()
.battleType("single")
.moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH ])
.moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH, Moves.SUBSTITUTE ])
.enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH)
@ -234,6 +236,25 @@ describe("Abilities - Gulp Missile", () => {
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1);
});
it("doesn't trigger if user is behind a substitute", async () => {
game.override
.enemyAbility(Abilities.STURDY)
.enemyMoveset([ Moves.SPLASH, Moves.POWER_TRIP ]);
await game.classicMode.startBattle([ Species.CRAMORANT ]);
game.move.select(Moves.SURF);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
game.move.select(Moves.SUBSTITUTE);
await game.forceEnemyMove(Moves.POWER_TRIP);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM);
});
it("cannot be suppressed", async () => {
game.override.enemyMoveset(Moves.GASTRO_ACID);

View File

@ -1,3 +1,4 @@
import { BattlerIndex } from "#app/battle";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
@ -36,7 +37,7 @@ describe("Abilities - Ice Face", () => {
});
it("takes no damage from physical move and transforms to Noice", async () => {
await game.startBattle([ Species.HITMONLEE ]);
await game.classicMode.startBattle([ Species.HITMONLEE ]);
game.move.select(Moves.TACKLE);
@ -52,7 +53,7 @@ describe("Abilities - Ice Face", () => {
it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => {
game.override.moveset([ Moves.SURGING_STRIKES ]);
game.override.enemyLevel(1);
await game.startBattle([ Species.HITMONLEE ]);
await game.classicMode.startBattle([ Species.HITMONLEE ]);
game.move.select(Moves.SURGING_STRIKES);
@ -78,7 +79,7 @@ describe("Abilities - Ice Face", () => {
});
it("takes damage from special moves", async () => {
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.ICE_BEAM);
@ -92,7 +93,7 @@ describe("Abilities - Ice Face", () => {
});
it("takes effects from status moves", async () => {
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.TOXIC_THREAD);
@ -108,7 +109,7 @@ describe("Abilities - Ice Face", () => {
game.override.moveset([ Moves.QUICK_ATTACK ]);
game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]);
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.QUICK_ATTACK);
@ -130,7 +131,7 @@ describe("Abilities - Ice Face", () => {
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
game.override.moveset([ Moves.SNOWSCAPE ]);
await game.startBattle([ Species.EISCUE, Species.NINJASK ]);
await game.classicMode.startBattle([ Species.EISCUE, Species.NINJASK ]);
game.move.select(Moves.SNOWSCAPE);
@ -157,7 +158,7 @@ describe("Abilities - Ice Face", () => {
game.override.enemySpecies(Species.SHUCKLE);
game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]);
await game.startBattle([ Species.EISCUE ]);
await game.classicMode.startBattle([ Species.EISCUE ]);
game.move.select(Moves.HAIL);
const eiscue = game.scene.getPlayerPokemon()!;
@ -176,7 +177,7 @@ describe("Abilities - Ice Face", () => {
it("persists form change when switched out", async () => {
game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]);
await game.startBattle([ Species.EISCUE, Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.EISCUE, Species.MAGIKARP ]);
game.move.select(Moves.ICE_BEAM);
@ -205,7 +206,7 @@ describe("Abilities - Ice Face", () => {
[Species.EISCUE]: noiceForm,
});
await game.startBattle([ Species.EISCUE ]);
await game.classicMode.startBattle([ Species.EISCUE ]);
const eiscue = game.scene.getPlayerPokemon()!;
@ -222,10 +223,23 @@ describe("Abilities - Ice Face", () => {
expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined);
});
it("doesn't trigger if user is behind a substitute", async () => {
game.override
.enemyMoveset(Moves.SUBSTITUTE)
.moveset(Moves.POWER_TRIP);
await game.classicMode.startBattle();
game.move.select(Moves.POWER_TRIP);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.formIndex).toBe(icefaceForm);
});
it("cannot be suppressed", async () => {
game.override.moveset([ Moves.GASTRO_ACID ]);
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.GASTRO_ACID);
@ -241,7 +255,7 @@ describe("Abilities - Ice Face", () => {
it("cannot be swapped with another ability", async () => {
game.override.moveset([ Moves.SKILL_SWAP ]);
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.SKILL_SWAP);
@ -257,7 +271,7 @@ describe("Abilities - Ice Face", () => {
it("cannot be copied", async () => {
game.override.ability(Abilities.TRACE);
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
game.move.select(Moves.SIMPLE_BEAM);

View File

@ -9,6 +9,7 @@ import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { allMoves } from "#app/data/move";
describe("Abilities - Sheer Force", () => {
@ -174,5 +175,31 @@ describe("Abilities - Sheer Force", () => {
}, 20000);
it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => {
const moveToUse = Moves.CRUNCH;
game.override.enemyAbility(Abilities.COLOR_CHANGE)
.ability(Abilities.COLOR_CHANGE)
.moveset(moveToUse)
.enemyMoveset(moveToUse);
await game.classicMode.startBattle([
Species.PIDGEOT
]);
const pidgeot = game.scene.getParty()[0];
const onix = game.scene.getEnemyParty()[0];
pidgeot.stats[Stat.DEF] = 10000;
onix.stats[Stat.DEF] = 10000;
game.move.select(moveToUse);
await game.toNextTurn();
// Check that both Pokemon's Color Change activated
const expectedTypes = [ allMoves[moveToUse].type ];
expect(pidgeot.getTypes()).toStrictEqual(expectedTypes);
expect(onix.getTypes()).toStrictEqual(expectedTypes);
});
//TODO King's Rock Interaction Unit Test
});

View File

@ -71,4 +71,23 @@ describe("Abilities - Volt Absorb", () => {
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
});
it("regardless of accuracy should not trigger on pokemon in semi invulnerable state", async () => {
game.override.moveset(Moves.THUNDERBOLT);
game.override.enemyMoveset(Moves.DIVE);
game.override.enemySpecies(Species.MAGIKARP);
game.override.enemyAbility(Abilities.VOLT_ABSORB);
await game.classicMode.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.move.select(Moves.THUNDERBOLT);
enemyPokemon.hp = enemyPokemon.hp - 1;
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("MoveEffectPhase");
await game.move.forceMiss();
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
});
});

View File

@ -55,7 +55,7 @@ describe("Egg Generation Tests", () => {
let gachaSpeciesCount = 0;
for (let i = 0; i < EGG_HATCH_COUNT; i++) {
const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId;
const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon(scene).species.speciesId;
if (result === expectedSpecies) {
gachaSpeciesCount++;
}
@ -82,7 +82,7 @@ describe("Egg Generation Tests", () => {
});
it("should return an rare tier egg", () => {
const scene = game.scene;
const expectedTier = EggTier.GREAT;
const expectedTier = EggTier.RARE;
const result = new Egg({ scene, tier: expectedTier }).tier;
@ -90,7 +90,7 @@ describe("Egg Generation Tests", () => {
});
it("should return an epic tier egg", () => {
const scene = game.scene;
const expectedTier = EggTier.ULTRA;
const expectedTier = EggTier.EPIC;
const result = new Egg({ scene, tier: expectedTier }).tier;
@ -98,7 +98,7 @@ describe("Egg Generation Tests", () => {
});
it("should return an legendary tier egg", () => {
const scene = game.scene;
const expectedTier = EggTier.MASTER;
const expectedTier = EggTier.LEGENDARY;
const result = new Egg({ scene, tier: expectedTier }).tier;
@ -200,7 +200,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene;
const expectedEggTier = EggTier.COMMON;
const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).tier;
const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).tier;
expect(result).toBe(expectedEggTier);
});
@ -208,7 +208,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene;
const expectedHatchWaves = 10;
const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).hatchWaves;
const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).hatchWaves;
expect(result).toBe(expectedHatchWaves);
});
@ -229,7 +229,7 @@ describe("Egg Generation Tests", () => {
const result = new EggData(legacyEgg).toEgg();
expect(result.tier).toBe(EggTier.GREAT);
expect(result.tier).toBe(EggTier.RARE);
expect(result.id).toBe(legacyEgg.id);
expect(result.timestamp).toBe(legacyEgg.timestamp);
expect(result.hatchWaves).toBe(legacyEgg.hatchWaves);
@ -241,9 +241,9 @@ describe("Egg Generation Tests", () => {
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.COMMON });
expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1);
expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1);
expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 1);
expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1);
expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1);
expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 1);
});
it("should increase legendary egg pity by two", () => {
const scene = game.scene;
@ -251,9 +251,9 @@ describe("Egg Generation Tests", () => {
new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true, tier: EggTier.COMMON });
expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1);
expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1);
expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 2);
expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1);
expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1);
expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 2);
});
it("should not increase manaphy egg count if bulbasaurs are pulled", () => {
const scene = game.scene;
@ -277,7 +277,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene;
const startingRareEggsPulled = scene.gameData.gameStats.rareEggsPulled;
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.GREAT });
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.RARE });
expect(scene.gameData.gameStats.rareEggsPulled).toBe(startingRareEggsPulled + 1);
});
@ -285,7 +285,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene;
const startingEpicEggsPulled = scene.gameData.gameStats.epicEggsPulled;
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.ULTRA });
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.EPIC });
expect(scene.gameData.gameStats.epicEggsPulled).toBe(startingEpicEggsPulled + 1);
});
@ -293,7 +293,7 @@ describe("Egg Generation Tests", () => {
const scene = game.scene;
const startingLegendaryEggsPulled = scene.gameData.gameStats.legendaryEggsPulled;
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.MASTER });
new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.LEGENDARY });
expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1);
});
@ -301,8 +301,8 @@ describe("Egg Generation Tests", () => {
vi.spyOn(Utils, "randInt").mockReturnValue(1);
const scene = game.scene;
const expectedTier1 = EggTier.MASTER;
const expectedTier2 = EggTier.ULTRA;
const expectedTier1 = EggTier.LEGENDARY;
const expectedTier2 = EggTier.EPIC;
const result1 = new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true }).tier;
const result2 = new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true }).tier;

View File

@ -1,7 +1,4 @@
import { StatusEffect } from "#app/data/status-effect";
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
import { MessagePhase } from "#app/phases/message-phase";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import i18next from "#app/plugins/i18n";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
@ -10,6 +7,7 @@ import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const TIMEOUT = 20 * 1000;
describe("Items - Toxic orb", () => {
let phaserGame: Phaser.Game;
@ -27,40 +25,36 @@ describe("Items - Toxic orb", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.GROWTH;
const oppMoveToUse = Moves.TACKLE;
game.override.battleType("single");
game.override.enemySpecies(Species.RATTATA);
game.override.ability(Abilities.INSOMNIA);
game.override.enemyAbility(Abilities.INSOMNIA);
game.override.startingLevel(2000);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset([ oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse ]);
game.override.startingHeldItems([{
game.override
.battleType("single")
.enemySpecies(Species.RATTATA)
.ability(Abilities.BALL_FETCH)
.enemyAbility(Abilities.BALL_FETCH)
.moveset([ Moves.SPLASH ])
.enemyMoveset(Moves.SPLASH)
.startingHeldItems([{
name: "TOXIC_ORB",
}]);
vi.spyOn(i18next, "t");
});
it("TOXIC ORB", async () => {
const moveToUse = Moves.GROWTH;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB");
it("badly poisons the holder", async () => {
await game.classicMode.startBattle([ Species.MIGHTYENA ]);
game.move.select(moveToUse);
const player = game.scene.getPlayerField()[0];
// will run the 13 phase from enemyCommandPhase to TurnEndPhase
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
// Toxic orb should trigger here
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run("MessagePhase");
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything());
await game.phaseInterceptor.run(MessagePhase);
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.activation", expect.anything());
expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC);
}, 20000);
await game.toNextTurn();
expect(player.status?.effect).toBe(StatusEffect.TOXIC);
// Damage should not have ticked yet.
expect(player.status?.turnCount).toBe(0);
}, TIMEOUT);
});

View File

@ -111,7 +111,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (defender.scene.arena.getTagOnSide(ArenaTagType.AURORA_VEIL, side)) {
defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, move.category, defender.scene.currentBattle.double, multiplierHolder);
defender.scene.arena.applyTagsForSide(ArenaTagType.AURORA_VEIL, side, false, move.category, multiplierHolder);
}
return move.power * multiplierHolder.value;

View File

@ -139,4 +139,58 @@ describe("Moves - Dragon Tail", () => {
expect(enemy.isFullHp()).toBe(false);
});
it("should force a switch upon fainting an opponent normally", async () => {
game.override.startingWave(5)
.startingLevel(1000); // To make sure Dragon Tail KO's the opponent
await game.classicMode.startBattle([ Species.DRATINI ]);
game.move.select(Moves.DRAGON_TAIL);
await game.toNextTurn();
// Make sure the enemy switched to a healthy Pokemon
const enemy = game.scene.getEnemyPokemon()!;
expect(enemy).toBeDefined();
expect(enemy.isFullHp()).toBe(true);
// Make sure the enemy has a fainted Pokemon in their party and not on the field
const faintedEnemy = game.scene.getEnemyParty().find(p => !p.isAllowedInBattle());
expect(faintedEnemy).toBeDefined();
expect(game.scene.getEnemyField().length).toBe(1);
});
it("should not cause a softlock when activating an opponent trainer's reviver seed", async () => {
game.override.startingWave(5)
.enemyHeldItems([{ name: "REVIVER_SEED" }])
.startingLevel(1000); // To make sure Dragon Tail KO's the opponent
await game.classicMode.startBattle([ Species.DRATINI ]);
game.move.select(Moves.DRAGON_TAIL);
await game.toNextTurn();
// Make sure the enemy field is not empty and has a revived Pokemon
const enemy = game.scene.getEnemyPokemon()!;
expect(enemy).toBeDefined();
expect(enemy.hp).toBe(Math.floor(enemy.getMaxHp() / 2));
expect(game.scene.getEnemyField().length).toBe(1);
});
it("should not cause a softlock when activating a player's reviver seed", async () => {
game.override.startingHeldItems([{ name: "REVIVER_SEED" }])
.enemyMoveset(Moves.DRAGON_TAIL)
.enemyLevel(1000); // To make sure Dragon Tail KO's the player
await game.classicMode.startBattle([ Species.DRATINI, Species.BULBASAUR ]);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
// Make sure the player's field is not empty and has a revived Pokemon
const dratini = game.scene.getPlayerPokemon()!;
expect(dratini).toBeDefined();
expect(dratini.hp).toBe(Math.floor(dratini.getMaxHp() / 2));
expect(game.scene.getPlayerField().length).toBe(1);
});
});

View File

@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (defender.scene.arena.getTagOnSide(ArenaTagType.LIGHT_SCREEN, side)) {
defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, move.category, defender.scene.currentBattle.double, multiplierHolder);
defender.scene.arena.applyTagsForSide(ArenaTagType.LIGHT_SCREEN, side, false, move.category, multiplierHolder);
}
return move.power * multiplierHolder.value;

View File

@ -1,6 +1,7 @@
import { Moves } from "#app/enums/moves";
import { Stat } from "#app/enums/stat";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
@ -22,13 +23,15 @@ describe("Moves - Obstruct", () => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.enemySpecies(Species.MAGIKARP)
.enemyMoveset(Moves.TACKLE)
.enemyAbility(Abilities.BALL_FETCH)
.ability(Abilities.BALL_FETCH)
.moveset([ Moves.OBSTRUCT ]);
.moveset([ Moves.OBSTRUCT ])
.starterSpecies(Species.FEEBAS);
});
it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT);
@ -42,7 +45,6 @@ describe("Moves - Obstruct", () => {
});
it("bypasses accuracy checks when applying protection and defense reduction", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH));
await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT);
@ -59,7 +61,7 @@ describe("Moves - Obstruct", () => {
);
it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN));
game.override.enemyMoveset(Moves.WATER_GUN);
await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT);
@ -73,7 +75,7 @@ describe("Moves - Obstruct", () => {
});
it("doesn't protect from status moves", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.GROWL));
game.override.enemyMoveset(Moves.GROWL);
await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT);
@ -83,4 +85,14 @@ describe("Moves - Obstruct", () => {
expect(player.getStatStage(Stat.ATK)).toBe(-1);
});
it("doesn't reduce the stats of an opponent with Clear Body/etc", async () => {
game.override.enemyAbility(Abilities.CLEAR_BODY);
await game.classicMode.startBattle();
game.move.select(Moves.OBSTRUCT);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(0);
});
});

View File

@ -0,0 +1,113 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import { Moves } from "#enums/moves";
import { Stat } from "#enums/stat";
import { Species } from "#enums/species";
import { TurnEndPhase } from "#app/phases/turn-end-phase";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
describe("Moves - Power Trick", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH)
.enemySpecies(Species.MEW)
.enemyLevel(200)
.moveset([ Moves.POWER_TRICK ])
.ability(Abilities.BALL_FETCH);
});
it("swaps the user's ATK and DEF stats", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);
const player = game.scene.getPlayerPokemon()!;
const baseATK = player.getStat(Stat.ATK, false);
const baseDEF = player.getStat(Stat.DEF, false);
game.move.select(Moves.POWER_TRICK);
await game.phaseInterceptor.to(TurnEndPhase);
expect(player.getStat(Stat.ATK, false)).toBe(baseDEF);
expect(player.getStat(Stat.DEF, false)).toBe(baseATK);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeDefined();
});
it("resets initial ATK and DEF stat swap when used consecutively", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);
const player = game.scene.getPlayerPokemon()!;
const baseATK = player.getStat(Stat.ATK, false);
const baseDEF = player.getStat(Stat.DEF, false);
game.move.select(Moves.POWER_TRICK);
await game.phaseInterceptor.to(TurnEndPhase);
game.move.select(Moves.POWER_TRICK);
await game.phaseInterceptor.to(TurnEndPhase);
expect(player.getStat(Stat.ATK, false)).toBe(baseATK);
expect(player.getStat(Stat.DEF, false)).toBe(baseDEF);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined();
});
it("should pass effect when using BATON_PASS", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
await game.override.moveset([ Moves.POWER_TRICK, Moves.BATON_PASS ]);
const player = game.scene.getPlayerPokemon()!;
player.addTag(BattlerTagType.POWER_TRICK);
game.move.select(Moves.BATON_PASS);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to(TurnEndPhase);
const switchedPlayer = game.scene.getPlayerPokemon()!;
const baseATK = switchedPlayer.getStat(Stat.ATK);
const baseDEF = switchedPlayer.getStat(Stat.DEF);
expect(switchedPlayer.getStat(Stat.ATK, false)).toBe(baseDEF);
expect(switchedPlayer.getStat(Stat.DEF, false)).toBe(baseATK);
expect(switchedPlayer.getTag(BattlerTagType.POWER_TRICK)).toBeDefined();
});
it("should remove effect after using Transform", async () => {
await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]);
await game.override.moveset([ Moves.POWER_TRICK, Moves.TRANSFORM ]);
const player = game.scene.getPlayerPokemon()!;
player.addTag(BattlerTagType.POWER_TRICK);
game.move.select(Moves.TRANSFORM);
await game.phaseInterceptor.to(TurnEndPhase);
const enemy = game.scene.getEnemyPokemon()!;
const baseATK = enemy.getStat(Stat.ATK);
const baseDEF = enemy.getStat(Stat.DEF);
expect(player.getStat(Stat.ATK, false)).toBe(baseATK);
expect(player.getStat(Stat.DEF, false)).toBe(baseDEF);
expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined();
});
});

View File

@ -94,7 +94,7 @@ const getMockedMoveDamage = (defender: Pokemon, attacker: Pokemon, move: Move) =
const side = defender.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
if (defender.scene.arena.getTagOnSide(ArenaTagType.REFLECT, side)) {
defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, move.category, defender.scene.currentBattle.double, multiplierHolder);
defender.scene.arena.applyTagsForSide(ArenaTagType.REFLECT, side, false, move.category, multiplierHolder);
}
return move.power * multiplierHolder.value;

View File

@ -33,7 +33,7 @@ describe("Moves - Safeguard", () => {
.enemyLevel(5)
.starterSpecies(Species.DRATINI)
.moveset([ Moves.NUZZLE, Moves.SPORE, Moves.YAWN, Moves.SPLASH ])
.ability(Abilities.BALL_FETCH);
.ability(Abilities.UNNERVE); // Stop wild Pokemon from potentially eating Lum Berry
});
it("protects from damaging moves with additional effects", async () => {

View File

@ -0,0 +1,53 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { MoveResult } from "#app/field/pokemon";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Sketch", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.SHUCKLE)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => {
game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]);
await game.classicMode.startBattle([ Species.REGIELEKI ]);
const playerPokemon = game.scene.getPlayerPokemon();
game.move.select(Moves.SKETCH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL);
const moveSlot0 = playerPokemon?.getMoveset()[0];
expect(moveSlot0?.moveId).toBe(Moves.SKETCH);
expect(moveSlot0?.getPpRatio()).toBe(0);
await game.toNextTurn();
game.move.select(Moves.SKETCH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
// Can't verify if the player Pokemon's moveset was successfully changed because of overrides.
});
});

View File

@ -1,4 +1,3 @@
import { allMoves } from "#app/data/move";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities";
@ -7,7 +6,7 @@ import { Stat } from "#enums/stat";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { BattlerIndex } from "#app/battle";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - SYRUP BOMB", () => {
let phaserGame: Phaser.Game;
@ -26,20 +25,21 @@ describe("Moves - SYRUP BOMB", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.starterSpecies(Species.MAGIKARP)
.battleType("single")
.enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.BALL_FETCH)
.ability(Abilities.BALL_FETCH)
.startingLevel(30)
.enemyLevel(100)
.moveset([ Moves.SYRUP_BOMB, Moves.SPLASH ])
.enemyMoveset(Moves.SPLASH);
vi.spyOn(allMoves[Moves.SYRUP_BOMB], "accuracy", "get").mockReturnValue(100);
});
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move)
it("decreases the target Pokemon's speed stat once per turn for 3 turns",
async () => {
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
const targetPokemon = game.scene.getEnemyPokemon()!;
expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0);
@ -66,7 +66,7 @@ describe("Moves - SYRUP BOMB", () => {
it("does not affect Pokemon with the ability Bulletproof",
async () => {
game.override.enemyAbility(Abilities.BULLETPROOF);
await game.startBattle([ Species.MAGIKARP ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
const targetPokemon = game.scene.getEnemyPokemon()!;
@ -79,4 +79,18 @@ describe("Moves - SYRUP BOMB", () => {
expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0);
}
);
it("stops lowering the target's speed if the user leaves the field", async () => {
await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]);
game.move.select(Moves.SYRUP_BOMB);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.move.forceHit();
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPD)).toBe(-1);
});
});

View File

@ -0,0 +1,124 @@
import { BattlerTagType } from "#enums/battler-tag-type";
import { allMoves } from "#app/data/move";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { MoveResult } from "#app/field/pokemon";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
describe("Moves - Telekinesis", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.TELEKINESIS, Moves.TACKLE, Moves.MUD_SHOT, Moves.SMACK_DOWN ])
.battleType("single")
.enemySpecies(Species.SNORLAX)
.enemyLevel(60)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset([ Moves.SPLASH ]);
});
it("Telekinesis makes the affected vulnerable to most attacking moves regardless of accuracy", async () => {
await game.classicMode.startBattle([ Species.MAGIKARP ]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
game.move.select(Moves.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
await game.toNextTurn();
vi.spyOn(allMoves[Moves.TACKLE], "accuracy", "get").mockReturnValue(0);
game.move.select(Moves.TACKLE);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.isFullHp()).toBe(false);
});
it("Telekinesis makes the affected airborne and immune to most Ground-moves", async () => {
await game.classicMode.startBattle([ Species.MAGIKARP ]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
game.move.select(Moves.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
await game.toNextTurn();
vi.spyOn(allMoves[Moves.MUD_SHOT], "accuracy", "get").mockReturnValue(100);
game.move.select(Moves.MUD_SHOT);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.isFullHp()).toBe(true);
});
it("Telekinesis can still affect Pokemon that have been transformed into invalid Pokemon", async () => {
game.override.enemyMoveset(Moves.TRANSFORM);
await game.classicMode.startBattle([ Species.DIGLETT ]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
game.move.select(Moves.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
expect(enemyOpponent.summonData.speciesForm?.speciesId).toBe(Species.DIGLETT);
});
it("Moves like Smack Down and 1000 Arrows remove all effects of Telekinesis from the target Pokemon", async () => {
await game.classicMode.startBattle([ Species.MAGIKARP ]);
const enemyOpponent = game.scene.getEnemyPokemon()!;
game.move.select(Moves.TELEKINESIS);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
await game.toNextTurn();
game.move.select(Moves.SMACK_DOWN);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined();
});
it("Ingrain will remove the floating effect of Telekinesis, but not the 100% hit", async () => {
game.override.enemyMoveset([ Moves.SPLASH, Moves.INGRAIN ]);
await game.classicMode.startBattle([ Species.MAGIKARP ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyOpponent = game.scene.getEnemyPokemon()!;
game.move.select(Moves.TELEKINESIS);
await game.forceEnemyMove(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined();
await game.toNextTurn();
vi.spyOn(allMoves[Moves.MUD_SHOT], "accuracy", "get").mockReturnValue(0);
game.move.select(Moves.MUD_SHOT);
await game.forceEnemyMove(Moves.INGRAIN);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.INGRAIN)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined();
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
});
});

View File

@ -85,13 +85,13 @@ describe("Moves - Thousand Arrows", () => {
const enemyPokemon = game.scene.getEnemyPokemon()!;
enemyPokemon.addTag(BattlerTagType.MAGNET_RISEN, undefined, Moves.MAGNET_RISE);
enemyPokemon.addTag(BattlerTagType.FLOATING, undefined, Moves.MAGNET_RISE);
game.move.select(Moves.THOUSAND_ARROWS);
await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.getTag(BattlerTagType.MAGNET_RISEN)).toBeUndefined();
expect(enemyPokemon.getTag(BattlerTagType.FLOATING)).toBeUndefined();
expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined();
expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp());
}

View File

@ -0,0 +1,136 @@
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
import { StatusEffect } from "#app/data/status-effect";
import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
const TIMEOUT = 20 * 1000;
describe("Moves - Toxic Spikes", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.startingWave(5)
.enemySpecies(Species.RATTATA)
.enemyAbility(Abilities.BALL_FETCH)
.ability(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH)
.moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]);
});
it("should not affect the opponent if they do not switch", async() => {
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
const enemy = game.scene.getEnemyField()[0];
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
game.doSwitchPokemon(1);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemy.hp).toBe(enemy.getMaxHp());
expect(enemy.status?.effect).toBeUndefined();
}, TIMEOUT);
it("should poison the opponent if they switch into 1 layer", async() => {
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.ROAR);
await game.phaseInterceptor.to("TurnEndPhase");
const enemy = game.scene.getEnemyField()[0];
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
expect(enemy.status?.effect).toBe(StatusEffect.POISON);
}, TIMEOUT);
it("should badly poison the opponent if they switch into 2 layers", async() => {
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.ROAR);
await game.phaseInterceptor.to("TurnEndPhase");
const enemy = game.scene.getEnemyField()[0];
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
expect(enemy.status?.effect).toBe(StatusEffect.TOXIC);
}, TIMEOUT);
it("should be removed if a grounded poison pokemon switches in", async() => {
game.override.enemySpecies(Species.GRIMER);
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.ROAR);
await game.phaseInterceptor.to("TurnEndPhase");
const enemy = game.scene.getEnemyField()[0];
expect(enemy.hp).toBe(enemy.getMaxHp());
expect(enemy.status?.effect).toBeUndefined();
expect(game.scene.arena.tags.length).toBe(0);
}, TIMEOUT);
it("shouldn't create multiple layers per use in doubles", async() => {
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag);
expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES);
expect(arenaTags.layers).toBe(1);
}, TIMEOUT);
it("should persist through reload", async() => {
game.override.startingWave(1);
const scene = game.scene;
const gameData = new GameData(scene);
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
game.move.select(Moves.TOXIC_SPIKES);
await game.phaseInterceptor.to("TurnEndPhase");
game.move.select(Moves.SPLASH);
await game.doKillOpponents();
await game.phaseInterceptor.to("BattleEndPhase");
await game.toNextWave();
const sessionData : SessionSaveData = gameData["getSessionSaveData"](game.scene);
localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true));
const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true));
gameData.loadSession(game.scene, 0, recoveredData);
expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags);
localStorage.removeItem("sessionTestData");
}, TIMEOUT);
});

View File

@ -96,4 +96,23 @@ describe("Moves - U-turn", () => {
expect(game.scene.getEnemyPokemon()!.battleData.abilityRevealed).toBe(true); // proxy for asserting ability activated
expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase");
}, 20000);
it("still forces a switch if u-turn KO's the opponent", async () => {
game.override.startingLevel(1000); // Ensure that U-Turn KO's the opponent
await game.classicMode.startBattle([
Species.RAICHU,
Species.SHUCKLE
]);
const enemy = game.scene.getEnemyPokemon()!;
// KO the opponent with U-Turn
game.move.select(Moves.U_TURN);
game.doSelectPartyPokemon(1);
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.isFainted()).toBe(true);
// Check that U-Turn forced a switch
expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase");
expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.SHUCKLE);
});
});

View File

@ -128,7 +128,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + 1).toBe(eggsAfter.length);
const eggTier = eggsAfter[eggsAfter.length - 1].tier;
expect(eggTier === EggTier.ULTRA || eggTier === EggTier.MASTER).toBeTruthy();
expect(eggTier === EggTier.EPIC || eggTier === EggTier.LEGENDARY).toBeTruthy();
});
});
@ -176,7 +176,7 @@ describe("A Trainer's Test - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + 1).toBe(eggsAfter.length);
const eggTier = eggsAfter[eggsAfter.length - 1].tier;
expect(eggTier).toBe(EggTier.GREAT);
expect(eggTier).toBe(EggTier.RARE);
});
it("should leave encounter without battle", async () => {

View File

@ -18,6 +18,7 @@ import { Moves } from "#enums/moves";
import { ShinyRateBoosterModifier } from "#app/modifier/modifier";
import { SelectModifierPhase } from "#app/phases/select-modifier-phase";
import i18next from "i18next";
import { Abilities } from "#enums/abilities";
const namespace = "mysteryEncounters/anOfferYouCantRefuse";
/** Gyarados for Indimidate */
@ -37,10 +38,11 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
beforeEach(async () => {
game = new GameManager(phaserGame);
scene = game.scene;
game.override.mysteryEncounterChance(100);
game.override.startingWave(defaultWave);
game.override.startingBiome(defaultBiome);
game.override.disableTrainerWaves();
game.override.mysteryEncounterChance(100)
.startingWave(defaultWave)
.startingBiome(defaultBiome)
.disableTrainerWaves()
.ability(Abilities.INTIMIDATE); // Extortion ability
const biomeMap = new Map<Biome, MysteryEncounterType[]>([
[ Biome.VOLCANO, [ MysteryEncounterType.MYSTERIOUS_CHALLENGERS ]],
@ -195,6 +197,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => {
});
it("should award EXP to a pokemon with a move in EXTORTION_MOVES", async () => {
game.override.ability(Abilities.SYNCHRONIZE); // Not an extortion ability, so we can test extortion move
await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, [ Species.ABRA ]);
const party = scene.getParty();
const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA)!;

View File

@ -176,10 +176,12 @@ describe("Berries Abound - Mystery Encounter", () => {
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
scene.getParty().forEach(pkm => {
vi.spyOn(pkm, "getStat").mockReturnValue(1); // for ease return for every stat
});
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
// Setting enemy's level arbitrarily high to outspeed
config.pokemonConfigs![0].dataSource!.level = 1000;
await runMysteryEncounterToEnd(game, 2, undefined, true);
@ -198,10 +200,12 @@ describe("Berries Abound - Mystery Encounter", () => {
const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText");
await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty);
scene.getParty().forEach(pkm => {
vi.spyOn(pkm, "getStat").mockReturnValue(1); // for ease return for every stat
});
const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId;
// Setting enemy's level arbitrarily high to outspeed
config.pokemonConfigs![0].dataSource!.level = 1000;
await runMysteryEncounterToEnd(game, 2, undefined, true);

View File

@ -155,7 +155,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
@ -213,7 +213,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);
@ -271,7 +271,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => {
expect(eggsAfter).toBeDefined();
expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length);
expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs);
expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs);
game.phaseInterceptor.superEndPhase();
await game.phaseInterceptor.to(PostMysteryEncounterPhase);

View File

@ -147,8 +147,8 @@ describe("The Pokemon Salesman - Mystery Encounter", () => {
expect(scene.getParty().length).toBe(initialPartySize + 1);
const newlyPurchasedPokemon = scene.getParty().find(p => p.name === pokemonName);
expect(newlyPurchasedPokemon).toBeDefined();
const newlyPurchasedPokemon = scene.getParty()[scene.getParty().length - 1];
expect(newlyPurchasedPokemon.name).toBe(pokemonName);
expect(newlyPurchasedPokemon!.moveset.length > 0).toBeTruthy();
});

View File

@ -86,7 +86,7 @@ export function waitUntil(truth) {
export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) {
const playerPokemon = scene.getPlayerField()[pokemonIndex];
const moveSet = playerPokemon.getMoveset();
const index = moveSet.findIndex((m) => m?.moveId === move);
const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp());
console.log(`Move position for ${Moves[move]} (=${move}):`, index);
return index;
}

View File

@ -53,6 +53,7 @@ import {
} from "#app/phases/mystery-encounter-phases";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { PartyExpPhase } from "#app/phases/party-exp-phase";
import { ExpPhase } from "#app/phases/exp-phase";
export interface PromptHandler {
phaseTarget?: string;
@ -61,7 +62,114 @@ export interface PromptHandler {
expireFn?: () => void;
awaitingActionInput?: boolean;
}
import { ExpPhase } from "#app/phases/exp-phase";
type PhaseClass =
| typeof LoginPhase
| typeof TitlePhase
| typeof SelectGenderPhase
| typeof EncounterPhase
| typeof NewBiomeEncounterPhase
| typeof SelectStarterPhase
| typeof PostSummonPhase
| typeof SummonPhase
| typeof ToggleDoublePositionPhase
| typeof CheckSwitchPhase
| typeof ShowAbilityPhase
| typeof MessagePhase
| typeof TurnInitPhase
| typeof CommandPhase
| typeof EnemyCommandPhase
| typeof TurnStartPhase
| typeof MovePhase
| typeof MoveEffectPhase
| typeof DamagePhase
| typeof FaintPhase
| typeof BerryPhase
| typeof TurnEndPhase
| typeof BattleEndPhase
| typeof EggLapsePhase
| typeof SelectModifierPhase
| typeof NextEncounterPhase
| typeof NewBattlePhase
| typeof VictoryPhase
| typeof LearnMovePhase
| typeof MoveEndPhase
| typeof StatStageChangePhase
| typeof ShinySparklePhase
| typeof SelectTargetPhase
| typeof UnavailablePhase
| typeof QuietFormChangePhase
| typeof SwitchPhase
| typeof SwitchSummonPhase
| typeof PartyHealPhase
| typeof EvolutionPhase
| typeof EndEvolutionPhase
| typeof LevelCapPhase
| typeof AttemptRunPhase
| typeof SelectBiomePhase
| typeof MysteryEncounterPhase
| typeof MysteryEncounterOptionSelectedPhase
| typeof MysteryEncounterBattlePhase
| typeof MysteryEncounterRewardsPhase
| typeof PostMysteryEncounterPhase
| typeof ModifierRewardPhase
| typeof PartyExpPhase
| typeof ExpPhase;
type PhaseString =
| "LoginPhase"
| "TitlePhase"
| "SelectGenderPhase"
| "EncounterPhase"
| "NewBiomeEncounterPhase"
| "SelectStarterPhase"
| "PostSummonPhase"
| "SummonPhase"
| "ToggleDoublePositionPhase"
| "CheckSwitchPhase"
| "ShowAbilityPhase"
| "MessagePhase"
| "TurnInitPhase"
| "CommandPhase"
| "EnemyCommandPhase"
| "TurnStartPhase"
| "MovePhase"
| "MoveEffectPhase"
| "DamagePhase"
| "FaintPhase"
| "BerryPhase"
| "TurnEndPhase"
| "BattleEndPhase"
| "EggLapsePhase"
| "SelectModifierPhase"
| "NextEncounterPhase"
| "NewBattlePhase"
| "VictoryPhase"
| "LearnMovePhase"
| "MoveEndPhase"
| "StatStageChangePhase"
| "ShinySparklePhase"
| "SelectTargetPhase"
| "UnavailablePhase"
| "QuietFormChangePhase"
| "SwitchPhase"
| "SwitchSummonPhase"
| "PartyHealPhase"
| "EvolutionPhase"
| "EndEvolutionPhase"
| "LevelCapPhase"
| "AttemptRunPhase"
| "SelectBiomePhase"
| "MysteryEncounterPhase"
| "MysteryEncounterOptionSelectedPhase"
| "MysteryEncounterBattlePhase"
| "MysteryEncounterRewardsPhase"
| "PostMysteryEncounterPhase"
| "ModifierRewardPhase"
| "PartyExpPhase"
| "ExpPhase";
type PhaseInterceptorPhase = PhaseClass | PhaseString;
export default class PhaseInterceptor {
public scene;
@ -172,7 +280,7 @@ export default class PhaseInterceptor {
* @param phaseFrom - The phase to start from.
* @returns The instance of the PhaseInterceptor.
*/
runFrom(phaseFrom) {
runFrom(phaseFrom: PhaseInterceptorPhase): PhaseInterceptor {
this.phaseFrom = phaseFrom;
return this;
}
@ -180,9 +288,10 @@ export default class PhaseInterceptor {
/**
* Method to transition to a target phase.
* @param phaseTo - The phase to transition to.
* @param runTarget - Whether or not to run the target phase.
* @returns A promise that resolves when the transition is complete.
*/
async to(phaseTo, runTarget: boolean = true): Promise<void> {
async to(phaseTo: PhaseInterceptorPhase, runTarget: boolean = true): Promise<void> {
return new Promise(async (resolve, reject) => {
ErrorInterceptor.getInstance().add(this);
if (this.phaseFrom) {
@ -219,7 +328,7 @@ export default class PhaseInterceptor {
* @param skipFn - Optional skip function.
* @returns A promise that resolves when the phase is run.
*/
run(phaseTarget, skipFn?): Promise<void> {
run(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => {
@ -253,7 +362,7 @@ export default class PhaseInterceptor {
});
}
whenAboutToRun(phaseTarget, skipFn?): Promise<void> {
whenAboutToRun(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise<void> {
const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name;
this.scene.moveAnimations = null; // Mandatory to avoid crash
return new Promise(async (resolve, reject) => {
@ -311,7 +420,7 @@ export default class PhaseInterceptor {
* Method to start a phase and log it.
* @param phase - The phase to start.
*/
startPhase(phase) {
startPhase(phase: PhaseClass) {
this.log.push(phase.name);
const instance = this.scene.getCurrentPhase();
this.onHold.push({
@ -340,9 +449,10 @@ export default class PhaseInterceptor {
/**
* m2m to set mode.
* @param phase - The phase to start.
* @param mode - The {@linkcode Mode} to set.
* @param args - Additional arguments to pass to the original method.
*/
setMode(mode: Mode, ...args: any[]): Promise<void> {
setMode(mode: Mode, ...args: unknown[]): Promise<void> {
const currentPhase = this.scene.getCurrentPhase();
const instance = this.scene.ui;
console.log("setMode", `${Mode[mode]} (=${mode})`, args);

View File

@ -165,6 +165,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
if (this.config.delay) {
this.blockInput = true;
this.optionSelectText.setAlpha(0.5);
this.cursorObj?.setAlpha(0.8);
this.scene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput());
}
@ -256,6 +257,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler {
this.blockInput = false;
this.optionSelectText.setAlpha(1);
this.cursorObj?.setAlpha(1);
}
getOptionsWithScroll(): OptionSelectItem[] {

View File

@ -40,7 +40,9 @@ export default class AchvsUiHandler extends MessageUiHandler {
private iconsBg: Phaser.GameObjects.NineSlice;
private icons: Phaser.GameObjects.Sprite[];
private titleBg: Phaser.GameObjects.NineSlice;
private titleText: Phaser.GameObjects.Text;
private scoreContainer: Phaser.GameObjects.Container;
private scoreText: Phaser.GameObjects.Text;
private unlockText: Phaser.GameObjects.Text;
@ -114,29 +116,31 @@ export default class AchvsUiHandler extends MessageUiHandler {
const titleBg = addWindow(this.scene, 0, this.headerBg.height + this.iconsBg.height, 174, 24);
titleBg.setOrigin(0, 0);
this.titleBg = titleBg;
this.titleText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW);
const textSize = languageSettings[i18next.language]?.TextSize ?? this.titleText.style.fontSize;
this.titleText.setFontSize(textSize);
this.titleText.setOrigin(0, 0);
const titleBgCenterX = titleBg.x + titleBg.width / 2;
const titleBgCenterY = titleBg.y + titleBg.height / 2;
this.titleText.setOrigin(0.5, 0.5);
this.titleText.setPosition(titleBgCenterX, titleBgCenterY);
const scoreBg = addWindow(this.scene, titleBg.x + titleBg.width, titleBg.y, 46, 24);
this.scoreContainer = this.scene.add.container(titleBg.x + titleBg.width, titleBg.y);
const scoreBg = addWindow(this.scene, 0, 0, 46, 24);
scoreBg.setOrigin(0, 0);
this.scoreContainer.add(scoreBg);
this.scoreText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW);
this.scoreText.setOrigin(0, 0);
this.scoreText.setPositionRelative(scoreBg, 8, 4);
this.scoreText = addTextObject(this.scene, scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW);
this.scoreText.setOrigin(0.5, 0.5);
this.scoreContainer.add(this.scoreText);
const unlockBg = addWindow(this.scene, scoreBg.x + scoreBg.width, scoreBg.y, 98, 24);
const unlockBg = addWindow(this.scene, this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24);
unlockBg.setOrigin(0, 0);
this.unlockText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW);
this.unlockText.setOrigin(0, 0);
this.unlockText.setPositionRelative(unlockBg, 8, 4);
this.unlockText.setOrigin(0.5, 0.5);
this.unlockText.setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2);
const descriptionBg = addWindow(this.scene, 0, titleBg.y + titleBg.height, (this.scene.game.canvas.width / 6) - 2, 42);
descriptionBg.setOrigin(0, 0);
@ -157,8 +161,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
this.mainContainer.add(this.iconsContainer);
this.mainContainer.add(titleBg);
this.mainContainer.add(this.titleText);
this.mainContainer.add(scoreBg);
this.mainContainer.add(this.scoreText);
this.mainContainer.add(this.scoreContainer);
this.mainContainer.add(unlockBg);
this.mainContainer.add(this.unlockText);
this.mainContainer.add(descriptionBg);
@ -167,8 +170,6 @@ export default class AchvsUiHandler extends MessageUiHandler {
ui.add(this.mainContainer);
this.currentPage = Page.ACHIEVEMENTS;
this.setCursor(0);
this.setScrollCursor(0);
this.mainContainer.setVisible(false);
}
@ -316,9 +317,19 @@ export default class AchvsUiHandler extends MessageUiHandler {
if (update || pageChange) {
switch (this.currentPage) {
case Page.ACHIEVEMENTS:
if (pageChange) {
this.titleBg.width = 174;
this.titleText.x = this.titleBg.width / 2;
this.scoreContainer.setVisible(true);
}
this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]);
break;
case Page.VOUCHERS:
if (pageChange) {
this.titleBg.width = 220;
this.titleText.x = this.titleBg.width / 2;
this.scoreContainer.setVisible(false);
}
this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]);
break;
}
@ -442,6 +453,7 @@ export default class AchvsUiHandler extends MessageUiHandler {
this.currentPage = Page.ACHIEVEMENTS;
this.mainContainer.setVisible(false);
this.setScrollCursor(0);
this.setCursor(0, true);
this.eraseCursor();
}

View File

@ -593,7 +593,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
};
const updatePokemonHp = () => {
let duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0;
let duration = !instant ? Phaser.Math.Clamp(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0;
const speed = (this.scene as BattleScene).hpBarSpeed;
if (speed) {
duration = speed >= 3 ? 0 : duration / Math.pow(2, speed);

View File

@ -76,7 +76,8 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
}
}
],
delay: args.length >= 6 && args[5] !== null ? args[5] as integer : 0
delay: args.length >= 6 && args[5] !== null ? args[5] as number : 0,
noCancel: args.length >= 7 && args[6] !== null ? args[6] as boolean : false,
};
super.show([ config ]);
@ -96,7 +97,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler {
}
processInput(button: Button): boolean {
if (button === Button.CANCEL && this.blockInput) {
if (button === Button.CANCEL && this.blockInput && !this.config?.noCancel) {
this.unblockInput();
}

Some files were not shown because too many files have changed in this diff Show More