Merge changes and resolve conflicts

This commit is contained in:
Christopher Schmidt 2024-10-30 21:02:51 -04:00
commit 77c9fb0daa
55 changed files with 2328 additions and 635 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "pokemon-rogue-battle",
"version": "1.1.0",
"version": "1.1.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "pokemon-rogue-battle",
"version": "1.1.0",
"version": "1.1.6",
"hasInstallScript": true,
"dependencies": {
"@material/material-color-utilities": "^0.2.7",

View File

@ -1,7 +1,7 @@
{
"name": "pokemon-rogue-battle",
"private": true,
"version": "1.1.0",
"version": "1.1.6",
"type": "module",
"scripts": {
"start": "vite",

View File

@ -3353,12 +3353,12 @@
"rotated": false,
"trimmed": true,
"sourceSize": {
"w": 24,
"h": 23
"w": 32,
"h": 32
},
"spriteSourceSize": {
"x": 1,
"y": 1,
"x": 5,
"y": 5,
"w": 22,
"h": 21
},
@ -8436,6 +8436,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:875c6d67e72590dfc6d319101aa31cfa:dd2bb865ecbc5ac7b975ddf70b993334:110e074689c9edd2c54833ce2e4d9270$"
"smartupdate": "$TexturePacker:SmartUpdate:d91a46c431ace3f09f5ca68916a2171e:1e84369d9a13e1416fa58028d629d116:110e074689c9edd2c54833ce2e4d9270$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 390 B

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658-ash.png",
"format": "RGBA8888",
"size": {
"w": 79,
"h": 79
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 79,
"h": 74
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 79,
"h": 74
},
"frame": {
"x": 0,
"y": 0,
"w": 79,
"h": 74
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:936f62fa49ba4d6e402bb2e2eaf2afd0:ed00ba047a44b4bf1309bc147dd000e3:bfbf521a5c7bd80bcd95a96d9789c0dd$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 79, "h": 74 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 79, "h": 74 },
"sourceSize": { "w": 79, "h": 74 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 79, "h": 74 },
"scale": "1"
}
}

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658.png",
"format": "RGBA8888",
"size": {
"w": 75,
"h": 75
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 75,
"h": 65
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 65
},
"frame": {
"x": 0,
"y": 0,
"w": 75,
"h": 65
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:e0b10df331bd4ce6760edab61dee144b:061561c45beff89a92bf0158d065204f:5affcab976148657d36bf4ff3410f92d$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 85, "h": 67 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 85, "h": 67 },
"sourceSize": { "w": 85, "h": 67 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 85, "h": 67 },
"scale": "1"
}
}

View File

@ -5,8 +5,7 @@
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 63 },
"sourceSize": { "w": 64, "h": 63 },
"duration": 100
"sourceSize": { "w": 64, "h": 63 }
}
],
"meta": {

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658-ash.png",
"format": "RGBA8888",
"size": {
"w": 73,
"h": 73
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 73,
"h": 69
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 73,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 73,
"h": 69
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:4f38801bb3afeda5faff04bdcf6a666f:0c78ce2715e7510bf55da0a92b42661c:bfbf521a5c7bd80bcd95a96d9789c0dd$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 73, "h": 73 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 73, "h": 73 },
"sourceSize": { "w": 73, "h": 73 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 73, "h": 73 },
"scale": "1"
}
}

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658.png",
"format": "RGBA8888",
"size": {
"w": 77,
"h": 77
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 77,
"h": 65
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 77,
"h": 65
},
"frame": {
"x": 0,
"y": 0,
"w": 77,
"h": 65
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:acdb9925f3f23b947504eec7cc28c92d:1a13d9d418f6c107bb9e5d621d9154bb:5affcab976148657d36bf4ff3410f92d$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 77, "h": 77 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 77, "h": 77 },
"sourceSize": { "w": 77, "h": 77 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 77, "h": 77 },
"scale": "1"
}
}

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "688.png",
"format": "RGBA8888",
"size": {
"w": 52,
"h": 52
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 41,
"h": 52
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 41,
"h": 52
},
"frame": {
"x": 0,
"y": 0,
"w": 41,
"h": 52
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:ea462f2b1b46327e3b8fcb7ec5e44f08:2d2598cc03dec73182dbea237ad83b34:176060351d0044923af938ba7932a6ef$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 51, "h": 65 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 51, "h": 65 },
"sourceSize": { "w": 51, "h": 65 }
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-dev",
"image": "688.png",
"format": "I8",
"size": { "w": 51, "h": 65 },
"scale": "1"
}
}

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658-ash.png",
"format": "RGBA8888",
"size": {
"w": 73,
"h": 73
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 73,
"h": 69
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 73,
"h": 69
},
"frame": {
"x": 0,
"y": 0,
"w": 73,
"h": 69
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:d474b821316a87dfe09b397bdc2db5ef:497de0c2ec59ceba163e870b3226c76c:bfbf521a5c7bd80bcd95a96d9789c0dd$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 73, "h": 73 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 73, "h": 73 },
"sourceSize": { "w": 73, "h": 73 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 73, "h": 73 },
"scale": "1"
}
}

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658.png",
"format": "RGBA8888",
"size": {
"w": 77,
"h": 77
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 77,
"h": 65
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 77,
"h": 65
},
"frame": {
"x": 0,
"y": 0,
"w": 77,
"h": 65
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:5891f87a78022cde3402e7d9714cc7bf:756360084290e39c139e3fef91c81759:5affcab976148657d36bf4ff3410f92d$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 77, "h": 77 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 77, "h": 77 },
"sourceSize": { "w": 77, "h": 77 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 77, "h": 77 },
"scale": "1"
}
}

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "688.png",
"format": "RGBA8888",
"size": {
"w": 52,
"h": 52
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 41,
"h": 52
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 41,
"h": 52
},
"frame": {
"x": 0,
"y": 0,
"w": 41,
"h": 52
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:0261b6c9242bba728fcfbfc515875b27:de0d9ddceed9311b33ae50ba86e969d1:176060351d0044923af938ba7932a6ef$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 51, "h": 65 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 51, "h": 65 },
"sourceSize": { "w": 51, "h": 65 }
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-dev",
"image": "688.png",
"format": "I8",
"size": { "w": 51, "h": 65 },
"scale": "1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658-ash.png",
"format": "RGBA8888",
"size": {
"w": 79,
"h": 79
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 79,
"h": 74
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 79,
"h": 74
},
"frame": {
"x": 0,
"y": 0,
"w": 79,
"h": 74
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:3dd081ba5490f090a73de8423aac2f6b:f088fafaea755476f2abf488e7340cab:bfbf521a5c7bd80bcd95a96d9789c0dd$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 79, "h": 74 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 79, "h": 74 },
"sourceSize": { "w": 79, "h": 74 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 79, "h": 74 },
"scale": "1"
}
}

View File

@ -1,41 +1,19 @@
{
"textures": [
{
"image": "658.png",
"format": "RGBA8888",
"size": {
"w": 75,
"h": 75
},
"scale": 1,
"frames": [
{
"filename": "0001.png",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 75,
"h": 65
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 75,
"h": 65
},
"frame": {
"x": 0,
"y": 0,
"w": 75,
"h": 65
}
}
]
}
],
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:be07c062265a19e890f1e2d2d1b5527d:ad4583a5a0498c496e9a93574c55ee03:5affcab976148657d36bf4ff3410f92d$"
}
{ "frames": [
{
"filename": "0001.png",
"frame": { "x": 0, "y": 0, "w": 85, "h": 67 },
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 85, "h": 67 },
"sourceSize": { "w": 85, "h": 67 },
"duration": 100
}
],
"meta": {
"app": "https://www.aseprite.org/",
"version": "1.3.7-x64",
"format": "I8",
"size": { "w": 85, "h": 67 },
"scale": "1"
}
}

View File

@ -5,8 +5,7 @@
"rotated": false,
"trimmed": false,
"spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 63 },
"sourceSize": { "w": 64, "h": 63 },
"duration": 100
"sourceSize": { "w": 64, "h": 63 }
}
],
"meta": {

View File

@ -472,7 +472,7 @@
}
},
{
"filename": "167_3",
"filename": "167_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -492,6 +492,48 @@
"h": 30
}
},
{
"filename": "167_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 40,
"h": 30
},
"frame": {
"x": 400,
"y": 30,
"w": 40,
"h": 30
}
},
{
"filename": "168_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 40,
"h": 30
},
"frame": {
"x": 440,
"y": 30,
"w": 40,
"h": 30
}
},
{
"filename": "168_3",
"rotated": false,
@ -507,7 +549,7 @@
"h": 30
},
"frame": {
"x": 400,
"x": 480,
"y": 30,
"w": 40,
"h": 30
@ -528,8 +570,8 @@
"h": 30
},
"frame": {
"x": 440,
"y": 30,
"x": 0,
"y": 60,
"w": 40,
"h": 30
}
@ -549,8 +591,8 @@
"h": 30
},
"frame": {
"x": 480,
"y": 30,
"x": 40,
"y": 60,
"w": 40,
"h": 30
}
@ -570,7 +612,7 @@
"h": 30
},
"frame": {
"x": 0,
"x": 80,
"y": 60,
"w": 40,
"h": 30
@ -591,7 +633,7 @@
"h": 30
},
"frame": {
"x": 40,
"x": 120,
"y": 60,
"w": 40,
"h": 30
@ -612,7 +654,7 @@
"h": 30
},
"frame": {
"x": 80,
"x": 160,
"y": 60,
"w": 40,
"h": 30
@ -633,7 +675,7 @@
"h": 30
},
"frame": {
"x": 120,
"x": 200,
"y": 60,
"w": 40,
"h": 30
@ -654,7 +696,7 @@
"h": 30
},
"frame": {
"x": 160,
"x": 240,
"y": 60,
"w": 40,
"h": 30
@ -675,7 +717,7 @@
"h": 30
},
"frame": {
"x": 200,
"x": 280,
"y": 60,
"w": 40,
"h": 30
@ -696,7 +738,7 @@
"h": 30
},
"frame": {
"x": 240,
"x": 320,
"y": 60,
"w": 40,
"h": 30
@ -717,7 +759,7 @@
"h": 30
},
"frame": {
"x": 280,
"x": 360,
"y": 60,
"w": 40,
"h": 30
@ -738,7 +780,7 @@
"h": 30
},
"frame": {
"x": 320,
"x": 400,
"y": 60,
"w": 40,
"h": 30
@ -759,7 +801,7 @@
"h": 30
},
"frame": {
"x": 360,
"x": 440,
"y": 60,
"w": 40,
"h": 30
@ -780,7 +822,7 @@
"h": 30
},
"frame": {
"x": 400,
"x": 480,
"y": 60,
"w": 40,
"h": 30
@ -801,8 +843,8 @@
"h": 30
},
"frame": {
"x": 440,
"y": 60,
"x": 0,
"y": 90,
"w": 40,
"h": 30
}
@ -822,8 +864,8 @@
"h": 30
},
"frame": {
"x": 480,
"y": 60,
"x": 40,
"y": 90,
"w": 40,
"h": 30
}
@ -843,7 +885,7 @@
"h": 30
},
"frame": {
"x": 0,
"x": 80,
"y": 90,
"w": 40,
"h": 30
@ -864,7 +906,7 @@
"h": 30
},
"frame": {
"x": 40,
"x": 120,
"y": 90,
"w": 40,
"h": 30
@ -885,7 +927,7 @@
"h": 30
},
"frame": {
"x": 80,
"x": 160,
"y": 90,
"w": 40,
"h": 30
@ -906,7 +948,7 @@
"h": 30
},
"frame": {
"x": 120,
"x": 200,
"y": 90,
"w": 40,
"h": 30
@ -927,7 +969,7 @@
"h": 30
},
"frame": {
"x": 160,
"x": 240,
"y": 90,
"w": 40,
"h": 30
@ -948,7 +990,7 @@
"h": 30
},
"frame": {
"x": 200,
"x": 280,
"y": 90,
"w": 40,
"h": 30
@ -969,7 +1011,7 @@
"h": 30
},
"frame": {
"x": 240,
"x": 320,
"y": 90,
"w": 40,
"h": 30
@ -990,7 +1032,7 @@
"h": 30
},
"frame": {
"x": 280,
"x": 360,
"y": 90,
"w": 40,
"h": 30
@ -1011,7 +1053,7 @@
"h": 30
},
"frame": {
"x": 320,
"x": 400,
"y": 90,
"w": 40,
"h": 30
@ -1032,7 +1074,7 @@
"h": 30
},
"frame": {
"x": 360,
"x": 440,
"y": 90,
"w": 40,
"h": 30
@ -1053,7 +1095,7 @@
"h": 30
},
"frame": {
"x": 400,
"x": 480,
"y": 90,
"w": 40,
"h": 30
@ -1074,8 +1116,8 @@
"h": 30
},
"frame": {
"x": 440,
"y": 90,
"x": 0,
"y": 120,
"w": 40,
"h": 30
}
@ -1095,8 +1137,8 @@
"h": 30
},
"frame": {
"x": 480,
"y": 90,
"x": 40,
"y": 120,
"w": 40,
"h": 30
}
@ -1116,7 +1158,7 @@
"h": 30
},
"frame": {
"x": 0,
"x": 80,
"y": 120,
"w": 40,
"h": 30
@ -1137,7 +1179,7 @@
"h": 30
},
"frame": {
"x": 40,
"x": 120,
"y": 120,
"w": 40,
"h": 30
@ -1158,7 +1200,7 @@
"h": 30
},
"frame": {
"x": 80,
"x": 160,
"y": 120,
"w": 40,
"h": 30
@ -1179,7 +1221,7 @@
"h": 30
},
"frame": {
"x": 120,
"x": 200,
"y": 120,
"w": 40,
"h": 30
@ -1200,7 +1242,7 @@
"h": 30
},
"frame": {
"x": 160,
"x": 240,
"y": 120,
"w": 40,
"h": 30
@ -1221,7 +1263,7 @@
"h": 30
},
"frame": {
"x": 200,
"x": 280,
"y": 120,
"w": 40,
"h": 30
@ -1242,7 +1284,7 @@
"h": 30
},
"frame": {
"x": 240,
"x": 320,
"y": 120,
"w": 40,
"h": 30
@ -1263,7 +1305,7 @@
"h": 30
},
"frame": {
"x": 280,
"x": 360,
"y": 120,
"w": 40,
"h": 30
@ -1284,7 +1326,7 @@
"h": 30
},
"frame": {
"x": 320,
"x": 400,
"y": 120,
"w": 40,
"h": 30
@ -1305,7 +1347,7 @@
"h": 30
},
"frame": {
"x": 360,
"x": 440,
"y": 120,
"w": 40,
"h": 30
@ -1326,7 +1368,7 @@
"h": 30
},
"frame": {
"x": 400,
"x": 480,
"y": 120,
"w": 40,
"h": 30
@ -1346,48 +1388,6 @@
"w": 40,
"h": 30
},
"frame": {
"x": 440,
"y": 120,
"w": 40,
"h": 30
}
},
{
"filename": "185_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 40,
"h": 30
},
"frame": {
"x": 480,
"y": 120,
"w": 40,
"h": 30
}
},
{
"filename": "190_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 40,
"h": 30
},
"frame": {
"x": 0,
"y": 150,
@ -1396,7 +1396,7 @@
}
},
{
"filename": "190_3",
"filename": "185_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1417,7 +1417,7 @@
}
},
{
"filename": "193_2",
"filename": "190_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1438,7 +1438,7 @@
}
},
{
"filename": "193_3",
"filename": "190_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1459,7 +1459,7 @@
}
},
{
"filename": "194_2",
"filename": "193_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1480,7 +1480,7 @@
}
},
{
"filename": "194_3",
"filename": "193_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1501,7 +1501,7 @@
}
},
{
"filename": "195_2",
"filename": "194_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1522,7 +1522,7 @@
}
},
{
"filename": "195_3",
"filename": "194_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1543,7 +1543,7 @@
}
},
{
"filename": "196_1",
"filename": "195_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1564,7 +1564,7 @@
}
},
{
"filename": "196_2",
"filename": "195_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1585,7 +1585,7 @@
}
},
{
"filename": "196_3",
"filename": "196_1",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1606,7 +1606,7 @@
}
},
{
"filename": "197_2",
"filename": "196_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1627,7 +1627,7 @@
}
},
{
"filename": "197_3",
"filename": "196_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1648,7 +1648,7 @@
}
},
{
"filename": "198-f_2",
"filename": "197_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1669,7 +1669,7 @@
}
},
{
"filename": "198-f_3",
"filename": "197_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
@ -1689,6 +1689,27 @@
"h": 30
}
},
{
"filename": "198-f_2",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 40,
"h": 30
},
"frame": {
"x": 80,
"y": 180,
"w": 40,
"h": 30
}
},
{
"filename": "198_2",
"rotated": false,
@ -1710,6 +1731,27 @@
"h": 30
}
},
{
"filename": "198-f_3",
"rotated": false,
"trimmed": false,
"sourceSize": {
"w": 40,
"h": 30
},
"spriteSourceSize": {
"x": 0,
"y": 0,
"w": 40,
"h": 30
},
"frame": {
"x": 120,
"y": 180,
"w": 40,
"h": 30
}
},
{
"filename": "198_3",
"rotated": false,
@ -4593,6 +4635,6 @@
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "3.0",
"smartupdate": "$TexturePacker:SmartUpdate:ff54fa5126a17cec88b98f162597ce6f:99c19b3effadce83c903f5e2a68c7dce:63b368599cdc6e139499267117e91cd5$"
"smartupdate": "$TexturePacker:SmartUpdate:cb87bf48266ab3d893dbbd24a52f875f:d37e73561b49b4fe831e3fcaee67b851:63b368599cdc6e139499267117e91cd5$"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -330,6 +330,30 @@ export class ReceivedMoveDamageMultiplierAbAttr extends PreDefendAbAttr {
}
}
/**
* Reduces the damage dealt to an allied Pokemon. Used by Friend Guard.
* @see {@linkcode applyPreDefend}
*/
export class AlliedFieldDamageReductionAbAttr extends PreDefendAbAttr {
private damageMultiplier: number;
constructor(damageMultiplier: number) {
super();
this.damageMultiplier = damageMultiplier;
}
/**
* Handles the damage reduction
* @param args
* - `[0]` {@linkcode Utils.NumberHolder} - The damage being dealt
*/
override applyPreDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, _move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean {
const damage = args[0] as Utils.NumberHolder;
damage.value = Utils.toDmgValue(damage.value * this.damageMultiplier);
return true;
}
}
export class ReceivedTypeDamageMultiplierAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
constructor(moveType: Type, damageMultiplier: number) {
super((target, user, move) => user.getMoveType(move) === moveType, damageMultiplier);
@ -3590,22 +3614,19 @@ export class MoodyAbAttr extends PostTurnAbAttr {
}
}
export class PostTurnStatStageChangeAbAttr extends PostTurnAbAttr {
private stats: BattleStat[];
private stages: number;
export class SpeedBoostAbAttr extends PostTurnAbAttr {
constructor(stats: BattleStat[], stages: number) {
constructor() {
super(true);
this.stats = Array.isArray(stats)
? stats
: [ stats ];
this.stages = stages;
}
applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
if (!simulated) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, this.stats, this.stages));
if (!pokemon.turnData.switchedInThisTurn && !pokemon.turnData.failedRunAway) {
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPD ], 1));
} else {
return false;
}
}
return true;
}
@ -4679,6 +4700,84 @@ export class PreventBypassSpeedChanceAbAttr extends AbAttr {
}
}
/**
* This applies a terrain-based type change to the Pokemon.
* Used by Mimicry.
*/
export class TerrainEventTypeChangeAbAttr extends PostSummonAbAttr {
constructor() {
super(true);
}
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: Utils.BooleanHolder, _args: any[]): boolean {
if (pokemon.isTerastallized()) {
return false;
}
const currentTerrain = pokemon.scene.arena.getTerrainType();
const typeChange: Type[] = this.determineTypeChange(pokemon, currentTerrain);
if (typeChange.length !== 0) {
if (pokemon.summonData.addedType && typeChange.includes(pokemon.summonData.addedType)) {
pokemon.summonData.addedType = null;
}
pokemon.summonData.types = typeChange;
pokemon.updateInfo();
}
return true;
}
/**
* Retrieves the type(s) the Pokemon should change to in response to a terrain
* @param pokemon
* @param currentTerrain {@linkcode TerrainType}
* @returns a list of type(s)
*/
private determineTypeChange(pokemon: Pokemon, currentTerrain: TerrainType): Type[] {
const typeChange: Type[] = [];
switch (currentTerrain) {
case TerrainType.ELECTRIC:
typeChange.push(Type.ELECTRIC);
break;
case TerrainType.MISTY:
typeChange.push(Type.FAIRY);
break;
case TerrainType.GRASSY:
typeChange.push(Type.GRASS);
break;
case TerrainType.PSYCHIC:
typeChange.push(Type.PSYCHIC);
break;
default:
pokemon.getTypes(false, false, true).forEach(t => {
typeChange.push(t);
});
break;
}
return typeChange;
}
/**
* Checks if the Pokemon should change types if summoned into an active terrain
* @returns `true` if there is an active terrain requiring a type change | `false` if not
*/
override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
if (pokemon.scene.arena.getTerrainType() !== TerrainType.NONE) {
return this.apply(pokemon, passive, simulated, new Utils.BooleanHolder(false), []);
}
return false;
}
override getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) {
const currentTerrain = pokemon.scene.arena.getTerrainType();
const pokemonNameWithAffix = getPokemonNameWithAffix(pokemon);
if (currentTerrain === TerrainType.NONE) {
return i18next.t("abilityTriggers:pokemonTypeChangeRevert", { pokemonNameWithAffix });
} else {
const moveType = i18next.t(`pokemonInfo:Type.${Type[this.determineTypeChange(pokemon, currentTerrain)[0]]}`);
return i18next.t("abilityTriggers:pokemonTypeChange", { pokemonNameWithAffix, moveType });
}
}
}
async function applyAbAttrsInternal<TAttr extends AbAttr>(
attrType: Constructor<TAttr>,
pokemon: Pokemon | null,
@ -4909,7 +5008,7 @@ export function initAbilities() {
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
new Ability(Abilities.SPEED_BOOST, 3)
.attr(PostTurnStatStageChangeAbAttr, [ Stat.SPD ], 1),
.attr(SpeedBoostAbAttr),
new Ability(Abilities.BATTLE_ARMOR, 3)
.attr(BlockCritAbAttr)
.ignorable(),
@ -5310,8 +5409,8 @@ export function initAbilities() {
new Ability(Abilities.HEALER, 5)
.conditionalAttr(pokemon => pokemon.getAlly() && Utils.randSeedInt(10) < 3, PostTurnResetStatusAbAttr, true),
new Ability(Abilities.FRIEND_GUARD, 5)
.ignorable()
.unimplemented(),
.attr(AlliedFieldDamageReductionAbAttr, 0.75)
.ignorable(),
new Ability(Abilities.WEAK_ARMOR, 5)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.DEF, -1)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, Stat.SPD, 2),
@ -5743,7 +5842,7 @@ export function initAbilities() {
new Ability(Abilities.POWER_SPOT, 8)
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ], 1.3),
new Ability(Abilities.MIMICRY, 8)
.unimplemented(),
.attr(TerrainEventTypeChangeAbAttr),
new Ability(Abilities.SCREEN_CLEANER, 8)
.attr(PostSummonRemoveArenaTagAbAttr, [ ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.REFLECT ]),
new Ability(Abilities.STEELY_SPIRIT, 8)
@ -5900,7 +5999,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(), // Counter resets every wave
.partial(), // Counter resets every wave instead of on arena reset
new Ability(Abilities.COSTAR, 9)
.attr(PostSummonCopyAllyStatsAbAttr),
new Ability(Abilities.TOXIC_DEBRIS, 9)

View File

@ -126,6 +126,7 @@ export class MistTag extends ArenaTag {
* Cancels the lowering of stats
* @param arena the {@linkcode Arena} containing this effect
* @param simulated `true` if the effect should be applied quietly
* @param attacker the {@linkcode Pokemon} using a move into this effect.
* @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

View File

@ -1443,7 +1443,7 @@ export const pokemonEvolutions: PokemonEvolutions = {
],
[Species.ROCKRUFF]: [
new SpeciesFormEvolution(Species.LYCANROC, "", "midday", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DAWN || p.scene.arena.getTimeOfDay() === TimeOfDay.DAY) && (p.formIndex === 0))),
new SpeciesFormEvolution(Species.LYCANROC, "", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)),
new SpeciesFormEvolution(Species.LYCANROC, "own-tempo", "dusk", 25, null, new SpeciesEvolutionCondition(p => p.formIndex === 1)),
new SpeciesFormEvolution(Species.LYCANROC, "", "midnight", 25, null, new SpeciesEvolutionCondition(p => (p.scene.arena.getTimeOfDay() === TimeOfDay.DUSK || p.scene.arena.getTimeOfDay() === TimeOfDay.NIGHT) && (p.formIndex === 0)))
],
[Species.STEENEE]: [

View File

@ -497,7 +497,7 @@ export const speciesEggTiers = {
[Species.DREEPY]: EggTier.RARE,
[Species.ZACIAN]: EggTier.LEGENDARY,
[Species.ZAMAZENTA]: EggTier.LEGENDARY,
[Species.ETERNATUS]: EggTier.COMMON,
[Species.ETERNATUS]: EggTier.LEGENDARY,
[Species.KUBFU]: EggTier.EPIC,
[Species.ZARUDE]: EggTier.EPIC,
[Species.REGIELEKI]: EggTier.EPIC,

File diff suppressed because it is too large Load Diff

View File

@ -544,11 +544,15 @@ export class Egg {
////
}
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
const legendarySpecies = Object.entries(speciesEggTiers)
export function getValidLegendaryGachaSpecies() : Species[] {
return Object.entries(speciesEggTiers)
.filter(s => s[1] === EggTier.LEGENDARY)
.map(s => parseInt(s[0]))
.filter(s => getPokemonSpecies(s).isObtainable());
.filter(s => getPokemonSpecies(s).isObtainable() && s !== Species.ETERNATUS);
}
export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species {
const legendarySpecies = getValidLegendaryGachaSpecies();
let ret: Species;

View File

@ -1409,6 +1409,11 @@ export class RecoilAttr extends MoveEffectAttr {
return false;
}
// Chloroblast and Struggle should not deal recoil damage if the move was not successful
if (this.useHp && [ MoveResult.FAIL, MoveResult.MISS ].includes(user.getLastXMoves(1)[0]?.result)) {
return false;
}
const damageValue = (!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio;
const minValue = user.turnData.damageDealt ? 1 : 0;
const recoilDamage = Utils.toDmgValue(damageValue, minValue);
@ -2166,7 +2171,10 @@ export class StatusEffectAttr extends MoveEffectAttr {
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
const score = (moveChance < 0) ? -10 : Math.floor(moveChance * -0.1);
const pokemon = this.selfTarget ? user : target;
return !pokemon.status && pokemon.canSetStatus(this.effect, true, false, user) ? score : 0;
}
}
@ -2186,7 +2194,10 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
const score = (moveChance < 0) ? -10 : Math.floor(moveChance * -0.1);
const pokemon = this.selfTarget ? user : target;
return !pokemon.status && pokemon.canSetStatus(this.effect, true, false, user) ? score : 0;
}
}
@ -2217,7 +2228,7 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
}
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(user.status?.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
return !target.status && target.canSetStatus(user.status?.effect, true, false, user) ? -10 : 0;
}
}
/**
@ -5847,6 +5858,9 @@ export class RemoveTypeAttr extends MoveEffectAttr {
const userTypes = user.getTypes(true);
const modifiedTypes = userTypes.filter(type => type !== this.removedType);
if (modifiedTypes.length === 0) {
modifiedTypes.push(Type.UNKNOWN);
}
user.summonData.types = modifiedTypes;
user.updateInfo();
@ -5869,7 +5883,11 @@ export class CopyTypeAttr extends MoveEffectAttr {
return false;
}
user.summonData.types = target.getTypes(true);
const targetTypes = target.getTypes(true);
if (targetTypes.includes(Type.UNKNOWN) && targetTypes.indexOf(Type.UNKNOWN) > -1) {
targetTypes[targetTypes.indexOf(Type.UNKNOWN)] = Type.NORMAL;
}
user.summonData.types = targetTypes;
user.updateInfo();
user.scene.queueMessage(i18next.t("moveTriggers:copyType", { pokemonName: getPokemonNameWithAffix(user), targetPokemonName: getPokemonNameWithAffix(target) }));
@ -5878,7 +5896,7 @@ export class CopyTypeAttr extends MoveEffectAttr {
}
getCondition(): MoveConditionFunc {
return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN;
return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN || target.summonData.addedType !== null;
}
}
@ -5936,11 +5954,7 @@ export class AddTypeAttr extends MoveEffectAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN); // TODO: Figure out some way to actually check if another version of this effect is already applied
if (this.type !== Type.UNKNOWN) {
types.push(this.type);
}
target.summonData.types = types;
target.summonData.addedType = this.type;
target.updateInfo();
user.scene.queueMessage(i18next.t("moveTriggers:addType", { typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target) }));
@ -9243,8 +9257,7 @@ export function initMoves() {
.chargeAttr(SemiInvulnerableAttr, BattlerTagType.HIDDEN)
.ignoresProtect(),
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
.attr(AddTypeAttr, Type.GHOST)
.edgeCase(), // Weird interaction with Forest's Curse, reflect type, burn up
.attr(AddTypeAttr, Type.GHOST),
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1)
.soundBased(),
@ -9256,8 +9269,7 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_OTHERS)
.triageMove(),
new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6)
.attr(AddTypeAttr, Type.GRASS)
.edgeCase(), // Weird interaction with Trick or Treat, reflect type, burn up
.attr(AddTypeAttr, Type.GRASS),
new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6)
.windMove()
.makesContact(false)
@ -9697,7 +9709,7 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_ENEMIES)
.edgeCase(), // I assume it needs clanging scales and Kommo-O
/* End Unused */
new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) //LGPE Implementation
new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) // LGPE Implementation
.attr(CritOnlyAttr),
new AttackMove(Moves.SPLISHY_SPLASH, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 30, 0, 7)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
@ -9707,7 +9719,7 @@ export function initMoves() {
new AttackMove(Moves.PIKA_PAPOW, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 20, -1, 0, 7)
.attr(FriendshipPowerAttr),
new AttackMove(Moves.BOUNCY_BUBBLE, Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, -1, 0, 7)
.attr(HitHealAttr, 1.0)
.attr(HitHealAttr) // Custom
.triageMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
new AttackMove(Moves.BUZZY_BUZZ, Type.ELECTRIC, MoveCategory.SPECIAL, 60, 100, 20, 100, 0, 7)
@ -10190,6 +10202,7 @@ export function initMoves() {
.attr(ConfuseAttr)
.recklessMove(),
new AttackMove(Moves.LAST_RESPECTS, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9)
.partial() // Counter resets every wave instead of on arena reset
.attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 100))
.makesContact(false),
new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)

View File

@ -3,6 +3,8 @@
* or {@linkcode SwitchSummonPhase} will carry out.
*/
export enum SwitchType {
/** Switchout specifically for when combat starts and the player is prompted if they will switch Pokemon */
INITIAL_SWITCH,
/** Basic switchout where the Pokemon to switch in is selected */
SWITCH,
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */

View File

@ -10,7 +10,14 @@ import Move from "#app/data/move";
import { ArenaTag, ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
import { BattlerIndex } from "#app/battle";
import { Terrain, TerrainType } from "#app/data/terrain";
import { applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs, PostTerrainChangeAbAttr, PostWeatherChangeAbAttr } from "#app/data/ability";
import {
applyAbAttrs,
applyPostTerrainChangeAbAttrs,
applyPostWeatherChangeAbAttrs,
PostTerrainChangeAbAttr,
PostWeatherChangeAbAttr,
TerrainEventTypeChangeAbAttr
} from "#app/data/ability";
import Pokemon from "#app/field/pokemon";
import Overrides from "#app/overrides";
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
@ -387,6 +394,7 @@ export class Arena {
this.scene.getField(true).filter(p => p.isOnField()).map(pokemon => {
pokemon.findAndRemoveTags(t => "terrainTypes" in t && !(t.terrainTypes as TerrainType[]).find(t => t === terrain));
applyPostTerrainChangeAbAttrs(PostTerrainChangeAbAttr, pokemon, terrain);
applyAbAttrs(TerrainEventTypeChangeAbAttr, pokemon, null, false);
});
return true;

View File

@ -22,7 +22,7 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/
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, 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, InfiltratorAbAttr } from "#app/data/ability";
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, 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, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr } from "#app/data/ability";
import PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#app/battle";
import { Mode } from "#app/ui/ui";
@ -1258,6 +1258,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
// the type added to Pokemon from moves like Forest's Curse or Trick Or Treat
if (!ignoreOverride && this.summonData && this.summonData.addedType && !types.includes(this.summonData.addedType)) {
types.push(this.summonData.addedType);
}
// If both types are the same (can happen in weird custom typing scenarios), reduce to single type
if (types.length > 1 && types[0] === types[1]) {
types.splice(0, 1);
@ -2667,9 +2672,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage);
}
/** Apply this Pokemon's post-calc defensive modifiers (e.g. Fur Coat) */
if (!ignoreAbility) {
applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, this, source, move, cancelled, simulated, damage);
/** Additionally apply friend guard damage reduction if ally has it. */
if (this.scene.currentBattle.double && this.getAlly()?.isActive(true)) {
applyPreDefendAbAttrs(AlliedFieldDamageReductionAbAttr, this.getAlly(), source, move, cancelled, simulated, damage);
}
}
// This attribute may modify damage arbitrarily, so be careful about changing its order of application.
@ -5091,6 +5102,7 @@ export class PokemonSummonData {
public moveset: (PokemonMove | null)[];
// If not initialized this value will not be populated from save data.
public types: Type[] = [];
public addedType: Type | null = null;
}
export class PokemonBattleData {
@ -5128,6 +5140,8 @@ export class PokemonTurnData {
public statStagesDecreased: boolean = false;
public moveEffectiveness: TypeDamageMultiplier | null = null;
public combiningPledge?: Moves;
public switchedInThisTurn: boolean = false;
public failedRunAway: boolean = false;
}
export enum AiType {

View File

@ -44,7 +44,7 @@ document.fonts.load("16px emerald").then(() => document.fonts.load("10px pkmnems
let game;
const startGame = async () => {
const startGame = async (manifest?: any) => {
await initI18n();
const LoadingScene = (await import("./loading-scene")).LoadingScene;
const BattleScene = (await import("./battle-scene")).default;
@ -94,13 +94,15 @@ const startGame = async () => {
version: version
});
game.sound.pauseOnBlur = false;
if (manifest) {
game["manifest"] = manifest;
}
};
fetch("/manifest.json")
.then(res => res.json())
.then(jsonResponse => {
startGame();
game["manifest"] = jsonResponse.manifest;
startGame(jsonResponse.manifest);
}).catch(() => {
// Manifest not found (likely local build)
startGame();

View File

@ -502,45 +502,25 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge
}
}
function getAttackTypeBoosterItemName(type: Type) {
switch (type) {
case Type.NORMAL:
return "Silk Scarf";
case Type.FIGHTING:
return "Black Belt";
case Type.FLYING:
return "Sharp Beak";
case Type.POISON:
return "Poison Barb";
case Type.GROUND:
return "Soft Sand";
case Type.ROCK:
return "Hard Stone";
case Type.BUG:
return "Silver Powder";
case Type.GHOST:
return "Spell Tag";
case Type.STEEL:
return "Metal Coat";
case Type.FIRE:
return "Charcoal";
case Type.WATER:
return "Mystic Water";
case Type.GRASS:
return "Miracle Seed";
case Type.ELECTRIC:
return "Magnet";
case Type.PSYCHIC:
return "Twisted Spoon";
case Type.ICE:
return "Never-Melt Ice";
case Type.DRAGON:
return "Dragon Fang";
case Type.DARK:
return "Black Glasses";
case Type.FAIRY:
return "Fairy Feather";
}
enum AttackTypeBoosterItem {
SILK_SCARF,
BLACK_BELT,
SHARP_BEAK,
POISON_BARB,
SOFT_SAND,
HARD_STONE,
SILVER_POWDER,
SPELL_TAG,
METAL_COAT,
CHARCOAL,
MYSTIC_WATER,
MIRACLE_SEED,
MAGNET,
TWISTED_SPOON,
NEVER_MELT_ICE,
DRAGON_FANG,
BLACK_GLASSES,
FAIRY_FEATHER
}
export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
@ -548,7 +528,7 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i
public boostPercent: integer;
constructor(moveType: Type, boostPercent: integer) {
super("", `${getAttackTypeBoosterItemName(moveType)?.replace(/[ \-]/g, "_").toLowerCase()}`,
super("", `${AttackTypeBoosterItem[moveType]?.toLowerCase()}`,
(_type, args) => new AttackTypeBoosterModifier(this, (args[0] as Pokemon).id, moveType, boostPercent));
this.moveType = moveType;
@ -556,7 +536,7 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i
}
get name(): string {
return i18next.t(`modifierType:AttackTypeBoosterItem.${getAttackTypeBoosterItemName(this.moveType)?.replace(/[ \-]/g, "_").toLowerCase()}`);
return i18next.t(`modifierType:AttackTypeBoosterItem.${AttackTypeBoosterItem[this.moveType]?.toLowerCase()}`);
}
getDescription(scene: BattleScene): string {

View File

@ -10,6 +10,10 @@ import { NewBattlePhase } from "./new-battle-phase";
import { PokemonPhase } from "./pokemon-phase";
export class AttemptRunPhase extends PokemonPhase {
/** For testing purposes: this is to force the pokemon to fail and escape */
public forceFailEscape = false;
constructor(scene: BattleScene, fieldIndex: number) {
super(scene, fieldIndex);
}
@ -28,7 +32,7 @@ export class AttemptRunPhase extends PokemonPhase {
applyAbAttrs(RunSuccessAbAttr, playerPokemon, null, false, escapeChance);
if (playerPokemon.randSeedInt(100) < escapeChance.value) {
if (playerPokemon.randSeedInt(100) < escapeChance.value && !this.forceFailEscape) {
this.scene.playSound("se/flee");
this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500);
@ -51,6 +55,7 @@ export class AttemptRunPhase extends PokemonPhase {
this.scene.pushPhase(new BattleEndPhase(this.scene));
this.scene.pushPhase(new NewBattlePhase(this.scene));
} else {
playerPokemon.turnData.failedRunAway = true;
this.scene.queueMessage(i18next.t("battle:runAwayCannotEscape"), null, true, 500);
}

View File

@ -51,7 +51,7 @@ export class CheckSwitchPhase extends BattlePhase {
this.scene.ui.setMode(Mode.CONFIRM, () => {
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, false, true));
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.INITIAL_SWITCH, this.fieldIndex, false, true));
this.end();
}, () => {
this.scene.ui.setMode(Mode.MESSAGE);

View File

@ -65,6 +65,15 @@ export class FaintPhase extends PokemonPhase {
}
}
/** In case the current pokemon was just switched in, make sure it is counted as participating in the combat */
this.scene.getPlayerField().forEach((pokemon, i) => {
if (pokemon?.isActive(true)) {
if (pokemon.isPlayer()) {
this.scene.currentBattle.addParticipant(pokemon as PlayerPokemon);
}
}
});
if (!this.tryOverrideForBattleSpec()) {
this.doFaint();
}

View File

@ -3,7 +3,7 @@ import BattleScene from "#app/battle-scene";
import { applyAbAttrs, applyPostMoveUsedAbAttrs, applyPreAttackAbAttrs, BlockRedirectAbAttr, IncreasePpAbAttr, PokemonTypeChangeAbAttr, PostMoveUsedAbAttr, RedirectMoveAbAttr, ReduceStatusEffectDurationAbAttr } from "#app/data/ability";
import { CommonAnim } from "#app/data/battle-anims";
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags";
import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, CopyMoveAttr, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move";
import { allMoves, applyMoveAttrs, BypassRedirectAttr, BypassSleepAttr, CopyMoveAttr, frenzyMissFunc, HealStatusEffectAttr, MoveFlags, PreMoveMessageAttr } from "#app/data/move";
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms";
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
import { Type } from "#app/data/type";
@ -473,6 +473,10 @@ export class MovePhase extends BattlePhase {
this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed));
}
if (this.cancelled && this.pokemon.summonData?.tags?.find(t => t.tagType === BattlerTagType.FRENZY)) {
frenzyMissFunc(this.pokemon, this.move.getMove());
}
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAIL, targets: this.targets });
this.pokemon.lapseTags(BattlerTagLapseType.MOVE_EFFECT);

View File

@ -65,7 +65,7 @@ export class StatStageChangePhase extends PokemonPhase {
if (!this.selfTarget && stages.value < 0) {
// TODO: add a reference to the source of the stat change to fix Infiltrator interaction
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, false, cancelled);
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, false, null, cancelled);
}
if (!cancelled.value && !this.selfTarget && stages.value < 0) {

View File

@ -64,10 +64,8 @@ export class SwitchSummonPhase extends SummonPhase {
}
const pokemon = this.getPokemon();
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
if (this.switchType === SwitchType.SWITCH) {
if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) {
const substitute = pokemon.getTag(SubstituteTag);
if (substitute) {
this.scene.tweens.add({
@ -186,6 +184,11 @@ export class SwitchSummonPhase extends SummonPhase {
}
}
if (this.switchType !== SwitchType.INITIAL_SWITCH) {
pokemon.resetTurnData();
pokemon.turnData.switchedInThisTurn = true;
}
this.lastPokemon?.resetSummonData();
this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger, true);

View File

@ -205,11 +205,11 @@ export class TurnStartPhase extends FieldPhase {
}
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
this.scene.pushPhase(new BerryPhase(this.scene));
/** 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

@ -164,7 +164,7 @@ export async function initI18n(): Promise<void> {
} else {
fileName = camelCaseToKebabCase(ns);
}
return `/locales/${lng}/${fileName}.json?v=${pkg.version}`;
return `./locales/${lng}/${fileName}.json?v=${pkg.version}`;
},
},
defaultNS: "menu",

View File

@ -118,8 +118,8 @@ export default class PokemonData {
// Deprecated, but needed for session data migration
this.natureOverride = source.natureOverride;
this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData);
this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData);
this.mysteryEncounterPokemonData = source.mysteryEncounterPokemonData ? new CustomPokemonData(source.mysteryEncounterPokemonData) : null;
this.fusionMysteryEncounterPokemonData = source.fusionMysteryEncounterPokemonData ? new CustomPokemonData(source.fusionMysteryEncounterPokemonData) : null;
if (!forHistory) {
this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss);

View File

@ -0,0 +1,120 @@
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Abilities } from "#enums/abilities";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { BattlerIndex } from "#app/battle";
import { allAbilities } from "#app/data/ability";
import { allMoves, MoveCategory } from "#app/data/move";
describe("Moves - Friend Guard", () => {
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("double")
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset([ Moves.TACKLE, Moves.SPLASH, Moves.DRAGON_RAGE ])
.enemySpecies(Species.SHUCKLE)
.moveset([ Moves.SPLASH ])
.startingLevel(100);
});
it("should reduce damage that other allied Pokémon receive from attacks (from any Pokémon) by 25%", async () => {
await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER ]);
const [ player1, player2 ] = game.scene.getPlayerField();
const spy = vi.spyOn(player1, "getAttackDamage");
const enemy1 = game.scene.getEnemyField()[0];
game.move.select(Moves.SPLASH);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
// Get the last return value from `getAttackDamage`
const turn1Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
// Making sure the test is controlled; turn 1 damage is equal to base damage (after rounding)
expect(turn1Damage).toBe(Math.floor(player1.getBaseDamage(enemy1, allMoves[Moves.TACKLE], MoveCategory.PHYSICAL)));
vi.spyOn(player2, "getAbility").mockReturnValue(allAbilities[Abilities.FRIEND_GUARD]);
game.move.select(Moves.SPLASH);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
// Get the last return value from `getAttackDamage`
const turn2Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
// With the ally's Friend Guard, damage should have been reduced from base damage by 25%
expect(turn2Damage).toBe(Math.floor(player1.getBaseDamage(enemy1, allMoves[Moves.TACKLE], MoveCategory.PHYSICAL) * 0.75));
});
it("should NOT reduce damage to pokemon with friend guard", async () => {
await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER ]);
const player2 = game.scene.getPlayerField()[1];
const spy = vi.spyOn(player2, "getAttackDamage");
game.move.select(Moves.SPLASH);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
const turn1Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
vi.spyOn(player2, "getAbility").mockReturnValue(allAbilities[Abilities.FRIEND_GUARD]);
game.move.select(Moves.SPLASH);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.TACKLE, BattlerIndex.PLAYER_2);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
const turn2Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
expect(turn2Damage).toBe(turn1Damage);
});
it("should NOT reduce damage from fixed damage attacks", async () => {
await game.classicMode.startBattle([ Species.BULBASAUR, Species.CHARMANDER ]);
const [ player1, player2 ] = game.scene.getPlayerField();
const spy = vi.spyOn(player1, "getAttackDamage");
game.move.select(Moves.SPLASH);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.DRAGON_RAGE, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
const turn1Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
expect(turn1Damage).toBe(40);
vi.spyOn(player2, "getAbility").mockReturnValue(allAbilities[Abilities.FRIEND_GUARD]);
game.move.select(Moves.SPLASH);
game.move.select(Moves.SPLASH, 1);
await game.forceEnemyMove(Moves.DRAGON_RAGE, BattlerIndex.PLAYER);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
const turn2Damage = spy.mock.results[spy.mock.results.length - 1].value.damage;
expect(turn2Damage).toBe(40);
});
});

View File

@ -0,0 +1,91 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Type } from "#app/data/type";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Mimicry", () => {
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.SPLASH ])
.ability(Abilities.MIMICRY)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyMoveset(Moves.SPLASH);
});
it("Mimicry activates after the Pokémon with Mimicry is switched in while terrain is present, or whenever there is a change in terrain", async () => {
game.override.enemyAbility(Abilities.MISTY_SURGE);
await game.classicMode.startBattle([ Species.FEEBAS, Species.ABRA ]);
const [ playerPokemon1, playerPokemon2 ] = game.scene.getParty();
game.move.select(Moves.SPLASH);
await game.toNextTurn();
expect(playerPokemon1.getTypes().includes(Type.FAIRY)).toBe(true);
game.doSwitchPokemon(1);
await game.toNextTurn();
expect(playerPokemon2.getTypes().includes(Type.FAIRY)).toBe(true);
});
it("Pokemon should revert back to its original, root type once terrain ends", async () => {
game.override
.moveset([ Moves.SPLASH, Moves.TRANSFORM ])
.enemyAbility(Abilities.MIMICRY)
.enemyMoveset([ Moves.SPLASH, Moves.PSYCHIC_TERRAIN ]);
await game.classicMode.startBattle([ Species.REGIELEKI ]);
const playerPokemon = game.scene.getPlayerPokemon();
game.move.select(Moves.TRANSFORM);
await game.forceEnemyMove(Moves.PSYCHIC_TERRAIN);
await game.toNextTurn();
expect(playerPokemon?.getTypes().includes(Type.PSYCHIC)).toBe(true);
if (game.scene.arena.terrain) {
game.scene.arena.terrain.turnsLeft = 1;
}
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
expect(playerPokemon?.getTypes().includes(Type.ELECTRIC)).toBe(true);
});
it("If the Pokemon is under the effect of a type-adding move and an equivalent terrain activates, the move's effect disappears", async () => {
game.override
.enemyMoveset([ Moves.FORESTS_CURSE, Moves.GRASSY_TERRAIN ]);
await game.classicMode.startBattle([ Species.FEEBAS ]);
const playerPokemon = game.scene.getPlayerPokemon();
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.FORESTS_CURSE);
await game.toNextTurn();
expect(playerPokemon?.summonData.addedType).toBe(Type.GRASS);
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.GRASSY_TERRAIN);
await game.phaseInterceptor.to("TurnEndPhase");
expect(playerPokemon?.summonData.addedType).toBeNull();
expect(playerPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
});
});

View File

@ -0,0 +1,125 @@
import { Stat } from "#enums/stat";
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";
import { CommandPhase } from "#app/phases/command-phase";
import { Command } from "#app/ui/command-ui-handler";
import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
describe("Abilities - Speed Boost", () => {
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.DRAGAPULT)
.ability(Abilities.SPEED_BOOST)
.enemyMoveset(Moves.SPLASH)
.moveset([ Moves.SPLASH, Moves.U_TURN ]);
});
it("should increase speed by 1 stage at end of turn",
async () => {
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.SPLASH);
await game.toNextTurn();
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
});
it("should not trigger this turn if pokemon was switched into combat via attack, but the turn after",
async () => {
await game.classicMode.startBattle([
Species.SHUCKLE,
Species.NINJASK
]);
game.move.select(Moves.U_TURN);
game.doSelectPartyPokemon(1);
await game.toNextTurn();
const playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
});
it("checking back to back swtiches",
async () => {
await game.classicMode.startBattle([
Species.SHUCKLE,
Species.NINJASK
]);
game.move.select(Moves.U_TURN);
game.doSelectPartyPokemon(1);
await game.toNextTurn();
let playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.move.select(Moves.U_TURN);
game.doSelectPartyPokemon(1);
await game.toNextTurn();
playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
});
it("should not trigger this turn if pokemon was switched into combat via normal switch, but the turn after",
async () => {
await game.classicMode.startBattle([
Species.SHUCKLE,
Species.NINJASK
]);
game.doSwitchPokemon(1);
await game.toNextTurn();
const playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
});
it("should not trigger if pokemon fails to escape",
async () => {
await game.classicMode.startBattle([ Species.SHUCKLE ]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
const runPhase = game.scene.getCurrentPhase() as AttemptRunPhase;
runPhase.forceFailEscape = true;
await game.phaseInterceptor.to(AttemptRunPhase);
await game.toNextTurn();
const playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.move.select(Moves.SPLASH);
await game.toNextTurn();
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
});
});

View File

@ -1,4 +1,7 @@
import { Egg, getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg";
import { speciesEggTiers } from "#app/data/balance/species-egg-tiers";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { Egg, getLegendaryGachaSpeciesForTimestamp, getValidLegendaryGachaSpecies } from "#app/data/egg";
import { allSpecies } from "#app/data/pokemon-species";
import { EggSourceType } from "#app/enums/egg-source-types";
import { EggTier } from "#app/enums/egg-type";
import { VariantTier } from "#app/enums/variant-tier";
@ -64,6 +67,12 @@ describe("Egg Generation Tests", () => {
expect(gachaSpeciesCount).toBeGreaterThan(0.4 * EGG_HATCH_COUNT);
expect(gachaSpeciesCount).toBeLessThan(0.6 * EGG_HATCH_COUNT);
});
it("should never be allowed to generate Eternatus via the legendary gacha", () => {
const validLegendaryGachaSpecies = getValidLegendaryGachaSpecies();
expect(validLegendaryGachaSpecies.every(s => speciesEggTiers[s] === EggTier.LEGENDARY)).toBe(true);
expect(validLegendaryGachaSpecies.every(s => allSpecies[s].isObtainable())).toBe(true);
expect(validLegendaryGachaSpecies.includes(Species.ETERNATUS)).toBe(false);
});
it("should hatch an Arceus. Set from species", () => {
const scene = game.scene;
const expectedSpecies = Species.ARCEUS;
@ -376,4 +385,23 @@ describe("Egg Generation Tests", () => {
expect(diffShiny).toBe(true);
expect(diffAbility).toBe(true);
});
// For now, we are using this test to detect oversights in egg tiers.
// Delete this test if the balance team rebalances species costs independently of egg tiers.
it("should have correct egg tiers based on species costs", () => {
const getExpectedEggTier = (starterCost) =>
starterCost <= 3 ? EggTier.COMMON
: starterCost <= 5 ? EggTier.RARE
: starterCost <= 7 ? EggTier.EPIC
: EggTier.LEGENDARY;
allSpecies.forEach(pokemonSpecies => {
const rootSpecies = pokemonSpecies.getRootSpeciesId();
const speciesCost = speciesStarterCosts[rootSpecies];
const expectedEggTier = getExpectedEggTier(speciesCost);
const actualEggTier = speciesEggTiers[rootSpecies];
expect(actualEggTier).toBe(expectedEggTier);
});
});
});

View File

@ -0,0 +1,42 @@
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("Moves - Chloroblast", () => {
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.CHLOROBLAST ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.PROTECT);
});
it("should not deal recoil damage if the opponent uses protect", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.CHLOROBLAST);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getPlayerPokemon()!.isFullHp()).toBe(true);
});
});

View File

@ -0,0 +1,47 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Type } from "#app/data/type";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Forest's Curse", () => {
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.FORESTS_CURSE, Moves.TRICK_OR_TREAT ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("will replace the added type from Trick Or Treat", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
const enemyPokemon = game.scene.getEnemyPokemon();
game.move.select(Moves.TRICK_OR_TREAT);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon!.summonData.addedType).toBe(Type.GHOST);
game.move.select(Moves.FORESTS_CURSE);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon?.summonData.addedType).toBe(Type.GRASS);
});
});

View File

@ -0,0 +1,49 @@
import { Stat } from "#enums/stat";
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("Moves - Mist", () => {
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.MIST, Moves.SPLASH ])
.ability(Abilities.BALL_FETCH)
.battleType("double")
.disableCrits()
.enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.GROWL);
});
it("should prevent the user's side from having stats lowered", async () => {
await game.classicMode.startBattle([ Species.MAGIKARP, Species.FEEBAS ]);
const playerPokemon = game.scene.getPlayerField();
game.move.select(Moves.MIST, 0);
game.move.select(Moves.SPLASH, 1);
await game.phaseInterceptor.to("BerryPhase");
playerPokemon.forEach(p => expect(p.getStatStage(Stat.ATK)).toBe(0));
});
it.todo("should be ignored by opponents with Infiltrator");
});

View File

@ -0,0 +1,59 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Type } from "#app/data/type";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Reflect Type", () => {
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()
.enemyAbility(Abilities.BALL_FETCH);
});
it("will make the user Normal/Grass if targetting a typeless Pokemon affected by Forest's Curse", async () => {
game.override
.moveset([ Moves.FORESTS_CURSE, Moves.REFLECT_TYPE ])
.startingLevel(60)
.enemySpecies(Species.CHARMANDER)
.enemyMoveset([ Moves.BURN_UP, Moves.SPLASH ]);
await game.classicMode.startBattle([ Species.FEEBAS ]);
const playerPokemon = game.scene.getPlayerPokemon();
const enemyPokemon = game.scene.getEnemyPokemon();
game.move.select(Moves.SPLASH);
await game.forceEnemyMove(Moves.BURN_UP);
await game.toNextTurn();
game.move.select(Moves.FORESTS_CURSE);
await game.forceEnemyMove(Moves.SPLASH);
await game.toNextTurn();
expect(enemyPokemon?.getTypes().includes(Type.UNKNOWN)).toBe(true);
expect(enemyPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
game.move.select(Moves.REFLECT_TYPE);
await game.forceEnemyMove(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
expect(playerPokemon?.getTypes()[0]).toBe(Type.NORMAL);
expect(playerPokemon?.getTypes().includes(Type.GRASS)).toBe(true);
});
});

View File

@ -0,0 +1,47 @@
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Type } from "#app/data/type";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Moves - Trick Or Treat", () => {
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.FORESTS_CURSE, Moves.TRICK_OR_TREAT ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("will replace added type from Forest's Curse", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
const enemyPokemon = game.scene.getEnemyPokemon();
game.move.select(Moves.FORESTS_CURSE);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon!.summonData.addedType).toBe(Type.GRASS);
game.move.select(Moves.TRICK_OR_TREAT);
await game.phaseInterceptor.to("TurnEndPhase");
expect(enemyPokemon?.summonData.addedType).toBe(Type.GHOST);
});
});

View File

@ -0,0 +1,72 @@
import { BattlerIndex } from "#app/battle";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { StatusEffect } from "#enums/status-effect";
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, it, expect } from "vitest";
describe("Frenzy Move Reset", () => {
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")
.disableCrits()
.starterSpecies(Species.MAGIKARP)
.moveset(Moves.THRASH)
.statusEffect(StatusEffect.PARALYSIS)
.enemyMoveset(Moves.SPLASH)
.enemyLevel(100)
.enemySpecies(Species.SHUCKLE)
.enemyAbility(Abilities.BALL_FETCH);
});
/*
* Thrash (or frenzy moves in general) should not continue to run if attack fails due to paralysis
*
* This is a 3-turn Thrash test:
* 1. Thrash is selected and succeeds to hit the enemy -> Enemy Faints
*
* 2. Thrash is automatically selected but misses due to paralysis
* Note: After missing the Pokemon should stop automatically attacking
*
* 3. At the start of the 3rd turn the Player should be able to select a move/switch Pokemon/etc.
* Note: This means that BattlerTag.FRENZY is not anymore in pokemon.summonData.tags and pokemon.summonData.moveQueue is empty
*
*/
it("should cancel frenzy move if move fails turn 2", async () => {
await game.classicMode.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.move.select(Moves.THRASH);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.move.forceStatusActivation(false);
await game.toNextTurn();
expect(playerPokemon.summonData.moveQueue.length).toBe(2);
expect(playerPokemon.summonData.tags.some(tag => tag.tagType === BattlerTagType.FRENZY)).toBe(true);
await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]);
await game.move.forceStatusActivation(true);
await game.toNextTurn();
expect(playerPokemon.summonData.moveQueue.length).toBe(0);
expect(playerPokemon.summonData.tags.some(tag => tag.tagType === BattlerTagType.FRENZY)).toBe(false);
});
});

View File

@ -31,7 +31,7 @@ const timedEvents: TimedEvent[] = [
eventType: EventType.SHINY,
shinyMultiplier: 2,
friendshipMultiplier: 2,
startDate: new Date(Date.UTC(2024, 9, 25, 0)),
startDate: new Date(Date.UTC(2024, 9, 27, 0)),
endDate: new Date(Date.UTC(2024, 10, 4, 0)),
bannerKey: "halloween2024-event-",
scale: 0.21,

View File

@ -32,7 +32,7 @@ let wikiUrl = "https://wiki.pokerogue.net/start";
const discordUrl = "https://discord.gg/uWpTfdKG49";
const githubUrl = "https://github.com/pagefaultgames/pokerogue";
const redditUrl = "https://www.reddit.com/r/pokerogue";
const donateUrl = "https://github.com/sponsors/patapancakes";
const donateUrl = "https://github.com/sponsors/pagefaultgames";
export default class MenuUiHandler extends MessageUiHandler {
private readonly textPadding = 8;

View File

@ -237,14 +237,20 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
const formKey = (pokemon.species?.forms?.[pokemon.formIndex!]?.formKey);
const formText = Utils.capitalizeString(formKey, "-", false, false) || "";
const speciesName = Utils.capitalizeString(Species[pokemon.species.getRootSpeciesId()], "_", true, false);
const speciesName = Utils.capitalizeString(Species[pokemon.species.speciesId], "_", true, false);
let formName = "";
if (pokemon.species.speciesId === Species.ARCEUS) {
formName = i18next.t(`pokemonInfo:Type.${formText?.toUpperCase()}`);
} else {
const i18key = `pokemonForm:${speciesName}${formText}`;
formName = i18next.exists(i18key) ? i18next.t(i18key) : formText;
if (i18next.exists(i18key)) {
formName = i18next.t(i18key);
} else {
const rootSpeciesName = Utils.capitalizeString(Species[pokemon.species.getRootSpeciesId()], "_", true, false);
const i18RootKey = `pokemonForm:${rootSpeciesName}${formText}`;
formName = i18next.exists(i18RootKey) ? i18next.t(i18RootKey) : formText;
}
}
if (formName) {