From 160b730921d8d58b925ae0c2c5d353d5ee3b4505 Mon Sep 17 00:00:00 2001 From: pom-eranian Date: Tue, 1 Oct 2024 15:24:08 -0400 Subject: [PATCH 01/24] [Hotfix] Revert "[Sprite] 451 - Skorupi Animation Fix " (#4535) --- public/images/pokemon/451.json | 3043 ++++++++++++++++++++------ public/images/pokemon/451.png | Bin 6490 -> 6518 bytes public/images/pokemon/shiny/451.json | 3043 ++++++++++++++++++++------ public/images/pokemon/shiny/451.png | Bin 6495 -> 6518 bytes 4 files changed, 4658 insertions(+), 1428 deletions(-) diff --git a/public/images/pokemon/451.json b/public/images/pokemon/451.json index 3a320a87c61..0e99c96f876 100644 --- a/public/images/pokemon/451.json +++ b/public/images/pokemon/451.json @@ -1,715 +1,2330 @@ -{ "frames": [ - { - "filename": "0001.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0002.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0003.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0004.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0005.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0006.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0007.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0008.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0009.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0010.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0011.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0012.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0013.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0014.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0015.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0016.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0017.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0018.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0019.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0020.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0021.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0022.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0023.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0024.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0025.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0026.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0027.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0028.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0029.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0030.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0031.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0032.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0033.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0034.png", - "frame": { "x": 68, "y": 0, "w": 64, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 4, "w": 64, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0035.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0036.png", - "frame": { "x": 177, "y": 42, "w": 61, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 61, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0037.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0038.png", - "frame": { "x": 238, "y": 43, "w": 59, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 59, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0039.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0040.png", - "frame": { "x": 0, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0041.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0042.png", - "frame": { "x": 57, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0043.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0044.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0045.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0046.png", - "frame": { "x": 287, "y": 86, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0047.png", - "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0048.png", - "frame": { "x": 232, "y": 86, "w": 55, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 1, "w": 55, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0049.png", - "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0050.png", - "frame": { "x": 117, "y": 85, "w": 57, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 2, "w": 57, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0051.png", - "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0052.png", - "frame": { "x": 117, "y": 42, "w": 60, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 5, "y": 3, "w": 60, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0053.png", - "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0054.png", - "frame": { "x": 132, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0055.png", - "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0056.png", - "frame": { "x": 0, "y": 0, "w": 68, "h": 40 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 6, "w": 68, "h": 40 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0057.png", - "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0058.png", - "frame": { "x": 195, "y": 0, "w": 63, "h": 42 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 4, "w": 63, "h": 42 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0059.png", - "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0060.png", - "frame": { "x": 258, "y": 0, "w": 61, "h": 43 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 3, "w": 61, "h": 43 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0061.png", - "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0062.png", - "frame": { "x": 58, "y": 42, "w": 59, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 2, "w": 59, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0063.png", - "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0064.png", - "frame": { "x": 0, "y": 40, "w": 58, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 58, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0065.png", - "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0066.png", - "frame": { "x": 177, "y": 84, "w": 55, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 0, "w": 55, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0067.png", - "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0068.png", - "frame": { "x": 0, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0069.png", - "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0070.png", - "frame": { "x": 112, "y": 129, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0071.png", - "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0072.png", - "frame": { "x": 167, "y": 130, "w": 55, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 55, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0073.png", - "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0074.png", - "frame": { "x": 0, "y": 173, "w": 54, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 54, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0075.png", - "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0076.png", - "frame": { "x": 54, "y": 177, "w": 52, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 2, "w": 52, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0077.png", - "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0078.png", - "frame": { "x": 210, "y": 176, "w": 53, "h": 44 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 0, "y": 2, "w": 53, "h": 44 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0079.png", - "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0080.png", - "frame": { "x": 158, "y": 174, "w": 52, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 2, "y": 1, "w": 52, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0081.png", - "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0082.png", - "frame": { "x": 222, "y": 131, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 3, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0083.png", - "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0084.png", - "frame": { "x": 275, "y": 132, "w": 53, "h": 45 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 4, "y": 1, "w": 53, "h": 45 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0085.png", - "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0086.png", - "frame": { "x": 55, "y": 131, "w": 52, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 6, "y": 0, "w": 52, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0087.png", - "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - }, - { - "filename": "0088.png", - "frame": { "x": 107, "y": 173, "w": 51, "h": 46 }, - "rotated": false, - "trimmed": true, - "spriteSourceSize": { "x": 7, "y": 0, "w": 51, "h": 46 }, - "sourceSize": { "w": 71, "h": 46 } - } - ], - "meta": { - "app": "https://www.aseprite.org/", - "version": "1.3.8.1-x64", - "image": "451.png", - "format": "I8", - "size": { "w": 339, "h": 221 }, - "scale": "1" - } +{ + "textures": [ + { + "image": "451.png", + "format": "RGBA8888", + "size": { + "w": 281, + "h": 281 + }, + "scale": 1, + "frames": [ + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0077.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0078.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 5, + "w": 69, + "h": 41 + }, + "frame": { + "x": 0, + "y": 0, + "w": 69, + "h": 41 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0053.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0054.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0057.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0058.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 63, + "h": 43 + }, + "frame": { + "x": 69, + "y": 0, + "w": 63, + "h": 43 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0055.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0056.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 66, + "h": 43 + }, + "frame": { + "x": 132, + "y": 0, + "w": 66, + "h": 43 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0075.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0076.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 198, + "y": 0, + "w": 65, + "h": 43 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0079.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0080.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 3, + "w": 65, + "h": 43 + }, + "frame": { + "x": 0, + "y": 41, + "w": 65, + "h": 43 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0051.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0052.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0059.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0060.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 61, + "h": 44 + }, + "frame": { + "x": 65, + "y": 43, + "w": 61, + "h": 44 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0073.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0074.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 4, + "y": 2, + "w": 62, + "h": 44 + }, + "frame": { + "x": 126, + "y": 43, + "w": 62, + "h": 44 + } + }, + { + "filename": "0037.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0038.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0081.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0082.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 2, + "w": 63, + "h": 44 + }, + "frame": { + "x": 188, + "y": 43, + "w": 63, + "h": 44 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0049.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0050.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0061.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0062.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 0, + "y": 84, + "w": 59, + "h": 45 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0071.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0072.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 1, + "w": 59, + "h": 45 + }, + "frame": { + "x": 59, + "y": 87, + "w": 59, + "h": 45 + } + }, + { + "filename": "0039.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0040.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0083.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0084.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 1, + "w": 61, + "h": 45 + }, + "frame": { + "x": 118, + "y": 87, + "w": 61, + "h": 45 + } + }, + { + "filename": "0089.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 179, + "y": 87, + "w": 57, + "h": 45 + } + }, + { + "filename": "0090.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 179, + "y": 87, + "w": 57, + "h": 45 + } + }, + { + "filename": "0091.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 0, + "y": 129, + "w": 57, + "h": 45 + } + }, + { + "filename": "0092.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 0, + "y": 129, + "w": 57, + "h": 45 + } + }, + { + "filename": "0093.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 57, + "y": 132, + "w": 57, + "h": 45 + } + }, + { + "filename": "0094.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 57, + "h": 45 + }, + "frame": { + "x": 57, + "y": 132, + "w": 57, + "h": 45 + } + }, + { + "filename": "0095.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 56, + "h": 45 + }, + "frame": { + "x": 114, + "y": 132, + "w": 56, + "h": 45 + } + }, + { + "filename": "0096.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 56, + "h": 45 + }, + "frame": { + "x": 114, + "y": 132, + "w": 56, + "h": 45 + } + }, + { + "filename": "0097.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 170, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0098.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 170, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0099.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 224, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0100.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 0, + "y": 1, + "w": 54, + "h": 45 + }, + "frame": { + "x": 224, + "y": 132, + "w": 54, + "h": 45 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0045.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0046.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0065.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0066.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0067.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0068.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 174, + "w": 54, + "h": 46 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0047.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0048.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0063.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0064.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0069.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0070.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 111, + "y": 177, + "w": 57, + "h": 46 + } + }, + { + "filename": "0041.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0042.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0085.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0086.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 60, + "h": 46 + }, + "frame": { + "x": 168, + "y": 177, + "w": 60, + "h": 46 + } + }, + { + "filename": "0109.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 53, + "h": 46 + }, + "frame": { + "x": 228, + "y": 177, + "w": 53, + "h": 46 + } + }, + { + "filename": "0110.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 6, + "y": 0, + "w": 53, + "h": 46 + }, + "frame": { + "x": 228, + "y": 177, + "w": 53, + "h": 46 + } + }, + { + "filename": "0101.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 54, + "h": 46 + } + }, + { + "filename": "0102.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 1, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 0, + "y": 220, + "w": 54, + "h": 46 + } + }, + { + "filename": "0043.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0044.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0087.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0088.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 57, + "h": 46 + }, + "frame": { + "x": 54, + "y": 223, + "w": 57, + "h": 46 + } + }, + { + "filename": "0103.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 111, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0104.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 2, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 111, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0105.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 166, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0106.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 3, + "y": 0, + "w": 55, + "h": 46 + }, + "frame": { + "x": 166, + "y": 223, + "w": 55, + "h": 46 + } + }, + { + "filename": "0107.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 221, + "y": 223, + "w": 54, + "h": 46 + } + }, + { + "filename": "0108.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 71, + "h": 46 + }, + "spriteSourceSize": { + "x": 5, + "y": 0, + "w": 54, + "h": 46 + }, + "frame": { + "x": 221, + "y": 223, + "w": 54, + "h": 46 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:e303c68c1876b77078f3e1fd4372a4ce:84139d6b94cea0f3c45dbd8fa7109c3d:c79e17c206de27e3b7f1ce96f7df8e51$" + } } diff --git a/public/images/pokemon/451.png b/public/images/pokemon/451.png index 716e8a0804135a9fe84e338b769805352f50713a..fac8f5a0170b216f4535ff54e1ea317807f422cb 100644 GIT binary patch literal 6518 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 literal 6490 zcmYkBRa6^Fw1z1ZZzwJSS{#B0NFhjZC=_=)xChrlDei?92@tHf2W^4iE~Qw35GuI4 z7l+$(?tQrPFnjjDzx}Vh=55x*X=x|}h=4>`SXcm+R|+~VCoOmVhxor5$P*FOj<;4+#O|_fYTsZkl%w+^LoIA>(Ee zM+IF*7voUlMb(?l(MFuSv)e-|@r8?d+QswtnC7Yf@C>QLf?TPGo4e#Es?HBNEq+U% zKfS}aNs}y|P8=Q{KK0h({;#>r?~VfaSxx`XL?#UXgXrllhK!z5W-qI;l50Ot7()^? zd;YQZ>UZO@b$y`0|4#JqjkTT5Vd z-@pDYbz@N7{O3SA z`lY~~_~r!nxU{HjwX(LK|0T&N@s-2Yqb%r{1nKz~JDV8#>{IgP3r@B}bZ{^uZReWW zsH4~e6*`cTz2B}qC|1fZ)K=qs9L`2pR^K;97b4MJcGjl4bM<_^J4x4+pG&Se+(c9* zxt!4YB~%7n7U-ZlMa#M`rs>cZ&ggwj^>g>;MxW$~O5VOyC1IXkvl6QF>zzi|xb(zBZpa-!{`kkNmlKK`YCFF=YRy|3%{6l6@kxY55doAC z05L?lLv+JTCH!1Mgtvm@lU~j`L*bErmRFXh=+CIWPw3rRR+lYE@L~T@^8;#RdtUTS*yLiGsA>QlVY14Xb9PqgxuuoE>Ce1Gfs8X|rq_Pg{ z0w~eZRzKpPD;>fPDqiD36b^{4mx{#bwb22&M8?20wY;e@|4pf4Tr_*ho5tW8rI?S% z1n%E2V@5lW9W7lViR%9+nGN;*Fkj+A8DyI{5A4+rM0Qx|Np+h!`4zaC~ zc>UeC$eCT2cxQ8COvF^?G#mJXm zsf4%qZks;`m4RwO)fU@<%n4Gw`3@S4!4T?d!bZ1LUK=ysQ%@~%?-aKBa@2%rZz_=D zv&;RNL^O3mQbImbG>Bq7Q(YV`!)ecB$99icuHvMF2r`&54 zUAWr2olrLQWNEuQD1i`eX-~M1;ywI)lWBO;$0(_0IIrQJ;W1q{3Go#573=KtNG2Oy zE7?|-kdbR|QrkH7Fx>69{cM&* zH-i<&-%UhN*bGA41r#_j-_EK-WrbI;-71CyAPdIC_?y@bLkt7nyB6SISup_SJMlCnaXgBm`?&q03dg zCHnn10t~bhtmVZ2oQ_p-XNw1@aT_PT`GgTu1lK~KWrcs;g}hpc$k=PvoLy))6j?S! zWtTC5j4p;x>|36V5lnXSO`6(pKyM@Gz8-rIal7Hzo53da_tCD%PpLSTrE^ zWOQq)P^SN$AOap!t|hNH4kE4Z>SY>W<`y~yd$~PIxRb&oY*K;e*$@rz&J)qz&_D4) zD;Q-5`m2Cnb6RIBEISW!{S49kMst!=*-T7{24J?Lx!BiPyeshsZI^|bUsEbp%r9so zfFMB*B11_fWB7&3;jm^RDJ?}5&$A+4)$>0eWN?(_eo^h^)}wUAH=UT$Zo5FMFfeq>BM{bwzY&A8P5l3 zOjyK4?r>As_S;; zk>j?)_zfqzm4S!x$MgBgu7v;Kvlvn>)_*;3@XT$}Vt+Y8=FR8hqU@#7x4-0Gcs&M3 zIksc`AulzP^+n9ZEl)prhV_i}g`f|D$j$>QVkd5i$T8bD;oDmXSkNZ;)2_>!hv_9f zDf9+LGNZdvc)W7NPwQTc%Zj0%h!Y^WSeHeJ^XZ$D>ltHStAQ|1fazqgp{d5#s-ISo z^rxxl!xBfVNxxX!)?LeX8kyC!=EfaHL7OB)lO|??s8W8hoM8}+clDgCF{&U4_*pf6 z7MV?+IrtYg)Fig=1haYuTryMXn{-yTE67jJysldp=OZk!so5S^7_?zuHeknprCqhC z5hu6mE4WZqgiE}LNJjm(%1bXAf1RCHRf*CV^PQ6EH%okAsonX9v}S+Q1mt{v1K#k@ zd24ipG7l!{^R8L_sbhZqg+bjMFDq~wI$w^cQsKp=3b}L1JSNQt)^AJ(nVwzEr`5 zD?v2e6@ZB@E?L9PuQ=BrL{uft7)=Zj^+{Y0*B#{^eK&lri7){Z;kvJji-#iI%j5KEeWlrBrW~jRJkTSuRUNgI%y4u} zaxV_gUUFrtm{ttr9&K!b|xx&6UcAxgHx@zTvLKj zEAQxjlibs4*`uNp`XxzKecLClWr6O%EdDDO>)eKOT_OSTpCyj!8MsEzS(2ckF$zU_ z1dmlBbNNMIx6jlaj)PL+gCtJxwHC*WCaCrelpp{TxJHw1G;>N}xcVg=z9wxO;G!uH zJDq;wFHO;eb@??jks9uZk+!d!!z%+Yc|vvrmz5lSq*G~lmaTUlpT z{s_hcd~SzT>`)V0gJ`|@9?f2#TZQ$b07jzD1BF#JVMmyOTKUzqaG@)oo(eV=)n!dL{hnua zzr}cL4MfYXox#)-r@@52W1oNVk%{pmRTTQE-2HL_?TTxGDmEPm3F$WtGIaR#OeStU z+A&0oH9u6MoIDWF2BB3dKk_3@0vg4&PzkY84^&rVl;%-aeyNiEz^eDw^c;%^-1R(s zG`1xupS&Trjrt4X!cbh-rrTT!%Aq=b{Y!n|Tu#5GU9Rn#wv1%;olc$DdMRg>KH zJ-OzadUmuUIA}DfUXX%& zzj|xrm(-gLJy9|tq_Nwkfp;6wodz*3X#|V{^n+E3y zA#p?i{t&OJPJUOE|EOboeJ`jFkJE2$m=4?a^=lx<1y%(&6o8$HO8>F@X;7HwUSZr_ zNy>*iPeQ+B@NK}PuW0J%gvO0JGWZe5ImA4E9ElXUs}-2xN!)(?yDraVZYSIc)+R8M z)A9*|)OF^r+et`|bjx;A9|b;p3=jypYPN9H@v*lndDO{UleTff0QWF1+tz&XrAjSN zF99NgPR}|d((|DQ%tyE_Kl4`9_QI}=NK?AowiIj%rd_XECHpi2!->MzSuFOV=5t@6?{Oc<<3A_ zr96pYj()%M{9{$>v$KS&$I@@)5AAa(Gfkc+s`~eF3pcUD2zsJnK5eEHAfU#rD@iJg zkZ+u@>zED|j%8rul+3kyNQ0G`U1&~C^9;k+%B#Rf$T&8DXNdR9c|{POa4qQCE==#@ z#lu%T;vGQ}uL0l(+q779bLZF_(^toXnXeRX&E}7aWq3;E{ApCyKSzJzdlT^Xh<{E= zFP;Fz;`eO_k8|1CH{3_&MAZSXxjO(|-*mGAaNgVN*z(CpX!{Bna(Z}T+ZbccB^2f}Gu+ZHgSE=^WPWFc?_sXxttuwAwN{-dKH+^=CU zUx+5$;;fI3!YZzdo(q&c;y$L?Gt1PdrbaO8%;VD8>5W?ZVh22=V}6mYXg<`SVHL zhzCqpco(9*w(G_HT~23yYip+Md>pF^yV(Fz6lU5YNW)m3swXNg4$+dZ3*$yL&LrTu z*?BwL0@%=rA2b$Gp=pA78ljDftS&wt&OSj&e^AL@Qm~@GHa8vA7!&R z;F*g zHbtZAK!einH~P|{tV2Hnr{RW(ULFB&+Oq2XyB?vj<;gl^Fu)wG`HM}W!@igw`5Go| zMW7gqs=%KAQJolNmj*S)JdM(|S;d%UDtw68=U_(KX~X@SNxqpt9zM2RGapcm4AiCy zZ^i}=rAK)%u$uQ(cCCo@Re8(@+nv5_yDm7O+>QAvKU$LHB(mZxV}er*2= znq=X&DkbBz6Ej=^eHOf*tv>NSAjv7HKj=Y4nrdyYj@KHtTlV)A#VJ*0&vY9Iea~xg z_^hbC9&LJq&E!Dh+uh(;UGW@PhPRCQX6*CoBsKA%OR^*^hgQD9QT=XG3_{vvK3XUi zH>m!wQ6GifgT<-ew!!$=D_zdD9_e#}gVE$_xXk-YHqnkzGX_g#D>{**CDg77y7khQ z3bnuJc4`Y(oCSkB5Q;Tl$D<;fd-U^~g%AJiay?mV=dcAZ+DnpDJ&(Xl zG?D%5XFkgEIip(ZYmdH?sDsaIVe;}kG1gTre12se>0e4qGkCXI;P)p&Zgop)r&Qm8 z^FJNcTqWB;1cV0Ci>cRf)}zoyk}mu4!aW?~nkz_(%ULr0-RmUp{syAN4)2|z<^k#= zso=33#e^mCqF<@vxKL)^Z@jw>_&W~S+RBXA8Fou!g`~MHa$Dh5ur}e=E@(_dj$pd} zKEFBUqspeD)?Fm!*hM3JY0@ur=Lz-iPs}G$hgJuNNYiVTtkqjJ9$iWzMC*ZtcA2)KZmZeg;fB-NI?-NBz_)Jsi{n1h>V*74PWacN)H*)mH=GCH*TjNN4(Wvr6cQxMEfA5_`@d5Rj zfcW4=d-5F0DBY53p?~YSJN0r;HF$c6{&R4Xdi?#mu`SfYhK7NxFu2`w7k}IC&fRXy yjbz-DX{Ri^IQYIUZOt40rkxOc`!Mn6x%~Eaoj<82>hEU`rMp8(0U3z_M@m4Xq(czt z6mVYd|MNWWhqKPUuKnBB`E=GgYn^yQeGM{VMq&T}K&GXs3Izag82(*^c=t}mcnR12 z#$%`pQ@h`RKp;a#8yGACwuHmMgi&C^3^1?_1Zr!WogLp^KD<1%v`iwcJi145e4+Zr z0LAS&1OPyPr=_X{3taeZlilmxShjgT?ES=< zY5CiKt;7YnCGWPI%N?sb@~cYj7xZ|hIo;1MWuJGiC@3hboW*4SCzOyZtXt)ONMJRq zRrW$vsb`LDh0f~RyiNyF&4Cq(8^-|*f%X5O*=D)cw|$8s+TIH`xGOV*Wbm9;W{Pw7 z|M2EtZcK8gVyv34xBhy%+E1SO4Q91j;1pGLQ`opVUgIBx+Cvv48qR~XCy6{L-ZzLj zls{8+wWml1o>43bltVEEIjgmv{@XI+1Nmk$?|h77-IeDQYH-@HKLRdEW+>VNeSYI| zeE5kD6POW2WXu*FtNs%#ik+SnV-ESY~%$0<>>d#+)pVZ{hY?%v%u!}P#I>zVw zH7vU`5}bz@EaNf_cO3{)B%9~2`VI0`M&)Th280e>)R%;&$(h#JP1a`hZK&PrxeD6Z zIC&Q5rBj;uH0E$lO3Wc7GEQcE_9m>5y#E}Z8>h^7Tde0aXxHH>Rw zf+ytJHd_U)l@{3rG#(*eP-og{C}2i9`#R^MXh>S={PgW4lJUh{eu~WwTK|^&asL)g$-E&R`eDdBt3=aud3?A~e4oEpLN%Y=zEIT-?KcoF9m?3#+yIWev^(rjL^r5ij z<2y`=cavk{*Wr@qgNYC!Chy-_GN?A{m&;vT;V*2WVjnh+?*X{K5`DUc%In=)Mi!Vi zj+D!ENB2wzdRbiIxj6U%rnii?N>7NJSoS$XudH&udo&cJ1T+~1G6bHE00;sGs(N`Q zKjL6#wv{(tI1qNIrS}A1Yn*8u{~}}4p>&&$HP!A$U7dM%;8%U%IczQDUS<#N#Sw;F zR21ltWxhyRqS!qQ8xFdZ4egDnLVoM98eg=c#O99FZ~gdZc{%m!WJ3gb9QHR53BSbx z85vUjMHZJySX-GMU(!jU6Xq{$C71VEm^aiGE`rwvP;vkDoaJB`5!=!B%l@F$HSa_^ zhTn(L6Ng2c_n`Vs@LEQuqsVYz>es)vlCESnBOJf7pWx5t-|CMO2gMhx7R80}N z9S_(i?zezK?&mkjyS%zm-1&OY7m{dM$L`@h3p6{c718(?7D zlSo$oiq34T=)NoKI>PCk0v}~>ch5wrlV+jYqSIAGdB6kAaOoDCSe{C#6FW|K73
vJfGTfJJZ|KK)uhkUw-P`f=GgU{4;gU*-p|Vv2%-f@JH&%g_{jkm1@BSzWKaR zp21gpY;hFGJ+~afZ?fSC#=dupJa@C^-@2?NL&$Da{98V)Ba_enqvR4QUjlY}B0G~8 z@R@9I^%<9VhxgvfD&WZFdSys7G#L*Sxz&dj4XwjN8AI@%KyH(USJnYQz zr->_@0K=#=qFg0Q+O+?un17Y6Fx9=<033CW7L$<`ZQXJ|43~%8sMzTPzU0#8UrGXI zKq2Y~!-gCa{%4FWCvxNf6HU2EkL1T(UvjSy>jLBeJxwXCk7?3{qT1Uh%Xv*0`9Js;mB{j_a9uoPab{Rn)%$KkqN4eMuVmcH^STs(m(|gwy zeWm>puQ00=XR#T*GQViufReV+{Y=d~k0zTB^fX_J&nkHO8A0bb%hcRT&2sgIFHv8C z!c3J%B%BjM4Dv1#PhcvRN+AA9^~=KY4Xww&-~nkRqXs8b zu86e&H!>{=)Y#Hh0(7-}!_cW=<{;qzAzN^A)oCNkeMx|gh$OYVf<9cNm3Oj<{cFWH zF*YWqr|pB{9~*!jU0IHV+{I;ystx>jaLyOlW=8^RxHpKrY_DG$XF}W}^4Di;-`^wX zEcbo$ImRtUA?kIQjtIWN*JB1xDKRER33BU=fmQ2`Ol3}2@TBqE(zdP$)1t~-O^Hx( z&I2A|@5TyxUIIdwUi~_X71|5Fp$uy{C5e?~9KOKN(M+Ywj!Ls;odJcYKSkyncU>n3 z9pXnxZY34;MGK@MizHk+d>4P;#=Gk}J0uW4O1N*#JyuVDwkpuv{MJj)u0w}JB*sro zr-U*3OA}Zm<7Fq1u8kV!hr9J(A}%x(Bk$NMbOTdLFeP@gRzIqOvxIFF6q5;39i4V zz;0^oU#D9r82g~+(GdDX2rh8L1felpdCXVBD|Xszr#L631)6b1yj4F0jC|zc=nKW! zozpgq&LC@4n)B@h?0E^b^Q=WF#te`e<|g02om1kfylC`&$YRFS6G~#5`SJyxM5T8l z_H7NBH}&S%c=Lrw+nWap*qQwDMf?1cca>s;Q^ymKC!T2(efABxCYon44;_rHW2NsD zHJI(sfB0>k=%SLxVAMBh2>`*m$#d;5GsN3?DqWOhd#}ePS_oMBGv>ABCFJTq63czH zaD}AHp!VziUDh*4s#8S1KOqu+_(rIqKf&=YhixT`yD3c`$_EiO6KWC2yaVk_dn#2J zlT-0lqbs)*C$)O-!S5WK7(rov?tuJnq=(QP-KRSNrlPS0xPX${@2o$qvu`0{g54IH zD?bu~!Tm8lx8t|-;(|1UCcS^y(ha(NVEPgQpS)#exKDJ!)@NzlWR2vuM+`7v0uX!~;3n|dAP9C+bu9y@ae`f+Co{ln&6emYDrK$Bd^ z<8`f-`RlQ2!uu9VKwRyM+`X0f+U57iUf@GMl?NGXXcAY<)JU$n*Y(Z;9z|W;hJ*`2 zO%T;Se}BwmrM>pTWk0=%%@*|+=1sz_EC=oO^5?iJjMJP5S06B<6OgdE$4T&OJy*V2 z!R-3%B>gb1Hk1~2t~)|*Sk)n=-$~kc1JP9}i-xs*S9a*srZVZ@SA5S{k9>A~YW#dF zS_t!5;k91%UhUkLtO>|8M#%}5B(~$4C9DK8xw9W=D_+x0Djo7M*62BLK6?T?*BlZ2 zm{hPz@MHiQ2mjKn*UmhJN9+pa+2SV)aqLu}5iEl9D{~gsejw+iH0lr(1Df1v?nUU1 z2->$oJks?FM0^7^b?lkzwS# z3Dz8@1ZHJ}?5+jH@U8*+BabuIQqQ?V-hD+#%vbyzH*z>8WfY}IF#Gt-nWB0RPr2QO zTnTezXki)=g#beFC5LzAyU8dG(l;1dIgr~`5ks(?T6u0Krh3xeB$X5sU+QabO%~ud zRE8RVhnRKbMf8wp$f^S)=mZWsX8;A4uZx5hv#Bl9%a%VT{6fIpnU-!hbgHnAYxEpa^_u4 z(u#9;eYDy3=m`!Zh>35&JoYvP83AE_JTWhsAQLJ9ViHuLs+5ymY);V+XTP_1CE-p{ zQ69ylA@7oYVl_3Z6p6@_cpUN#v57+g#y|Bi()p9vUnx@&ub62K79_p!Sp3zOC`kN1@`TW%IRb#+Y|fD$aCSmH`>7Gil))v=njJ(N92u zrB$3`+5nOo!YF>l&H=o2fdg77e;fa-EF0(Pw45SCmMDCwRN5Z-kFz6{<8}KZ603=X zovO@uy*Qem6r2BFWX91Bb`SrzlQ~8|^^CJAHhhJ<_l{wO)|yH5%`4eg{wQ(f`$mb zDoZ&`N)(%EWF5(hE1WB)*2DmR`FU?eDJ_M|U%`b7@HPp6e3gvs{K5{3R;75XF7ZC$ zO9Ku=FqNnW+=;C?A-Q5Nkl;)0K__9gFI8G2HWNzQ7zg8osaGhXN)gDZuS>1S=m-vp8m6t?#gA+GBzgj6aM>zF8a;<9qM)-lGr|73&8hOTFM6-;#L$cA8 z74z>W*NUYpajnL4ZumU%+!L>tpQ2K;N*Hx*Nr~IVAK})7a!lPHpNf-uMzgzzB0nLh zQ>t+Ll{}8CjPLX(NmwSjLEX#k&dcY{foyh`_1rBSIC9S9;P=Kn$zJC4*#vHQx1*N7OY{w!&wBeYDATo60s6T~Q*-es{OhJx*YyR@41%MdvM4Zr{ivV(~ zu3E5OA~Gg;H2)r8Azko|O+p*&HRS0uwL9rH_^@OSFLcjBxbc&bneuAd%1HHeHbd9m zh%CtwV}55|T)2BEN16UZQx50^ZVv%ZBR6CUqgzq}rk&Gwus(Pq&o=fYu#9lfI|uBJTIlb%`mR_#%o zfLgM{N#r%UAE+he`Cg4$#^Xt~7BbY(jO87~;rbMq1t7(VJ7C21!*{$9y>yK5!Y9dA zOVWN$sCZ(p^FfBN3&2&$;F^~&TI6|xLm!Ug`jA- zDDIXtESkLsQp zGxTK!tx7-9&qbcP-G{HHzS+c_^V@VN08h18V2FMgw^m{yX1ie!{Yb8 z7~b#+XK=88Hb1O+eQp*uY2G=xDHDgK{F+^IY*x&tVX3BWiT^-UtjO_q4K5g3Op!OA z@?<6AE?f04G3k~j9|K1oyLch*n>h#fHRX&$)L$J=yO>$Kh(=~tF1V_3Ioi0QS)kgK z;o`%)mT^^8$Sipnftm*ZGlD+@+FvmkT~mT|?sfe^9yqVtT#wzJN91 z2@4({jYOkREvWk$|+8 zjM6(WEqLK&?Iv3XkA?r7MQ4hbN-q{l#aP*gt0sl3g-!!c{iK#L$lSD?EOI&kHhY0w zWBvqY{cI7!+k6Z$Lnd;&ktV)<7&3$jGA{gWdK z!S6G`1{4Q+2fB``4uOu%a(Fd>cd;{xYA*Cq36coj%v!R-zJ6V)cOs&`h=15cNf!v% zk&#zWtMw)}D3XmKubiVkowI~-45-3L#m;eL2ISM=MDQ0oOfvgbX-9ixu$dwW$IJ~s zeV!ElNoT_Qyz13f++^YdntW0glNY8n&AeV4d>T%Lyw$6!_)zSJ)8W^jwd7Cja6*t1 q$5QgcJ6Fs_8$Pf8D>{)_941=6OH0X<(feN!fR>uRYNN9K>;DH;yfyj& literal 6495 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 Date: Mon, 7 Oct 2024 15:03:00 -0400 Subject: [PATCH 02/24] [P3 Hotfix] wrong content keys for Weird Dream ME (#4607) Co-authored-by: ImperialSympathizer --- .../mystery-encounters/encounters/weird-dream-encounter.ts | 6 +++--- .../encounters/weird-dream-encounter.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 71e8491df69..8e15faee853 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -199,11 +199,11 @@ export const WeirdDreamEncounter: MysteryEncounter = ) .withSimpleOption( { - buttonLabel: `${namespace}.option.2.label`, - buttonTooltip: `${namespace}.option.2.tooltip`, + buttonLabel: `${namespace}.option.3.label`, + buttonTooltip: `${namespace}.option.3.tooltip`, selected: [ { - text: `${namespace}.option.2.selected`, + text: `${namespace}.option.3.selected`, }, ], }, diff --git a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index d858d631596..3fd0ce83f9f 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -164,11 +164,11 @@ describe("Weird Dream - Mystery Encounter", () => { expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); expect(option.dialogue).toBeDefined(); expect(option.dialogue).toStrictEqual({ - buttonLabel: `${namespace}.option.2.label`, - buttonTooltip: `${namespace}.option.2.tooltip`, + buttonLabel: `${namespace}.option.3.label`, + buttonTooltip: `${namespace}.option.3.tooltip`, selected: [ { - text: `${namespace}.option.2.selected`, + text: `${namespace}.option.3.selected`, }, ], }); From 0a4c12387b5992e1f14f53a0f8e6b493a0390ad0 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:42:27 -0400 Subject: [PATCH 03/24] Revert #4607 (#4609) Co-authored-by: ImperialSympathizer --- .../mystery-encounters/encounters/weird-dream-encounter.ts | 6 +++--- .../encounters/weird-dream-encounter.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 8e15faee853..71e8491df69 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -199,11 +199,11 @@ export const WeirdDreamEncounter: MysteryEncounter = ) .withSimpleOption( { - buttonLabel: `${namespace}.option.3.label`, - buttonTooltip: `${namespace}.option.3.tooltip`, + buttonLabel: `${namespace}.option.2.label`, + buttonTooltip: `${namespace}.option.2.tooltip`, selected: [ { - text: `${namespace}.option.3.selected`, + text: `${namespace}.option.2.selected`, }, ], }, diff --git a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts index 3fd0ce83f9f..d858d631596 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -164,11 +164,11 @@ describe("Weird Dream - Mystery Encounter", () => { expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); expect(option.dialogue).toBeDefined(); expect(option.dialogue).toStrictEqual({ - buttonLabel: `${namespace}.option.3.label`, - buttonTooltip: `${namespace}.option.3.tooltip`, + buttonLabel: `${namespace}.option.2.label`, + buttonTooltip: `${namespace}.option.2.tooltip`, selected: [ { - text: `${namespace}.option.3.selected`, + text: `${namespace}.option.2.selected`, }, ], }); From e962ac1f182d33e6f06fef1858c49b7ffb90c4b9 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:47:23 -0700 Subject: [PATCH 04/24] [Beta Bug] Prevent duplicate move failure message (#4662) --- src/phases/move-phase.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 94093188571..d3a2a3329fd 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -128,7 +128,9 @@ export class MovePhase extends BattlePhase { this.lapsePreMoveAndMoveTags(); - this.resolveFinalPreMoveCancellationChecks(); + if (!(this.failed || this.cancelled)) { + this.resolveFinalPreMoveCancellationChecks(); + } if (this.cancelled || this.failed) { this.handlePreMoveFailures(); From bb98bc2f8e296de5b27d644193d284be40062625 Mon Sep 17 00:00:00 2001 From: Blitzy <118096277+Blitz425@users.noreply.github.com> Date: Tue, 15 Oct 2024 04:17:20 -0500 Subject: [PATCH 05/24] [Balance] Evil Team Update / Penny Adjustments (#4577) * Update trainer-config.ts * Update trainer-config.ts * Update trainer-config.ts * Fixed Flare Grunt's having Noivern > Noibat * Revert Inkay change, Change Penny * Give Admin aces canonical genders * Update trainer-config.ts * Update trainer-config.ts --------- Co-authored-by: Madmadness65 Co-authored-by: damocleas --- src/data/trainer-config.ts | 63 ++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index bc6596c74bd..bbeecba84e6 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -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) From d5f87bbea76428677003276c033cf07beb85c151 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:02:02 -0700 Subject: [PATCH 06/24] [P3][Beta] Fix missing move text when a move fails (#4664) * Fix missing move text when a move fails * Use `cancel` function instead of setting `this.cancelled` --- src/phases/move-phase.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index d3a2a3329fd..f50cfbd78ac 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -147,8 +147,9 @@ export class MovePhase extends BattlePhase { 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(); } } From 21b71595e0292a6a30503dc9d93905c8ade179bc Mon Sep 17 00:00:00 2001 From: PrabbyDD <147005742+PrabbyDD@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:04:26 -0700 Subject: [PATCH 07/24] [P2] Attacks that miss against pokemon in semi invul state that have abilities such as volt absorb will not trigger (#4663) * fixing issue where abilities trigger in semi invul state * fixing targets --- src/phases/move-effect-phase.ts | 10 ++++++---- src/test/abilities/volt_absorb.test.ts | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 581cd5ff017..e9bff882367 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -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 @@ -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 diff --git a/src/test/abilities/volt_absorb.test.ts b/src/test/abilities/volt_absorb.test.ts index 07907a34566..ec82b00ec5a 100644 --- a/src/test/abilities/volt_absorb.test.ts +++ b/src/test/abilities/volt_absorb.test.ts @@ -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()); + }); }); From d01d85689833dea8acb023478e42b6f6d0b2cef2 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:05:21 -0700 Subject: [PATCH 08/24] [Refactor] Default case to display challenge name (#4656) Co-authored-by: frutescens --- src/ui/run-info-ui-handler.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 39927f8e071..1976d5a997b 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -587,13 +587,13 @@ export default class RunInfoUiHandler extends UiHandler { const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; rules.push(typeText); break; - case Challenges.FRESH_START: - rules.push(i18next.t("challenges:freshStart.name")); - break; case Challenges.INVERSE_BATTLE: - // rules.push(i18next.t("challenges:inverseBattle.shortName")); break; + default: + const localisationKey = Challenges[this.runInfo.challenges[i].id].split("_").map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join(""); + rules.push(i18next.t(`challenges:${localisationKey}.name`)); + break; } } } From 196633562775b04920956d913c5059ddb9f39a31 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:13:54 -0700 Subject: [PATCH 09/24] [Refactor] Add type inference and support for simulated calls to `ArenaTag.apply` (#4659) * Add simulated support for Arena Tag application * Add type inference to ArenaTag.apply * Fix screen tests * back to `any` again lol * fix missing spread syntax (maybe) * updated docs * named imports for `Utils` --- src/data/arena-tag.ts | 189 ++++++++++++++++---------- src/data/move.ts | 4 +- src/field/arena.ts | 10 +- src/field/pokemon.ts | 4 +- src/phases/move-effect-phase.ts | 2 +- src/phases/post-summon-phase.ts | 2 +- src/phases/stat-stage-change-phase.ts | 3 +- src/phases/turn-start-phase.ts | 2 +- src/test/moves/aurora_veil.test.ts | 2 +- src/test/moves/light_screen.test.ts | 2 +- src/test/moves/reflect.test.ts | 2 +- 11 files changed, 135 insertions(+), 87 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 71cf11fa06f..2bd6ae09877 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1,7 +1,7 @@ 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, PokemonMove } from "#app/field/pokemon"; @@ -36,7 +36,7 @@ export abstract class ArenaTag { public side: ArenaTagSide = ArenaTagSide.BOTH ) {} - apply(arena: Arena, args: any[]): boolean { + apply(arena: Arena, simulated: boolean, ...args: unknown[]): boolean { return true; } @@ -122,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; - arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + if (!simulated) { + arena.scene.queueMessage(i18next.t("arenaTag:mistApply")); + } return true; } @@ -157,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; @@ -249,38 +257,34 @@ 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) === defender.isPlayer() + && this.protectConditionFunc(arena, moveId)) { + if (!isProtected.value) { + isProtected.value = true; + if (!simulated) { + attacker.stopMultiHit(defender); - if ((this.side === ArenaTagSide.PLAYER) === target.isPlayer() - && this.protectConditionFunc(arena, moveId)) { - if (!cancelled.value) { - cancelled.value = true; - user.stopMultiHit(target); - - 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; - return true; } + + ignoresProtectBypass.value = ignoresProtectBypass.value || this.ignoresBypass; + return true; } return false; } @@ -296,7 +300,7 @@ export class ConditionalProtectTag extends ArenaTag { */ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { const move = allMoves[moveId]; - const priority = new Utils.NumberHolder(move.priority); + const priority = new NumberHolder(move.priority); const effectPhase = arena.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase) { @@ -460,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"); } @@ -497,12 +501,19 @@ export class WeakenMoveTypeTag extends ArenaTag { 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; } } @@ -563,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; } @@ -608,16 +618,22 @@ 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; } @@ -651,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); @@ -702,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)) { @@ -807,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) { @@ -818,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; @@ -853,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; } } @@ -879,8 +912,15 @@ export class TrickRoomTag extends ArenaTag { 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; } @@ -1087,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); @@ -1111,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; } diff --git a/src/data/move.ts b/src/data/move.ts index 2d91363955a..b0078c32f12 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -808,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); } @@ -1026,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) { diff --git a/src/field/arena.ts b/src/field/arena.ts index 1e164903e9d..ad1846884fc 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -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, side: ArenaTagSide, ...args: unknown[]): void { + applyTagsForSide(tagType: ArenaTagType | Constructor, 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, ...args: unknown[]): void { - this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args); + applyTags(tagType: ArenaTagType | Constructor, simulated: boolean, ...args: unknown[]): void { + this.applyTagsForSide(tagType, ArenaTagSide.BOTH, simulated, ...args); } /** diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0204672cabd..8eca37f38ac 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1538,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; } @@ -2605,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: diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index e9bff882367..dc880f85e23 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -141,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? */ diff --git a/src/phases/post-summon-phase.ts b/src/phases/post-summon-phase.ts index b99c0b90fd8..617bb8b1cfe 100644 --- a/src/phases/post-summon-phase.ts +++ b/src/phases/post-summon-phase.ts @@ -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) { diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index bfe19ea9ca5..4c13b883445 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -64,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) { diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 627cee4b06a..25c079007fd 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -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) => { diff --git a/src/test/moves/aurora_veil.test.ts b/src/test/moves/aurora_veil.test.ts index e71d4ab9d11..243ba3a3269 100644 --- a/src/test/moves/aurora_veil.test.ts +++ b/src/test/moves/aurora_veil.test.ts @@ -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; diff --git a/src/test/moves/light_screen.test.ts b/src/test/moves/light_screen.test.ts index 2308458003d..11b8144bb4e 100644 --- a/src/test/moves/light_screen.test.ts +++ b/src/test/moves/light_screen.test.ts @@ -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; diff --git a/src/test/moves/reflect.test.ts b/src/test/moves/reflect.test.ts index 41a10988552..b18b2423895 100644 --- a/src/test/moves/reflect.test.ts +++ b/src/test/moves/reflect.test.ts @@ -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; From c04d81bd65364726e252754866e0859e93f25b56 Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:02:30 -0500 Subject: [PATCH 10/24] [Ability] Allow Power Construct to transform 10% PC Zygardes (#4626) * Allow Power Construct to transform 10% PC Zygarde * Add additional test for 10% PC Zygarde --- public/audio/cry/718-10-complete.m4a | Bin 0 -> 20709 bytes src/battle-scene.ts | 2 +- src/data/ability.ts | 14 +++++---- src/data/pokemon-forms.ts | 4 +-- src/data/pokemon-species.ts | 4 ++- src/test/abilities/power_construct.test.ts | 34 +++++++++++++++++++-- 6 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 public/audio/cry/718-10-complete.m4a diff --git a/public/audio/cry/718-10-complete.m4a b/public/audio/cry/718-10-complete.m4a new file mode 100644 index 0000000000000000000000000000000000000000..94d953605539c1ef2eef1252a75a6ec5d0b63f6e GIT binary patch literal 20709 zcmV)pK%2h+0010jba`-1G(jK$00IC_G(jL~b8l^Fb8j*L000PPa%E)z08Hy`WMOpN z08C+aV>U1@HZCzRFaQFKuUqQ+7#5boXI$Q1No!+zt#3@bXBeotAQ>*HfBzWO6ujO3 zj=;Zq{b*~jJoABvxc@)c)*$H2+DruPZJW;606#UvpAe|SQ-wTd@O-ailK^X=RsqJ_ zdxy9*tR%)!&(Dkr`yLxX`nrWqij$t}pR3Qo3a7@(PhRU4ijUvyUr=Xs{ifL%aPrza znFv_$5R>NP_s)T|MsOQ1rM_~v{w}SjrsD}hqsqoJK2R;n$bLtjVl5mUJ-^TOOkpq) zy>5y1{K3PFwti&VmsErj|0^`%077sP2bgwx1{t|~WcZR6#*y2>lTwpo6O02vN(ITm z#PJC}y_rCS(9HXnyB=m&tCx-1VDK{ci9=7LdU|u$Jpi0p6LH3M)2n`kL`K`6A|dE^8p$2R+3U-< zaXty<;(lH2B=Fhdet!8FPvt+X$?|TTPtTsDC+8n6lJ8u|hp9X&Ps0ARCDD8N4>o!* zhqrygL&+UnhmAabPj&5rUW(?VJ)i0F9yIa!4;1n;98bq2e1niD@u16%_80(IBw>4cy>;D&8tu(GG z-|zp9eTV`6f8yW)YmS}p1RQs9kObsgA;`TdE6E)cS!MY699j(4Uykp*Jl7K^dniwnBc2B#>>#smd+CCP!!)Ge}9Y zxNeS1Se?N}Y9)GwQ|Kj1f>!yEy$f}rL=-BxRtZkDQ-a;{3ScpeIT$gPPzKUku2x5p zz|pRpCIMwe*&6wi=hlIy=Bo-ECgzhlESj1XR?}IOQXyweOv(3|?qrDixB*o6qU_yO zh3}WUnWnIr!+cSarLL2DkP9M3|No)=yR~VQOLqwhS}8<9=9-|EBWpzlD(@qurJ;LG zio?yQqB3}qGZ%7L5^3U3wXvuwROaVEjdkYe$(MIJeUx&wPkmSI_4RBKefe<jhKN_FPXBlqZtmgz*{DSQ+HAyW%)44bzn&4^TwX2qxJW zn+g804U9_4@jOBa$1yBs1eFKY1Ih3S!lh8*h|t23o`Ou!5Po2uc7ZHQh}cOS1#3fo zPXQ}{0a7=s<<(eS-Bq$ze|(w6b2#UwGzlW08GKPv|NKXher&{~?QCMG7I`{pPNT?} zWG6U@=^tG4kH4MPe2*EgahjGP_d(<|KCM3a$_p$i36<$om2|boN@bd@8iLifd&=H# zR$QNG&Lg1UfnY+ls$^>>Giwz!e|OMwS)<+(i%r~o(mfcl-q=uDy zR^3{7;$(5E)}R8#fI(+znF%>Kptb@X4*G!hB*Zj{+Am8BMlOxDK3_%y>d@(qALUICrhw)<$bO-nOzuHBxTR>LXA6NAP}#+r6l6c-;z`%OXd& z$%TK1x^7N3)^5n<>Ow(Hg1rxpHm*BeR=&+t9<`sf6={Nmw>(kS%rhQ481PO{Ji9s! zWlE>uL1y!VlAPPK*wz^0Y}Gh&-_m8mFN93}MP`L6^MvTBJ%#!6P|0%ei7~8|!mR@}Dg9A$zxf%*=weePkUuIQu`3It zUa$t|+XhiP0{U)>K2wZ`OEp|=^f79+v%9#8 zrZ~5CR@l9I+=bn&)Nq3bLRLSMLtM^}Pu(`YV;QOD*1<+KLD8Dx=jrqn8F&KI*THoG z27XyE5>=($CUu!ewQFit!${br(lx~X#kjgc?6S>0xt8|=sAnjJQSaJS($N<}oF&#u zOr_GbjfSb1*yuv|a1}8-&zdp>5q(B9`MQh+7cCV5iff!hl`|5CK}ss38Z;ElcQ`rp z0YQT-sO&6HxrpbcC({a0BN&`|q#B$Fxom3=M>V(%4?DNmS?JDLLbO zN=me;Qb6HTJmTm7pz(c_s5;GGT#r4gV(k&+uWk$}3xkrB!Cr%HX#V`&_Tvn2mUla%JN=(H{9H0ZLbIEW<9 z*;sT`5qhqj1(#PKnh*uNxWrX(sIwv{OwgEiZq%z&KKj`r7v7TsN_mQx%tL7!Vr12_ z7A4VSyaIRxMM>n@o+qGAHMtkqffvcWePj*>siecsSqK>_X^`irGq!_5w&FzqqRoVf z#Ra7}M~PFSXT>4`Dctf=!vnhVa0it@nyyapR7#nMJechkCD)BR`bRx0n2?4GwZtPw zBblg1JTgcLItn2smL5!zNhXx53p7d6YkCCf1eupVg1=9j2o5}3Fua7i905c(tTfnjB@iL7zigax*E8|+nr4^A@>S;*QlwO&QdF=1Ktzjfhhn%@Kd*LoV@hhiwfm)CkVvfcyO zv=CVZsqVQLT#So&9o=@=I0V!cM*BMpD!8hLQ6GAYD-)I`8y$TJ_E1ip=3A&Wvr5?U{CwT z2m%680xoo#N=%OtAX1RVK+cZFAj*U%4le;nH>@#+DjdQ>(^y}|^^&8{EpA5wrg`S- zOiZhks4kN8|M&{FO$pwB@$Fx1pe_cSGMalL)&>mgTOU@YaTrHKzDjA7@-DS7r`o|Q z8T_ge@YJ)yv)!qbu+)gobh;%}YwhMtWeP7N&q2E6d6us)8?WlaPERLQMLqj!&|s^7 zY?5uRd-Rs4sX#votIe9?kKXIGaV>GQvxsKo`p4&9p+$>YOXYtf3Cv4Jh2Rxsdm0=^ zC!v=r>?kCMoCayz?FUUBt(MO3IO_KXyvIYC&{PQ}C*Qdl4&|gH+4^BF&66kDe5^}= z`5c#}H)MFfH7D5nF)n4%$Z9+iOS5=LpS$=WKS6OaMCasj7~6k<66RclhjQ_8E|21( zT!T1A8_$UJJ^2V&GFsaN__re`$^33ZhUN*IWKU(8C(^oE4d+1M0Y*2hF@`!A#ip>o zrE6aEtn0_Ez7C@`-RXSro562AU>T*%wS6L@DpE^7{|g`A5AKD zwoxq5t-9{H_Pg!m)U@A5Yf}rjqf?-u-bckJk5NJQqc-%m`y8ZqkxB_agjEL>z;sdN0meZEW3IDB=$~G)GF=;;MNuM z?C|RQ%M2muOIKhIkYp&!g$ta!D?#D`z08?eB5d3eA>FcDG2h_4Ej=fOyR-Ruxm`g$ zpAILH(2^%$C*n^n5a}6G96Q0pxoz;H+dlZ$P z7r+5RH?CAzDe&|WO7I(Wf>(s#MB=6uuZD76>hG$YKuW@xY$9yp+d$PryY973E_L0Xn{Z-S1eI}^a*PWD>o9tA_0kewxy z+lgSRLYh5+E$>MWWfb?sW$-NrI>$&uTm(D`z=3<#Uzk8rh-CAHVGn^*ttI!;e_fUL zYZ0t$Wu#R&%|1OIDm%sS-vSYE0UkH5l!y}%2qQp=U;Y)%=a)TSt~`0?>)-J2#|h2r zo;Q}P3c9+G5%SbheTNB`b|huSB@Z-UzbI+5?=G|~c)S&2p#aeZ5Rz6LJjiL~>`v`I zLTm%MwF&4B9m0@+hH_{#U-H})PvG=Lh%H?b?K#SvGLUKW z`%PcRuJzxW<9>Yp+w;!+=J>`VFBg(W0ED1exmPkhSx;#^Sku;Gb0El>u|H|)8Vf;) ziABqQEBMOClpfs5RPPrlqrWPHIK_pVqHM%zm=|McoZEX$E(d>2@S*0L>jf1=Jpz0w zO($BM_SW9qs6$EZwkr)x4XXzX{1XDt(U=%Yxs>ag>7iSsXr!j@moHPN=hxRI5!uy| z{!izubI*G)0?RGlCtY}8K)yzI5ghAA8!fY9)`c(yGS*M!7bbaSAzK#=jT zr|0p!_FcTombgwpyQ`mtxJ(63iadO^wtv&2T!l^*%XEh^VJhD&W7{#8RmS)WTsl}- z4;>mK4*APnZqtK`J*ZA>B5YJpV+J6$@&E&lCNifXu`Z>l!oG?qD@8!XgYwEMVdSf_ z7v@{h^Aw)Ajk3%RTJ9=L>n@t?K{eOXmd?@5^)NGYosncsfj<2{?9oVf&8xkvUq(PM zxL+BQw9iNKoD0zy&$w?^b1xXgN@{Kj)Lra8t-(<(MSYm%Jrxk&dP+cu)0XwfHMhF)r^8L` zn7&SRpO5GYFhdDxg(o1&0}qS>#IJNr!XMz_wIa&Js_of%tad!>0A8)2WcXKHj(15$ zgQm_uElyFzJzQKI-3CD4>N03{9lk4RX#BngD>f;d^4X-Fq03HX?X)E#q z9m(ZVNLB(_zyUBft#E5JAreDEKk3KK=f-?|e;aRAKO3(4*LGKr2Q<2?e`aiFux5nh762FUp96D-PgjYW? z&tx-o21k3Vid#9at{G)eFy>!8FkHZl+B8%nK}uA$Im3!V)tPL8IPFE@uVAOtJH13D zuoSZC9qv*TQSLr&^ctycM@Mz*kU3_noza`L>)u>;SC5b56^RK~v%#@*swIs~^M!S^ zo{VWv4<2r#5CLic=g<&pq&wahsQ^r>-Xq-#y3IX15%oP_H?S%i4lUl>*Sxyo>My=N z?iWydh4gO6m&Cv=4qc(0^o4(8c7az>*WdvxH?C|%VvQ31-m^X~?Z@$Y_WORi@&3PF z{@u9FD)(?TDka5&V3tpL#5xwD3~0opI3RghvK)VSu=)>XdEYkuZm@F6_BNN~b4MSK zJN?TK(ltgS@nzFk!~YTABtT2U@qb^)Fo-~1^&Z(JDmtDr-qN_4{3XFO8X?N%izJ!- zmCISI%Cq5(uzEnQHMm>2TT@#Vu;9JuD(q+U-#un9LE<<~4Y_W-&D}kI_eBcYZTjJU zcmaM{HtP9wrN(mXfMYI=K`8F z-{n!Bo%J_(7m1;SAg;RO>!35sf`WpVQ0HCU12+-WC|OgEZlOoTz=_qmyCG?k4S)b5 zvP6{;;6MP1@`;L~btGqj3+H!nh&4b0l>-jz&YHb4uG=cEe$vwpJVZbp#(eBkBrQ8z z^e;fo^EmYAU(pekCm8jOH06!X-~nnkploDSC6I(*p;Rs&{(9%ldgGewb6wZ(>qTAF zNtF_=ri)kwQZ`nQqmgrGg=Ot0$qk1DP5ViMlw(~5Ya$-X)hnF>%FI-%0@JrhGl7~- zJ_e%yb`M`4ne`?Eof{W@p6#6X@R7T{ z67ZYI7awo)D_s|Mx)$RKV?~7Uiv#=II-G2O74?(LeyWbm@^#O8Hw>fyY>zyV@6sIfLmC5ZuCjNe<4 zm2z{|yP2w#)XLVf{Q#-spE4&?#od zUvRgky&2^Hf7+Z_@!5orU3R@BlVc9@O3PD16gyp(g2O(2Myt#;`4e{dfQxq_Q0;U{ zv^`!oq|MGnRRA?nQwU0c z#vQ5D#1()5+HlV97c(F@fD;VykD{|G`v{_qDU84k&$)yx<6U?5iKO-5Hd8{7NJy47o%~6GakT9`yH^TC?XqjUYI0%n3B-oEgo=(GaFT%<(qzvr zoslU}!u?ts^U~6mvwokJgH|Fd7@cQ2t+yJJIfW;Vq|Pq9vL`rCyL|_fyW7PQq1{}OZt^O=#d!3I zdc+2n9*`A%jY~)6x!2OkdEXJ!nnRt~vTL)-WHv~vniX=vI5RUL3|WkhQ#xHVE2P*% z4U)WacNBmJxS>b^G_=U{>09&O>Hs`=OxqPkdZn`*ii}9n(ZRS_s$!@DlQnx(IPwCK zK|&-%F$-I`0Y*2hRF+K`o_ z1zX#RCQ`+q1V{l5u8~vu9Mc$rQK$&+L_m_Sb^Ey6%Dq2a`b0HxOp?dlo=sovB{dAr zIK)2xmf1MCZdZ)w?GsbY$_}YZX_IANZk-Y(!~@z{Q>qE8``R(>$+F~0M5N;tduf$6 z;xxtNNXX2X;#ABE3pGIv^{y>}VVukX){-KkVZSHXO&5tbb|3=ZvD;8D#oPZi2lXIF zggif1^6-BzyiNOa7J;N^Mv>2G)PO!N zWQU6c>3M1Z-2#8qy*gi@>aB8f{o^|`8+m`bFJGLlyBVTX;sFF>dd*_2!37G@S>$XJ zOjuN4?_oWa1rb;UwOcn3Xr!NGf~jY+PmmekNkkwj_BbaztU8ifhmN+dm$aQ!)0~v! zpbUV76?;VoAn1_M)T3|#UN@smbVC*bb`p2%!D<()NRy)( zty5CPsIHOIs>ZChKSBxzZ?^KZ@$568(c9?JKB1Tm!EvGs@O_$x**?NMDwWAG?fjL2 zM!9COuVr_8z4u%6URBR~?YBzJ&v_gmM9<_~btTtNM;%U#6B?H_GGMIMlV*^ICYW`3 zkiY@K9b6$)LEO#ZL{r1(O0>d^_`to$H@H>z{0uitEI!W}SQATgh6Q*8$zHl=scP?3DHe^fmaM zX|6l{;g#nZ$~LUxf?%#$6?TAwASeO>Oj3lZ5+dmfV;&GA?7 z@m0TPuQsmw=5ftR*0+d)OY@$Hx^jtS1tGz~-zu+#;Qct-l1G#uQJJogELa!5%;t&|!-g-!^nowQy=FFU2EY1utr zEYQQx+XRyb74TrQLd7L@U{mOZZTDPn-{0db20tJx8}3$t>%3;E_>sQ41Y`Jlv`5qP zBeSxZvR~wPU2oOvfel!VzBzzwSE_Yx?hFN`6!s)X(a3{$Ii22!b30q*i1!8BpLAQ_ z^n8wWQ`G!(KqTIgnCSJ#bz*m=AQhdA zLelHH#y|r-QFbL5I`n00Ot6a(wYg>0qM*DdInLk#S~sXMN)sA_(6G08OTSku+s3nu z*LC&XNIK-1X;+q$kH9n5Yv@E_fTWxUVbt7m=lNQbXXuM5g;>N_1Wr~B`(%AtJLq@_7N ziv>~80jM^nCdz<6ab!41z^;oIh|0nN761c6)2L$Vzz1?~0SK6&ts9(_0uUX~|=X9F>Iv^I!l)xJ`y}1XU_dbyJpO3UPvp%ApI1MFr4N zp&^9~ZeTugw-4aWL`FJa;g$80jPSrJ<}BE5p96@Z0N$qA*;SB*yN zWu`N^otNl>38g?W%}aI5U~nwemxV%ybKHj+7;7DVm9o@tSUNH+#)*+nKwokd;@GR-Io1@>>+CqH|9y3cj-hX!IjJoaBWE zLylfeTM4-nKrpzrC3ytozpl=^-OrHyRECnR1;;}Oot|TEtDFGg@LR&VLn`Fjth#=m ze#yB1Nt8uKZh?64lBh%gV|R8ixFd1BRcdd|ddaf`(6^6f*@T+fU;QG z!g}SGM9?37tX4XM)V;(GvbTrVq-gb3^) zYlZ=sR^wGSm?Rvz`fw=W{wlJH!=Jq1+XEA6Zr!bgycCVWUpU#=DgjoBX&MJ+~= z3Mn$^g&2#v$_Z4YY3`{YM-$-~A6l0Q-xR=T^}9*hMxVSJbegRE*y-CkHMZHQ*3&S| z2osY=gll>5#Z3Drt&^sAD;OC}w#i`JCpt|$vZ5?!E!2k?uDEn1B#25-4xCH!@#>`T zD>(Ab_21^sGso&bi5EDm?fB@_e=f9%TLk5~_g8}HYopaDEn5MuJX=Xd zyE+vg-s_-mwM|K2dkEgZ?WdMJGpp1 z#?+-=w)5l8_~xe_>vts_PCbT{yv)aSYxl<@VMtE%Ekc}laWN_puT z81v|Mv*@IRM2b1YswB>&`#rsISklJdznUlw@r;XH=$&e9NR`G`ARQw5XpPc(6vaBd ztMut4My9)JCtmB&LfAIpEF@1(+JSFWN}x-wH5` zbU}_|r2SREr$_rBRdohAdn-1q@!eKay)I_6dg||!=QU9ogrcF>jAGjMV-+!p%wqzl zccf}n5)q*h5 zO)8qt-R&mV-*k+yMe=;8{mrq`!IphJQ7dkI{3YMy^f==(($dcuH2V<(+Dm5F!NQFq^A%tb3EpJkX0lwCJ$&FZdY# z-_uwci3t2_)M#rMmu3xs0Tzp~&_G<)1_{R=%9wH5!tRemoK&I1tIFr?e7k|15@3hR z4G_vXHko+O@BT@TvMhMnNGR@N+13)3-GG#l%H}j+HC%O^(;_Pn&iCD8T1bO3jZ_18 zNbI%px|VtT`-s>8&QeR@=%PyA+7!XXAd71u4}g<|ML-c^ipb)7dcpt!AOnm9L?y%y zB>={yZCQW-8osmK0&;{Rs1gX-l?Y>6Q6tw$#OS7Tg{aQ8J2*PZXK>Kj(ryB9fQq7| zMUtd*sgBWM09Bz89@*SlDq33lgbM&j{agpkirT>5XyjlFgG(D#K!aCN&tnMn*e4@R zBiT?Pg>DyP*9h&MINfX^QXok)RDkU$@fs7P^!PnL59LlfE#05dE0M9s&s}}~Dg^<+ z0bDn!u*Nh6odI92y6l+v&eb^O5v;}1)R9RS)1l>B6^}ZIW{T{&JEuY(;0c8+G`V*) z#pLdRBb;1Y3-Spo*N_lW5mN>nhFZHwL;_t{h5|xO*hZLQ&8W;dym1kuU7}B+-tDId z*7&_VD8BxYVir2u;#22%p9wc5i)EeUkfmB?(s1h5U*7T}_%E`<%j&Ir=4Sx>HJ z>cIJRS4qhtXScRl8JV%N2WT1Ai?mIOaK6dFq5*&bV&ao!%ue8m7C6>`XLV=@O;f!h z=O7^)MA9_K^$Z9y1%!!RjS~$p8ATGoP?65G3x@9cK_Fnf6cd_V2u-vM5s(21C_&|} zm4i%*po$&9A4r{dDXk7G092R@GYSA3Fx&?Kjo3jP=1ZosTFepvXH>Rl0Ev)F0>aVM zEsWbL_ixZ3jDor5tG5SLRWa^sKD&z9uui8GWuwm`!|6m$12N0!;gfSjkX_{zqq_{N z)RJHU2uvzC^mqYQH>}XMB^i#0VnA2d>HPfnQ_kwR+?jbVUGarp_^PX=qUNW-a87dT z@`@$UBf6qp-n;Kc+uN}`m-ku(H;Wf7IUghM= zg|({SSrSQRWTxn)W1EKOTD8NOKyaAr6JTLurk9!q8Zn`X)sM!VMn(Z7@z#Rl1>j>F zZoO_3zjR6>=+<=9oppg|THvR2E73|>`e>1SlK#3LYM;ng3S#FmrY63W& z=Z(}twh(}O@2y>*dxySPp3PJjIXQ_2Ws3s^3Pi2G0dDd=>^Ca56JZC|IzW=qoq2Q+?`R}Y{^89;moXnsJ zb#W8`7`}yvKH$5$t#X?&tq$hs000EXTbu@Nu%IB?B=f~-6y>+zDx=N{Z0p6l3gl^ zlUY<40mgz#t<5iI+luwfVPAHn?!x&xl7#4rpbK|8wB^77S~sD#C_M%iqCv6XCcB1H zSD$_Oo8ePi%c|78Qlz|bE?r=kO0?+0R=fAgF{7i7!kp(gSr$0sw(Kq<0+oG0aACg}LupTd z0bDn!))-?g2*iN8-0|m@YNn^3efxTGyQf>-q-468s=0kVftUY8>aSP6s_4SpnD=6Y zZ08xJRAYn%2k8{GV)J`0+LqwN|1Vh@;@6D;$q`>R(_{Q20b`1Q%`k2Kr-g(S8)v6k@(qacGb)gAOElW#3mT~|BuL&vJ zMB?@}Ho3e-c3s(171D?cb1)$`L~)goLFY1vM-@Dx^0EaXDlxq`)mb0}yHQjeKpb9h z0&xu%0X=MlhBOGQ>(d2s&whg4?Z4+z0c`*7v+4$RKbm&jv0x-6xqluCJT2e%7}r;= z%)kKx(=)cwSg?4IJ&!pMIDxG-No?jI*ItMgTR9^N0lF|KiLsA-`(;}eMu$mw_+uWe9_P)p z;qU=gH=!1WEs30>L9tiqoo)5%UR3t=l04^ntHC8%Q7wF3i686s`-GzW5lFsE z&3Wnk92MD1)y7`a?#+q|^V7TO$36}1IifDK<*=`)?k5rAP6}{eg>smw(g&7KE0|KMF zvmvP4SurRimFqKn8qRU!ctg8xYR>dr>-nyw#eSo&lleSHDm*yvoajcMgisga)N4#2 zl73SN8vp_+$u#0LAR1nUIvBHQHvprFG6GnD$5JCBG$H^(uqoo*uvMxKu|N~2V;$1h z;7OAR9q8dqU?2by1=N~6g~na^&Up31qtm5VJjT7&h1q2|H07RFcYFgpxhA&W&)l= zE(Z3meQRROk>7%rX+|TYdhd+V!lQ@1xA;nNr0>F;2pPcE?Dg?|_ww>^dr5*DS-{2K zVAqu2ptm)Fjjy$UTXtc2D(L~476cxhub3AIFp)v?${?&3b9R#o%NUi zB(r0YMMwo@PKXnVOpQ%ZTZ}8ng8*bKs7@wuj{x$nabutn<_CKf0DwmJ(8(ja@|46P z5F4_facJ}HmXw4(A`M;_tTep^W$}PQ3Es8k6PpVg!sIiw2`T>5hOGq!LvLZf)$y& zDKe!68X8GcmS-%UpV!ZPEu8C7m@Ydz70#7Bw?4j=;{`4^s&R$uRu^e4a!K<2nYR>u zR`E)}-N>#$>{V^SMRcQQS^ZVrP19OF=9&0>0DiP9xyj=)!#6 z@85UTL@?66-4mf2`Ymv!|G=pH7nA2C?=}~E1q;7#9XayI$&f*j!nbVd0cJ7efPe=# zRFckPU!-nqES__NQ!)H!|GAMJQ>JpKlCoXj z@w!YMP90yRDue7eqT!%52bEu7vO3hp3yX@aCmTYG8Kcq>^ewx6M0qF;FsLRFDA+|*S zwA!njoyy5>N%=g%L5sM60g>HFA}G5|u-lLh$3jVlm|H5vl0iehtypjYR5z{kcFT=- zzi*qI@z>9_`mViAUMdozRFy1}+(3%k>?%S~KalLdA#JN$hLuWrJC?q5SI*L=7k{@( zf!MTph-QW7Ji~~v^H1w@Hei~mB;s%HLdu$;9s$EX$i`+HM zm6HOip!b?a@q07@1VR!Xv}&h!bs_*Jp;8J6rz!1rvCs^ZKmc?K5YF3JVp~K4QC5k} zSx{K-8<^Mv+KLefcPTL1pa7@<26h{=$+tMDfB+65g~oSWB^Xo*S|JqPM+le|!j{lZ z?f}fjFsU)H5Sm&utN=UG9p-bPQf!Gb0n7jZ002=H+G+{A3Mj?Gl!GaNOr7fD&kU`g zp%n?lUDg?6f3|9_ARF=T!pJ}#?Q>+^1P~(b+bYQWdM4c$@UCl*mfkaOox>c+0PmB4 z@Bv~st(53P2?U~IKv++Sub#Qzo@cftS2xecH#FrXRkw9_mv-YQ0Qr>!3Bl)HmIyo} zWpaqat;C8R1fi<0f310EgOSqJr@-c{v#WivKoa?$Z7)UYT407wUVT|{eGnlYXL9)| z98fS9K&g8*A1VoOx4 zTI@?1NV=-RkmL={SSewOGN=Kgh%0{il;y*1yTbb5CL~zBZu06Yk?w-~+vN~KpNV9k z498_a^3V)ww4pnbaXG1AJCe(>8{OY}4*&hhRPA#vY`rzv)57ncyFLA}b&{?H^TE2f zar@$7-Kcj=NLXyBi4|oKY0wGRBIQ6d@~uj3AOIJDfijK9QYwK_P|d;$i9S>_&j1Aa zK_CkGRo$g(&;k$u0oga;RRf1@J`%714MZ#y&NRj}UQ%g{=v53xanvYvI@Zh+Pn~n0 zV{!E*vg+a>0Cx{Npb!8Efsp6FIi9jintcVREoMIA004Iw-~mcEu5_k46-t3^QnKXh zzdt;7qUk_@fB!k z1G0*50@;wM5m5Ed$TYNStlVc16=hi~ze)o?;$Wo}E}J{i3C-jmgy)Xwh(+y`yyWn6jg@T-jLwlN|lsWFW2r(COVPlJT3 z1AQ7)E4w*t#xXn>SkJeN=O!mSHK%1GaApd|fnDa!q~5J{0v#N~v0YpNP&ckL))WPb zgJ7W4N5{{uII2&Ne3$X_s;lKSH#OT;O4O?%1Zx^{9GA8bDJh5Ppy`J`6=DnHlZ-w4 zm!H4f{*;<5nq>z&VeBEAb7~Eda_jRzH;X1c;y^*b4U-8&g@nv_R#C$foNARunQRu> z2i0*^+Lc{4i6L@~DVkH7olSF8^BORI2K5q#;}ddfbA>l7AEk~Pm6D=ZG)qde-2|O% z#uX9Qn|UP050U7XQQ*OB`wSTfjjSkw$kI(DpanPYC9=OW>pAe3SB%pUrEmKJ2vRh| zpj#V|L?3j(vqe{{5<{X;h^1t*gw6&o0kE0{BqT+302MG$dW*1*>W}YK?Yi$!^9T1u z+XZotj@KA~K2c@AX6BUk;*DaW2JL*r2DcdzIUt{1Ls0kTpF*m-v@`=MLhfT(HlzWT2iBC}{puBoXYvCl~C)2&~-me`Rj4vD3X90{enoe(xW1w4g->3?h z2LW6+u2h&x4MKuZAl}>7?<-tvdwlEP>)+dVxvSe*SG#4}tri6jJYXT7jFXv8th4uv z4C5yQ%lV73KPyLhSv6oa+NrZjnAd|6Y+kDsw@U7$eu0 z#0lt<3d{4^2mlEP3K+{i1IMO$&LyRlO*r^9#=@XjQbR^KMrq3noZ*1$ z1=LWu00>+?2?~~sstTGB5PTq8Ia!En53C;g`+0@v0r(7Be3W?o#>&TkWUE7Uu zp*^stv*X677|zys$4i0eQ9K^GHVMj8Ri3y0fO=>Ob-|&%5K5VtraED1oq8{FOFwzhX{XqcbW$W*J>R0K>X^?S!XtQsN6&%4aF-+fl4O}}>% zf?A3ic&*=n0a7=tl;}zp0>eYG1Y`L^|N4w9Z_0&vcQWTk5Ptvqr_cZ0 z@ppP`xTdH)M>~vKuF_ks@W5v@Bt_iJ000K{*j7S-Ue53%Z3RF!!Xa3DR#?{^C(Kf)$-N^B-tXAQA?Ui zmbBrBbF68!r9h{PGn9cvHm9$vfgVgs_0cIn1B|66b*fNI1l*3zj7f7gKmU*1Y34?1 zo2g0wBBs${*Nia?5^klgEpki%$UtKd_I^#fPHRjdS5=~2?$mro3Y05FoQ|gV*jeLT z#`sNl|1nzswyM57UFhkewH~3vy%DZa9ZdqYxDc>B<`N047~efDb$$%?boL}oanywN zWYm?tRl1{j%xZ6)$-HpJ@2jW7P53d2m@cHP0`FVzvv@eWMMbFa)LUD2f$^o+R{>Tx zu1rWHB?O~Eu;41zx6d1sKD|r2^ZCoW=h@mN7E8WY#(ZcaOy_Jy*yTUplSa-VSFrNH0oJ zq=%+}po>&dK@kBdQlyAi4UiPyyPL z19pqnW_@{ju2%K^G_{OBB5rfI2tE-}&>xh*2zm71(qUHMe;#J8^3hgRyitx$f4T`H zXo?aq7|i!neeqkE%xBcC?SOZ0jMQ}o${!fzD2Al2WGjtydegd^m1G^bT)X@um4ADN zRZON4rzf6Difm~N-kq;F7qpGdcVQ8H@G8*3$@blQ6j`R#_QhtSyEw+o(7V+)VMv1k z?|H$PRcCpD=%{jNt|hDKTRe++Af*8XAw2!VO9#WDlh+_IcLuiF4&MEcRmQ;kr}H=1 z>2 zYv3j4>C2Kl{g`$UNs-4-Zc0lVQJp z%Z5qVRC|2kRa3NbYWaPNhEr(u5fXtLGCp9|gK=cKn;yj7 zskzY?=St{G8)YS<+&*y#FCVk7^q`)-ATys&u+T)qmOw#*ylW+A ze5VQsDeQ-BR?5&htCZR#(M!c1ywuVn(eL(2Q&8Urns$JVm2m(qIo+CdL!mmKYjQrL znUB%a^M>Ji4?~!&-_`1J1Up&MC3QNsQP_;NtP*|kRD~mxeuA)b@fgAc+(rnpES%3z zC_5@V9GZv=p6K0NvfaAS7o?tqRkdwMke5-Qoo`(4TvCk4&?+EfH73i?oOZG;Hg7tT z$lZCUbDm&-x7n1v3~8wr%boM#m1Qhm?|rAybav+Ohk@g=wU;+!-tXY{-WU52BfT2G zjaBQGJEVQKB*IDU;mf@VO)@O%BJ{bgGR>L=Z1Qv{3`3g|>uh_;xkt3Y>p_NE?fSj9 zHHS;c@%tRph!Bo~Nkq!JNJtEb2OC z^ElF)%=2dc_|s6V9nLV%>|FE3sp*nllVf$&C+w*kxVD@9U0aoTnO=o96VD<^;+T*< ziF=^e?}|{kBaZy@2W`$Q%tF;u z*UP|O%QA~r9uDL~r=x$i`mHx?`Hg}VRo>p3E4Y47E4!rh8a>7b6{9WrI%y=KeDvG5 z5f6BZrI~Wnm z^(QE!y$GK=Z_dwY+X`8zsoWr|#_th}%#j#nF+Df<+R&f|@%xeJ=zUTW`=JBf99gc2 zkz-$xl6+aL1#RETk&=f39}y=P)&)hBBh!Ysm4|Gflxuyjh*H06sKiJ~K2j8RJVAE< zGX3{~Wtk9>l#P1@-TXBR=6q#aYP!o8=21HJlJ|PTXHfzz+XJi{=sdy9An#>tvSMhD z_u$)h?{bA5x5IwA>8(puh*XC_B|)t2Y}Gh_*4HWf!MQgsu{F(KHdi;tx}3~nH!JKe z^J~6a2)#%4#0*@~@rvzMHZb9d9Z|6oQr&PFMWpqKeZlvbu)Ss8|!5vwojm?I{ z^`yrSW}BW524f{eR3tDvEFBDfb?hcSftgkbW9rg`Y!i*1M0|}j)zA)ad-9e_xWLNt z%Ej%d;MVGaftboy@grHuHa#od(n`l{C;ioBv(E49mdgJ9w3Ff${ODH`j9nY@W8j=X zMN5+m3c1|gsWB>gUi0)tql!U0+9oYb*!y$Li9D)vzkZEn3y4?+`W|ZvDHlE`F6zF@it^p*Li$2@m@bBP_uT8maBD2vg(O;-pR;J!ZFOu z!Sd!!D^az9K`Ceoi zHEL2)8}wQq&h|_r7I&}$lx-`Sh>tNRn&ne>bFVhS5x1}oj$qDp%*MXaH9vsm2qJRQ zF1^Z}olUW{_;~bby+Dbp!<>Si_iaK4S4r;bi!(<%T;w&j@6poUV^LDID$N8Q+%nPr zfN0ASTila_!ENIcEGG85tmT>7BGd`I;E;Q7B0lx&TtY2#AB#tN{U>H~(Q~zU`*R2JAP^^~9#z*c~uAt&=++^J7Lt3qE(++YFelwUxCJRQ_7Fw(x zt!6czEE0N>ZeyfU$yPC_#1Px&2uVUnVlGYB7ddmC2+ z(n@*FH$sM2=Wd3qn!%csw9z>>5MMeqd`_RM zs`kJz3aY=)O*KFVbh3SKyBu#JJHz{IT5AK|S?ilD!FVIjDK*txq?i2XdR?65kV1W@ zwVBFl?m#eaoX&?zg<6{^L&vK*xs&sx#IC#gW@4%wdioEa)Boo^799CoePS`{H-aN%tqRy z4688r*NcyJ$B^$IOHueo{22Keyb4ft70Q z#5Ks0-lIIUqjNW@CFT3DmT%PeA|}_tH8V=+!r&_2ELu4Xxp21s%2h6hNIHm79fdGY z|H1%4hfc~5>{6Hw;${8bIfk~i&^9#@0vbgYWN}{hlS(gA#UM^CVN!{uY3;P1IyL=X z$gaqkrI_UUy~6l;tbEkhW5GSV&UZC3S5ilq-Z%*fQoXEyySlg2IYUSALGKqAeg$e1%TLy_9ZgpKO6zTjZ*X=(NFSEZ*JDlufw^g4K*;~Z6o3eGk=*o05hC8Ro z*DXeZ%CxxP%(|88BnCmMd~|dWLv41Iie?r6ooc^}P{U$OJF0QlxaJ+mjSwja{6FmbQuV3nQSE@8)z-t^nzfI@`r$7p$Rm=wr?$Bm zD)SLvzFr8}P_>NNEyT#04@M609^nR04zW#0Qwh<06ztA4#4`CYzR19j{*1{0KNu90RcvZ zI1dmF0MZ6=0l)*m5dfqv;^Hsv3;0z4*bkT@1UShL0s4*r5k>d`z`F4OV48l&uwB@O z7XaMu4iE$Y>w%Q{p+^8b697ECf%cKeE 2) - .attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) - .attr(PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) + new Ability(Abilities.POWER_CONSTRUCT, 7) + .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostBattleInitFormChangeAbAttr, () => 2) + .conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostBattleInitFormChangeAbAttr, () => 3) + .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) + .conditionalAttr(pokemon => pokemon.formIndex === 2 || pokemon.formIndex === 4, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2) + .conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3) + .conditionalAttr(pokemon => pokemon.formIndex === 3 || pokemon.formIndex === 5, PostTurnFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "10-complete" ? 5 : 3) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr) - .bypassFaint() - .partial(), + .bypassFaint(), new Ability(Abilities.CORROSION, 7) .attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ]) .edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet) diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 03b6b89e5b1..7cc20d50fb9 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -799,8 +799,8 @@ export const pokemonFormChanges: PokemonFormChanges = { [Species.ZYGARDE]: [ new SpeciesFormChange(Species.ZYGARDE, "50-pc", "complete", new SpeciesFormChangeManualTrigger(), true), new SpeciesFormChange(Species.ZYGARDE, "complete", "50-pc", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.ZYGARDE, "10-pc", "complete", new SpeciesFormChangeManualTrigger(), true), - new SpeciesFormChange(Species.ZYGARDE, "complete", "10-pc", new SpeciesFormChangeManualTrigger(), true) + new SpeciesFormChange(Species.ZYGARDE, "10-pc", "10-complete", new SpeciesFormChangeManualTrigger(), true), + new SpeciesFormChange(Species.ZYGARDE, "10-complete", "10-pc", new SpeciesFormChangeManualTrigger(), true) ], [Species.DIANCIE]: [ new SpeciesFormChange(Species.DIANCIE, "", SpeciesFormKey.MEGA, new SpeciesFormChangeItemTrigger(FormChangeItem.DIANCITE)) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 8bb23cfc208..eb1b761e306 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -425,6 +425,7 @@ export abstract class PokemonSpeciesForm { case "hero": case "roaming": case "complete": + case "10-complete": case "10": case "10-pc": case "super": @@ -2135,7 +2136,8 @@ export function initSpecies() { new PokemonForm("10% Forme", "10", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.AURA_BREAK, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, null, true), new PokemonForm("50% Forme Power Construct", "50-pc", Type.DRAGON, Type.GROUND, 5, 305, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 600, 108, 100, 121, 81, 95, 95, 3, 0, 300, false, "", true), new PokemonForm("10% Forme Power Construct", "10-pc", Type.DRAGON, Type.GROUND, 1.2, 33.5, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 486, 54, 100, 71, 61, 85, 115, 3, 0, 300, false, "10", true), - new PokemonForm("Complete Forme", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300), + new PokemonForm("Complete Forme (50% PC)", "complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300), + new PokemonForm("Complete Forme (10% PC)", "10-complete", Type.DRAGON, Type.GROUND, 4.5, 610, Abilities.POWER_CONSTRUCT, Abilities.NONE, Abilities.NONE, 708, 216, 100, 121, 91, 95, 85, 3, 0, 300, false, "complete"), ), new PokemonSpecies(Species.DIANCIE, 6, false, false, true, "Jewel Pokémon", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.ROCK, Type.FAIRY, 0.7, 8.8, Abilities.CLEAR_BODY, Abilities.NONE, Abilities.NONE, 600, 50, 100, 150, 100, 150, 50, 3, 50, 300, false, null, true), diff --git a/src/test/abilities/power_construct.test.ts b/src/test/abilities/power_construct.test.ts index 662f5d06258..1a9e7d4818a 100644 --- a/src/test/abilities/power_construct.test.ts +++ b/src/test/abilities/power_construct.test.ts @@ -32,7 +32,7 @@ describe("Abilities - POWER CONSTRUCT", () => { }); test( - "check if fainted pokemon switches to base form on arena reset", + "check if fainted 50% Power Construct Pokemon switches to base form on arena reset", async () => { const baseForm = 2, completeForm = 4; @@ -41,7 +41,37 @@ describe("Abilities - POWER CONSTRUCT", () => { [Species.ZYGARDE]: completeForm, }); - await game.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]); + await game.classicMode.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]); + + const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE); + expect(zygarde).not.toBe(undefined); + expect(zygarde!.formIndex).toBe(completeForm); + + zygarde!.hp = 0; + zygarde!.status = new Status(StatusEffect.FAINT); + expect(zygarde!.isFainted()).toBe(true); + + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to(TurnEndPhase); + game.doSelectModifier(); + await game.phaseInterceptor.to(QuietFormChangePhase); + + expect(zygarde!.formIndex).toBe(baseForm); + }, + ); + + test( + "check if fainted 10% Power Construct Pokemon switches to base form on arena reset", + async () => { + const baseForm = 3, + completeForm = 5; + game.override.startingWave(4); + game.override.starterForms({ + [Species.ZYGARDE]: completeForm, + }); + + await game.classicMode.startBattle([ Species.MAGIKARP, Species.ZYGARDE ]); const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE); expect(zygarde).not.toBe(undefined); From 093f3d90f5a4eab68a1ffe1317d1f76a50fa0a28 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:06:56 -0700 Subject: [PATCH 11/24] [Balance] Add Memory Mushroom to Shop (#4555) * Add Memory Mushroom to Shop + escape TM selection * consolidate learn move type params into an enum * Rewrite lock capsule test * Disable luck upgrades for copied SMPhases * Mem Mushroom Cost 4x Update modifier-type.ts * Add undefined cost check to `addModifier` * Increase shop options row limit * Prevent SMPhase copies from updating the seed --------- Co-authored-by: damocleas Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 6 +- src/modifier/modifier-type.ts | 3 +- src/modifier/modifier.ts | 9 +-- src/phases/learn-move-phase.ts | 37 +++++++++-- src/phases/select-modifier-phase.ts | 66 +++++++++++++------ src/test/items/lock_capsule.test.ts | 20 +++--- src/test/phases/select-modifier-phase.test.ts | 8 +-- src/ui/modifier-select-ui-handler.ts | 4 +- 8 files changed, 104 insertions(+), 49 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a84baa55266..75a19b8efaa 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -4,7 +4,7 @@ import Pokemon, { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import PokemonSpecies, { allSpecies, getPokemonSpecies, PokemonSpeciesFilter } from "#app/data/pokemon-species"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; -import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; +import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, Modifier, ModifierBar, ModifierPredicate, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier, TurnHeldItemTransferModifier } from "./modifier/modifier"; import { PokeballType } from "#app/data/pokeball"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; import { Phase } from "#app/phase"; @@ -2425,7 +2425,7 @@ export default class BattleScene extends SceneBase { return Math.floor(moneyValue / 10) * 10; } - addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean): Promise { + addModifier(modifier: Modifier | null, ignoreUpdate?: boolean, playSound?: boolean, virtual?: boolean, instant?: boolean, cost?: number): Promise { if (!modifier) { return Promise.resolve(false); } @@ -2482,6 +2482,8 @@ export default class BattleScene extends SceneBase { } } else if (modifier instanceof FusePokemonModifier) { args.push(this.getPokemonById(modifier.fusePokemonId) as PlayerPokemon); + } else if (modifier instanceof RememberMoveModifier && !Utils.isNullOrUndefined(cost)) { + args.push(cost); } if (modifier.shouldApply(pokemon, ...args)) { diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f20aa854bdf..32173a6fead 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -2227,7 +2227,8 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base ], [ new ModifierTypeOption(modifierTypes.HYPER_POTION(), 0, baseCost * 0.8), - new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75) + new ModifierTypeOption(modifierTypes.MAX_REVIVE(), 0, baseCost * 2.75), + new ModifierTypeOption(modifierTypes.MEMORY_MUSHROOM(), 0, baseCost * 4) ], [ new ModifierTypeOption(modifierTypes.MAX_POTION(), 0, baseCost * 1.5), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index dd8c82357a7..689b81be82f 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -11,7 +11,7 @@ import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; import { EvolutionPhase } from "#app/phases/evolution-phase"; -import { LearnMovePhase } from "#app/phases/learn-move-phase"; +import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { achvs } from "#app/system/achv"; @@ -2235,7 +2235,7 @@ export class TmModifier extends ConsumablePokemonModifier { */ override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, true)); + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), this.type.moveId, LearnMoveType.TM)); return true; } @@ -2255,8 +2255,9 @@ export class RememberMoveModifier extends ConsumablePokemonModifier { * @param playerPokemon The {@linkcode PlayerPokemon} that should remember the move * @returns always `true` */ - override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex])); + override apply(playerPokemon: PlayerPokemon, cost?: number): boolean { + + playerPokemon.scene.unshiftPhase(new LearnMovePhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.getLearnableLevelMoves()[this.levelMoveIndex], LearnMoveType.MEMORY, cost)); return true; } diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index 6480577258a..eb7cfbb65ef 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -2,24 +2,37 @@ import BattleScene from "#app/battle-scene"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import Move, { allMoves } from "#app/data/move"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; -import { Moves } from "#app/enums/moves"; +import { Moves } from "#enums/moves"; import { getPokemonNameWithAffix } from "#app/messages"; +import Overrides from "#app/overrides"; import EvolutionSceneHandler from "#app/ui/evolution-scene-handler"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; import { Mode } from "#app/ui/ui"; import i18next from "i18next"; -import { PlayerPartyMemberPokemonPhase } from "./player-party-member-pokemon-phase"; +import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import Pokemon from "#app/field/pokemon"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; + +export enum LearnMoveType { + /** For learning a move via level-up, evolution, or other non-item-based event */ + LEARN_MOVE, + /** For learning a move via Memory Mushroom */ + MEMORY, + /** For learning a move via TM */ + TM +} export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { private moveId: Moves; private messageMode: Mode; - private fromTM: boolean; + private learnMoveType; + private cost: number; - constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, fromTM?: boolean) { + constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves, learnMoveType: LearnMoveType = LearnMoveType.LEARN_MOVE, cost: number = -1) { super(scene, partyMemberIndex); this.moveId = moveId; - this.fromTM = fromTM ?? false; + this.learnMoveType = learnMoveType; + this.cost = cost; } start() { @@ -136,11 +149,23 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { * @param Pokemon The Pokemon learning the move */ async learnMove(index: number, move: Move, pokemon: Pokemon, textMessage?: string) { - if (this.fromTM) { + if (this.learnMoveType === LearnMoveType.TM) { if (!pokemon.usedTMs) { pokemon.usedTMs = []; } pokemon.usedTMs.push(this.moveId); + this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); + } else if (this.learnMoveType === LearnMoveType.MEMORY) { + if (this.cost !== -1) { + if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { + this.scene.money -= this.cost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("se/buy"); + } else { + this.scene.tryRemovePhase((phase) => phase instanceof SelectModifierPhase); + } } pokemon.setMove(index, this.moveId); initMoveAnim(this.scene, this.moveId).then(() => { diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index 159af979fa0..f9b3e978923 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -16,26 +16,32 @@ export class SelectModifierPhase extends BattlePhase { private rerollCount: integer; private modifierTiers?: ModifierTier[]; private customModifierSettings?: CustomModifierSettings; + private isCopy: boolean; - constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings) { + private typeOptions: ModifierTypeOption[]; + + constructor(scene: BattleScene, rerollCount: integer = 0, modifierTiers?: ModifierTier[], customModifierSettings?: CustomModifierSettings, isCopy: boolean = false) { super(scene); this.rerollCount = rerollCount; this.modifierTiers = modifierTiers; this.customModifierSettings = customModifierSettings; + this.isCopy = isCopy; } start() { super.start(); - if (!this.rerollCount) { + if (!this.rerollCount && !this.isCopy) { this.updateSeed(); - } else { + } else if (this.rerollCount) { this.scene.reroll = false; } const party = this.scene.getParty(); - regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); + if (!this.isCopy) { + regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); + } const modifierCount = new Utils.IntegerHolder(3); if (this.isPlayer()) { this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); @@ -54,7 +60,7 @@ export class SelectModifierPhase extends BattlePhase { } } - const typeOptions: ModifierTypeOption[] = this.getModifierTypeOptions(modifierCount.value); + this.typeOptions = this.getModifierTypeOptions(modifierCount.value); const modifierSelectCallback = (rowCursor: integer, cursor: integer) => { if (rowCursor < 0 || cursor < 0) { @@ -63,13 +69,13 @@ export class SelectModifierPhase extends BattlePhase { this.scene.ui.revertMode(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); - }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers))); + }, () => this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers))); }); return false; } let modifierType: ModifierType; let cost: integer; - const rerollCost = this.getRerollCost(typeOptions, this.scene.lockModifierTiers); + const rerollCost = this.getRerollCost(this.scene.lockModifierTiers); switch (rowCursor) { case 0: switch (cursor) { @@ -79,7 +85,7 @@ export class SelectModifierPhase extends BattlePhase { return false; } else { this.scene.reroll = true; - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); + this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[])); this.scene.ui.clearText(); this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -98,13 +104,13 @@ export class SelectModifierPhase extends BattlePhase { const itemModifier = itemModifiers[itemIndex]; this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } }, PartyUiHandler.FilterItemMaxStacks); break; case 2: this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.CHECK, -1, () => { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); }); break; case 3: @@ -115,21 +121,21 @@ export class SelectModifierPhase extends BattlePhase { } this.scene.lockModifierTiers = !this.scene.lockModifierTiers; const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); uiHandler.updateLockRaritiesText(); uiHandler.updateRerollCostText(); return false; } return true; case 1: - if (typeOptions.length === 0) { + if (this.typeOptions.length === 0) { this.scene.ui.clearText(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); return true; } - if (typeOptions[cursor].type) { - modifierType = typeOptions[cursor].type; + if (this.typeOptions[cursor].type) { + modifierType = this.typeOptions[cursor].type; } break; default: @@ -151,8 +157,16 @@ export class SelectModifierPhase extends BattlePhase { } const applyModifier = (modifier: Modifier, playSound: boolean = false) => { - const result = this.scene.addModifier(modifier, false, playSound); - if (cost) { + const result = this.scene.addModifier(modifier, false, playSound, undefined, undefined, cost); + // Queue a copy of this phase when applying a TM or Memory Mushroom. + // If the player selects either of these, then escapes out of consuming them, + // they are returned to a shop in the same state. + if (modifier.type instanceof RememberMoveModifierType || + modifier.type instanceof TmModifierType) { + this.scene.unshiftPhase(this.copy()); + } + + if (cost && !(modifier.type instanceof RememberMoveModifierType)) { result.then(success => { if (success) { if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) { @@ -189,7 +203,7 @@ export class SelectModifierPhase extends BattlePhase { applyModifier(modifier, true); }); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } }, modifierType.selectFilter); } else { @@ -216,7 +230,7 @@ export class SelectModifierPhase extends BattlePhase { applyModifier(modifier!, true); // TODO: is the bang correct? }); } else { - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); } @@ -226,7 +240,7 @@ export class SelectModifierPhase extends BattlePhase { return !cost!;// TODO: is the bang correct? }; - this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); + this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); } updateSeed(): void { @@ -237,13 +251,13 @@ export class SelectModifierPhase extends BattlePhase { return true; } - getRerollCost(typeOptions: ModifierTypeOption[], lockRarities: boolean): number { + getRerollCost(lockRarities: boolean): number { let baseValue = 0; if (Overrides.WAIVE_ROLL_FEE_OVERRIDE) { return baseValue; } else if (lockRarities) { const tierValues = [ 50, 125, 300, 750, 2000 ]; - for (const opt of typeOptions) { + for (const opt of this.typeOptions) { baseValue += tierValues[opt.type.tier ?? 0]; } } else { @@ -271,6 +285,16 @@ export class SelectModifierPhase extends BattlePhase { return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.customModifierSettings); } + copy(): SelectModifierPhase { + return new SelectModifierPhase( + this.scene, + this.rerollCount, + this.modifierTiers, + { guaranteedModifierTypeOptions: this.typeOptions, rerollMultiplier: this.customModifierSettings?.rerollMultiplier, allowLuckUpgrades: false }, + true + ); + } + addModifier(modifier: Modifier): Promise { return this.scene.addModifier(modifier, false, true); } diff --git a/src/test/items/lock_capsule.test.ts b/src/test/items/lock_capsule.test.ts index 2667ecea2dc..0b6534b5eaf 100644 --- a/src/test/items/lock_capsule.test.ts +++ b/src/test/items/lock_capsule.test.ts @@ -1,7 +1,8 @@ import { Abilities } from "#app/enums/abilities"; import { Moves } from "#app/enums/moves"; -import { ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; +import { ModifierTier } from "#app/modifier/modifier-tier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { Mode } from "#app/ui/ui"; import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -32,15 +33,16 @@ describe("Items - Lock Capsule", () => { }); it("doesn't set the cost of common tier items to 0", async () => { - await game.startBattle(); + await game.classicMode.startBattle(); + game.scene.overridePhase(new SelectModifierPhase(game.scene, 0, undefined, { guaranteedModifierTiers: [ ModifierTier.COMMON, ModifierTier.COMMON, ModifierTier.COMMON ], fillRemaining: false })); - game.move.select(Moves.SURF); - await game.phaseInterceptor.to(SelectModifierPhase, false); + game.onNextPrompt("SelectModifierPhase", Mode.MODIFIER_SELECT, () => { + const selectModifierPhase = game.scene.getCurrentPhase() as SelectModifierPhase; + const rerollCost = selectModifierPhase.getRerollCost(true); + expect(rerollCost).toBe(150); + }); - const rewards = game.scene.getCurrentPhase() as SelectModifierPhase; - const potion = new ModifierTypeOption(modifierTypes.POTION(), 0, 40); // Common tier item - const rerollCost = rewards.getRerollCost([ potion, potion, potion ], true); - - expect(rerollCost).toBe(150); + game.doSelectModifier(); + await game.phaseInterceptor.to("SelectModifierPhase"); }, 20000); }); diff --git a/src/test/phases/select-modifier-phase.test.ts b/src/test/phases/select-modifier-phase.test.ts index ea50c7e6524..a945aff055b 100644 --- a/src/test/phases/select-modifier-phase.test.ts +++ b/src/test/phases/select-modifier-phase.test.ts @@ -63,11 +63,11 @@ describe("SelectModifierPhase", () => { new ModifierTypeOption(modifierTypes.REVIVE(), 0, 1000) ]; - const selectModifierPhase1 = new SelectModifierPhase(scene); - const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { rerollMultiplier: 2 }); + const selectModifierPhase1 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options }); + const selectModifierPhase2 = new SelectModifierPhase(scene, 0, undefined, { guaranteedModifierTypeOptions: options, rerollMultiplier: 2 }); - const cost1 = selectModifierPhase1.getRerollCost(options, false); - const cost2 = selectModifierPhase2.getRerollCost(options, false); + const cost1 = selectModifierPhase1.getRerollCost(false); + const cost2 = selectModifierPhase2.getRerollCost(false); expect(cost2).toEqual(cost1 * 2); }); diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index f7e57b53193..0bae56c03b4 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -16,7 +16,7 @@ import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import { IntegerHolder } from "./../utils"; import Phaser from "phaser"; -export const SHOP_OPTIONS_ROW_LIMIT = 6; +export const SHOP_OPTIONS_ROW_LIMIT = 7; export default class ModifierSelectUiHandler extends AwaitableUiHandler { private modifierContainer: Phaser.GameObjects.Container; @@ -211,7 +211,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const row = m < SHOP_OPTIONS_ROW_LIMIT ? 0 : 1; const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT; const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT); - const sliceWidth = (this.scene.game.canvas.width / SHOP_OPTIONS_ROW_LIMIT) / (rowOptions.length + 2); + const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2); const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (40 - (28 * row - 1))), shopTypeOptions[m]); option.setScale(0.375); this.scene.add.existing(option); From 50ff6e703a6ff4613e85322bf790d45c221a8e0c Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:30:38 -0400 Subject: [PATCH 12/24] [P1 Bug] Fix several Destiny Bond crashes (#4665) * [P1 Bug] Fix several Destiny Bond crashes * PR Feedback --- src/data/move.ts | 12 +- src/field/pokemon.ts | 11 +- src/phases/faint-phase.ts | 28 ++- src/test/moves/destiny_bond.test.ts | 255 ++++++++++++++++++++++++++++ 4 files changed, 291 insertions(+), 15 deletions(-) create mode 100644 src/test/moves/destiny_bond.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index b0078c32f12..6d0701b79a2 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3834,8 +3834,8 @@ export class LastMoveDoublePowerAttr extends VariablePowerAttr { for (const p of pokemonActed) { const [ lastMove ] = p.getLastXMoves(1); - if (lastMove.result !== MoveResult.FAIL) { - if ((lastMove.result === MoveResult.SUCCESS) && (lastMove.move === this.move)) { + if (lastMove?.result !== MoveResult.FAIL) { + if ((lastMove?.result === MoveResult.SUCCESS) && (lastMove?.move === this.move)) { power.value *= 2; return true; } else { @@ -4736,7 +4736,7 @@ export class AddBattlerTagAttr extends MoveEffectAttr { } canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0].result === MoveResult.FAIL)) { + if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) { return false; } else { return true; @@ -5174,7 +5174,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { return false; } - if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0].result === MoveResult.SUCCESS) { + if ((move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY); return true; } @@ -5249,7 +5249,7 @@ export class AddArenaTrapTagHitAttr extends AddArenaTagAttr { const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true); const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag; - if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0].result === MoveResult.SUCCESS) { + if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side); if (!tag) { return true; @@ -5386,7 +5386,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr { override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { // TODO: add support for `HIT` effect triggering in AddArenaTagAttr to remove the need for this check - if (user.getLastXMoves(1)[0].result !== MoveResult.SUCCESS) { + if (user.getLastXMoves(1)[0]?.result !== MoveResult.SUCCESS) { return false; } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8eca37f38ac..f3e9c66ed15 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2810,15 +2810,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.isFainted()) { // set splice index here, so future scene queues happen before FaintedPhase this.scene.setPhaseQueueSplice(); - this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo)); + if (!isNullOrUndefined(destinyTag) && dmg) { + // Destiny Bond will activate during FaintPhase + this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo, destinyTag, source)); + } else { + this.scene.unshiftPhase(new FaintPhase(this.scene, this.getBattlerIndex(), isOneHitKo)); + } this.destroySubstitute(); this.resetSummonData(); } - if (dmg) { - destinyTag?.lapse(source, BattlerTagLapseType.CUSTOM); - } - return result; } } diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 66bb22899be..60dbbbfea0f 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -1,12 +1,12 @@ import BattleScene from "#app/battle-scene"; import { BattlerIndex, BattleType } from "#app/battle"; import { applyPostFaintAbAttrs, PostFaintAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr } from "#app/data/ability"; -import { BattlerTagLapseType } from "#app/data/battler-tags"; +import { BattlerTagLapseType, DestinyBondTag } from "#app/data/battler-tags"; import { battleSpecDialogue } from "#app/data/dialogue"; import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/move"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; -import { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon"; +import Pokemon, { PokemonMove, EnemyPokemon, PlayerPokemon, HitResult } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -19,19 +19,39 @@ import { SwitchPhase } from "./switch-phase"; import { VictoryPhase } from "./victory-phase"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SwitchType } from "#enums/switch-type"; +import { isNullOrUndefined } from "#app/utils"; export class FaintPhase extends PokemonPhase { + /** + * Whether or not enduring (for this phase's purposes, Reviver Seed) should be prevented + */ private preventEndure: boolean; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure?: boolean) { + /** + * Destiny Bond tag belonging to the currently fainting Pokemon, if applicable + */ + private destinyTag?: DestinyBondTag; + + /** + * The source Pokemon that dealt fatal damage and should get KO'd by Destiny Bond, if applicable + */ + private source?: Pokemon; + + constructor(scene: BattleScene, battlerIndex: BattlerIndex, preventEndure: boolean = false, destinyTag?: DestinyBondTag, source?: Pokemon) { super(scene, battlerIndex); - this.preventEndure = preventEndure!; // TODO: is this bang correct? + this.preventEndure = preventEndure; + this.destinyTag = destinyTag; + this.source = source; } start() { super.start(); + if (!isNullOrUndefined(this.destinyTag) && !isNullOrUndefined(this.source)) { + this.destinyTag.lapse(this.source, BattlerTagLapseType.CUSTOM); + } + if (!this.preventEndure) { const instantReviveModifier = this.scene.applyModifier(PokemonInstantReviveModifier, this.player, this.getPokemon()) as PokemonInstantReviveModifier; diff --git a/src/test/moves/destiny_bond.test.ts b/src/test/moves/destiny_bond.test.ts new file mode 100644 index 00000000000..4b4c8782862 --- /dev/null +++ b/src/test/moves/destiny_bond.test.ts @@ -0,0 +1,255 @@ +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { allMoves } from "#app/data/move"; +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, vi } from "vitest"; +import { BattlerIndex } from "#app/battle"; +import { StatusEffect } from "#enums/status-effect"; +import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; + + +describe("Moves - Destiny Bond", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + const defaultParty = [ Species.BULBASAUR, Species.SQUIRTLE ]; + const enemyFirst = [ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]; + const playerFirst = [ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override.battleType("single") + .ability(Abilities.UNNERVE) // Pre-emptively prevent flakiness from opponent berries + .enemySpecies(Species.RATTATA) + .enemyAbility(Abilities.RUN_AWAY) + .startingLevel(100) // Make sure tested moves KO + .enemyLevel(5) + .enemyMoveset(Moves.DESTINY_BOND); + }); + + it("should KO the opponent on the same turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset(moveToUse); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + }); + + it("should KO the opponent on the next turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPLASH, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPLASH); + await game.setTurnOrder(playerFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + + // Turn 2: Player KO's the enemy before the enemy's turn + game.move.select(moveToUse); + await game.setTurnOrder(playerFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + }); + + it("should fail if used twice in a row", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPLASH, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPLASH); + await game.setTurnOrder(enemyFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + + // Turn 2: Enemy should fail Destiny Bond then get KO'd + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO the opponent if the user dies to weather", async () => { + // Opponent will be reduced to 1 HP by False Swipe, then faint to Sandstorm + const moveToUse = Moves.FALSE_SWIPE; + + game.override.moveset(moveToUse) + .ability(Abilities.SAND_STREAM); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO the opponent if the user had another turn", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset([ Moves.SPORE, moveToUse ]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + // Turn 1: Enemy uses Destiny Bond and doesn't faint + game.move.select(Moves.SPORE); + await game.setTurnOrder(enemyFirst); + await game.toNextTurn(); + + expect(enemyPokemon?.isFainted()).toBe(false); + expect(playerPokemon?.isFainted()).toBe(false); + expect(enemyPokemon?.status?.effect).toBe(StatusEffect.SLEEP); + + // Turn 2: Enemy should skip a turn due to sleep, then get KO'd + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(false); + }); + + it("should not KO an ally", async () => { + game.override.moveset([ Moves.DESTINY_BOND, Moves.CRUNCH ]) + .battleType("double"); + await game.classicMode.startBattle([ Species.SHEDINJA, Species.BULBASAUR, Species.SQUIRTLE ]); + + const enemyPokemon0 = game.scene.getEnemyField()[0]; + const enemyPokemon1 = game.scene.getEnemyField()[1]; + const playerPokemon0 = game.scene.getPlayerField()[0]; + const playerPokemon1 = game.scene.getPlayerField()[1]; + + // Shedinja uses Destiny Bond, then ally Bulbasaur KO's Shedinja with Crunch + game.move.select(Moves.DESTINY_BOND, 0); + game.move.select(Moves.CRUNCH, 1, BattlerIndex.PLAYER); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon0?.isFainted()).toBe(false); + expect(enemyPokemon1?.isFainted()).toBe(false); + expect(playerPokemon0?.isFainted()).toBe(true); + expect(playerPokemon1?.isFainted()).toBe(false); + }); + + it("should not cause a crash if the user is KO'd by Ceaseless Edge", async () => { + const moveToUse = Moves.CEASELESS_EDGE; + vi.spyOn(allMoves[moveToUse], "accuracy", "get").mockReturnValue(100); + + game.override.moveset(moveToUse); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + + // Ceaseless Edge spikes effect should still activate + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter.tagType).toBe(ArenaTagType.SPIKES); + expect(tagAfter.layers).toBe(1); + }); + + it("should not cause a crash if the user is KO'd by Pledge moves", async () => { + game.override.moveset([ Moves.GRASS_PLEDGE, Moves.WATER_PLEDGE ]) + .battleType("double"); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon0 = game.scene.getEnemyField()[0]; + const enemyPokemon1 = game.scene.getEnemyField()[1]; + const playerPokemon0 = game.scene.getPlayerField()[0]; + const playerPokemon1 = game.scene.getPlayerField()[1]; + + game.move.select(Moves.GRASS_PLEDGE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.WATER_PLEDGE, 1, BattlerIndex.ENEMY); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.ENEMY_2, BattlerIndex.PLAYER, BattlerIndex.PLAYER_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon0?.isFainted()).toBe(true); + expect(enemyPokemon1?.isFainted()).toBe(false); + expect(playerPokemon0?.isFainted()).toBe(false); + expect(playerPokemon1?.isFainted()).toBe(true); + + // Pledge secondary effect should still activate + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter.tagType).toBe(ArenaTagType.GRASS_WATER_PLEDGE); + }); + + /** + * In particular, this should prevent something like + * {@link https://github.com/pagefaultgames/pokerogue/issues/4219} + * from occurring with fainting by KO'ing a Destiny Bond user with U-Turn. + */ + it("should not allow the opponent to revive via Reviver Seed", async () => { + const moveToUse = Moves.TACKLE; + + game.override.moveset(moveToUse) + .startingHeldItems([{ name: "REVIVER_SEED" }]); + await game.classicMode.startBattle(defaultParty); + + const enemyPokemon = game.scene.getEnemyPokemon(); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(moveToUse); + await game.setTurnOrder(enemyFirst); + await game.phaseInterceptor.to("BerryPhase"); + + expect(enemyPokemon?.isFainted()).toBe(true); + expect(playerPokemon?.isFainted()).toBe(true); + + // Check that the Tackle user's Reviver Seed did not activate + const revSeeds = game.scene.getModifiers(PokemonInstantReviveModifier).filter(m => m.pokemonId === playerPokemon?.id); + expect(revSeeds.length).toBe(1); + }); +}); From c6ec01958ce299b990227cfb01c98d2c9a8f02d5 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:31:32 -0400 Subject: [PATCH 13/24] [Bug] Fix for Expert Breeder's Pokemon being invisible and IV scanner in safari zone (#4661) * [Bug] Potential fix for Expert Breeder's Pokemon being invisible * PR Feedback * Consistency with await --- src/battle-scene.ts | 2 +- .../encounters/a-trainers-test-encounter.ts | 2 +- .../encounters/absolute-avarice-encounter.ts | 6 +- .../encounters/dancing-lessons-encounter.ts | 4 +- .../encounters/dark-deal-encounter.ts | 2 +- .../encounters/fiery-fallout-encounter.ts | 3 +- .../encounters/fun-and-games-encounter.ts | 2 +- .../global-trade-system-encounter.ts | 2 +- .../encounters/lost-at-sea-encounter.ts | 2 +- .../mysterious-challengers-encounter.ts | 18 ++--- .../encounters/mysterious-chest-encounter.ts | 2 +- .../encounters/safari-zone-encounter.ts | 23 +++++-- .../shady-vitamin-dealer-encounter.ts | 4 +- .../teleporting-hijinks-encounter.ts | 2 +- .../the-expert-pokemon-breeder-encounter.ts | 6 +- .../encounters/the-strong-stuff-encounter.ts | 2 +- .../the-winstrate-challenge-encounter.ts | 4 +- .../encounters/training-session-encounter.ts | 6 +- .../encounters/trash-to-treasure-encounter.ts | 6 +- .../the-expert-breeder-encounter.test.ts | 69 ++++++++++++++++++- 20 files changed, 120 insertions(+), 47 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 75a19b8efaa..6a70688dbf1 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -789,7 +789,7 @@ export default class BattleScene extends SceneBase { } getEnemyParty(): EnemyPokemon[] { - return this.currentBattle?.enemyParty || []; + return this.currentBattle?.enemyParty ?? []; } getEnemyPokemon(): EnemyPokemon | undefined { diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 4f3420f5194..56d80c9598c 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -154,7 +154,7 @@ export const ATrainersTestEncounter: MysteryEncounter = }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); } ) .withSimpleOption( diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 70b2d50fe99..c53b802bb22 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -286,7 +286,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = ignorePp: true }); - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); }) .build() @@ -328,7 +328,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = }); await scene.updateModifiers(true); - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); leaveEncounterWithoutBattle(scene, true); }) .build() @@ -359,7 +359,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = greedent.moveset = [ new PokemonMove(Moves.THRASH), new PokemonMove(Moves.BODY_PRESS), new PokemonMove(Moves.STUFF_CHEEKS), new PokemonMove(Moves.SLACK_OFF) ]; greedent.passive = true; - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); await catchPokemon(scene, greedent, null, PokeballType.POKEBALL, false); leaveEncounterWithoutBattle(scene, true); }) diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 0f784739777..d7f71194f48 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -228,7 +228,7 @@ export const DancingLessonsEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { // Learn its Dance - hideOricorioPokemon(scene); + await hideOricorioPokemon(scene); leaveEncounterWithoutBattle(scene, true); }) .build() @@ -303,7 +303,7 @@ export const DancingLessonsEncounter: MysteryEncounter = } } - hideOricorioPokemon(scene); + await hideOricorioPokemon(scene); await catchPokemon(scene, oricorio, null, PokeballType.POKEBALL, false); leaveEncounterWithoutBattle(scene, true); }) diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index 5ad6630386f..7f199b5487c 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -182,7 +182,7 @@ export const DarkDealEncounter: MysteryEncounter = const config: EnemyPartyConfig = { pokemonConfigs: [ pokemonConfig ], }; - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index d44e7bae596..d306206159a 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -222,12 +222,13 @@ export const FieryFalloutEncounter: MysteryEncounter = ], }) .withPreOptionPhase(async (scene: BattleScene) => { + // Do NOT await this, to prevent player from repeatedly pressing options transitionMysteryEncounterIntroVisuals(scene, false, false, 2000); }) .withOptionPhase(async (scene: BattleScene) => { // Fire types help calm the Volcarona const encounter = scene.currentBattle.mysteryEncounter!; - transitionMysteryEncounterIntroVisuals(scene); + await transitionMysteryEncounterIntroVisuals(scene); setEncounterRewards(scene, { fillRemaining: true }, undefined, diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 549faa01fa1..b843a929c08 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -152,7 +152,7 @@ export const FunAndGamesEncounter: MysteryEncounter = }, async (scene: BattleScene) => { // Leave encounter with no rewards or exp - transitionMysteryEncounterIntroVisuals(scene, true, true); + await transitionMysteryEncounterIntroVisuals(scene, true, true); leaveEncounterWithoutBattle(scene, true); return true; } diff --git a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts index bafc1901e5e..376bdf0c95d 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -399,7 +399,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = if (modifier.stackCount === 0) { scene.removeModifier(modifier); } - scene.updateModifiers(true, true); + await scene.updateModifiers(true, true); // Generate a trainer name const traderName = generateRandomTraderName(); diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 8fd46982dc1..8e7ea52a967 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -129,7 +129,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with * * @param scene Battle scene */ -async function handlePokemonGuidingYouPhase(scene: BattleScene) { +function handlePokemonGuidingYouPhase(scene: BattleScene) { const laprasSpecies = getPokemonSpecies(Species.LAPRAS); const { mysteryEncounter } = scene.currentBattle; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index fb25976ebd8..f282064bb94 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -147,11 +147,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter = setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams - let ret; + let initBattlePromise: Promise; scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); + initBattlePromise = initBattleWithEnemyConfig(scene, config); }, scene.currentBattle.waveIndex * 10); - return ret; + await initBattlePromise!; } ) .withSimpleOption( @@ -172,11 +172,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter = setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams - let ret; + let initBattlePromise: Promise; scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); + initBattlePromise = initBattleWithEnemyConfig(scene, config); }, scene.currentBattle.waveIndex * 100); - return ret; + await initBattlePromise!; } ) .withSimpleOption( @@ -200,11 +200,11 @@ export const MysteriousChallengersEncounter: MysteryEncounter = setEncounterRewards(scene, { guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT ], fillRemaining: true }); // Seed offsets to remove possibility of different trainers having exact same teams - let ret; + let initBattlePromise: Promise; scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); + initBattlePromise = initBattleWithEnemyConfig(scene, config); }, scene.currentBattle.waveIndex * 1000); - return ret; + await initBattlePromise!; } ) .withOutroDialogue([ diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 1eb1c4cb13e..693d935ae17 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -184,7 +184,7 @@ export const MysteriousChestEncounter: MysteryEncounter = scene.unshiftPhase(new GameOverPhase(scene)); } else { // Show which Pokemon was KOed, then start battle against Gimmighoul - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); setEncounterRewards(scene, { fillRemaining: true }); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); } diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index c6b04b7aca6..0fec305333e 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -303,13 +303,22 @@ async function summonSafariPokemon(scene: BattleScene) { scene.unshiftPhase(new SummonPhase(scene, 0, false)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); - showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", null, 1500, false) - .then(() => { - const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); - if (ivScannerModifier) { - scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); - } - }); + // TODO: If we await this showEncounterText, then the text will display without + // the wild Pokemon on screen, but if we don't await it, then the text never + // shows up and the IV scanner breaks. For now, we place the IV scanner code + // separately so that at least the IV scanner works. + // + // showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", null, 0, false) + // .then(() => { + // const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); + // if (ivScannerModifier) { + // scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); + // } + // }); + const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); + if (ivScannerModifier) { + scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); + } } function throwPokeball(scene: BattleScene, pokemon: EnemyPokemon): Promise { diff --git a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts index c70048ade07..5b609a2b1c3 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -142,7 +142,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = encounter.setDialogueToken("newNature", getNatureName(newNature)); queueEncounterMessage(scene, `${namespace}:cheap_side_effects`); setEncounterExp(scene, [ chosenPokemon.id ], 100); - chosenPokemon.updateInfo(); + await chosenPokemon.updateInfo(); }) .build() ) @@ -204,7 +204,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = queueEncounterMessage(scene, `${namespace}:no_bad_effects`); setEncounterExp(scene, [ chosenPokemon.id ], 100); - chosenPokemon.updateInfo(); + await chosenPokemon.updateInfo(); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 01e241f63d4..e8f11f02e18 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -149,7 +149,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = const magnet = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.STEEL ])!; const metalCoat = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [ Type.ELECTRIC ])!; setEncounterRewards(scene, { guaranteedModifierTypeOptions: [ magnet, metalCoat ], fillRemaining: true }); - transitionMysteryEncounterIntroVisuals(scene, true, true); + await transitionMysteryEncounterIntroVisuals(scene, true, true); await initBattleWithEnemyConfig(scene, config); } ) diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 0ac82243862..7bba603728b 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -245,7 +245,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .withPostOptionPhase(async (scene: BattleScene) => { await doPostEncounterCleanup(scene); @@ -297,7 +297,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .withPostOptionPhase(async (scene: BattleScene) => { await doPostEncounterCleanup(scene); @@ -349,7 +349,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = } encounter.onGameOver = onGameOver; - initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .withPostOptionPhase(async (scene: BattleScene) => { await doPostEncounterCleanup(scene); diff --git a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts index 7ee57d36027..03cf86d06a5 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -201,7 +201,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = }); encounter.dialogue.outro = []; - transitionMysteryEncounterIntroVisuals(scene, true, true, 500); + await transitionMysteryEncounterIntroVisuals(scene, true, true, 500); await initBattleWithEnemyConfig(scene, encounter.enemyPartyConfigs[0]); } ) diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index c7cb23fe6f8..bf322802f81 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -111,8 +111,8 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = }, async (scene: BattleScene) => { // Spawn 5 trainer battles back to back with Macho Brace in rewards - scene.currentBattle.mysteryEncounter!.doContinueEncounter = (scene: BattleScene) => { - return endTrainerBattleAndShowDialogue(scene); + scene.currentBattle.mysteryEncounter!.doContinueEncounter = async (scene: BattleScene) => { + await endTrainerBattleAndShowDialogue(scene); }; await transitionMysteryEncounterIntroVisuals(scene, true, false); await spawnNextTrainerOrEndEncounter(scene); diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index 10bb956636b..9f80bbbffde 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -162,7 +162,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -238,7 +238,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) @@ -351,7 +351,7 @@ export const TrainingSessionEncounter: MysteryEncounter = setEncounterRewards(scene, { fillRemaining: true }, undefined, onBeforeRewardsPhase); - return initBattleWithEnemyConfig(scene, config); + await initBattleWithEnemyConfig(scene, config); }) .build() ) diff --git a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index c2a0426bceb..2b3b38b2164 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -105,7 +105,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = }) .withOptionPhase(async (scene: BattleScene) => { // Gain 2 Leftovers and 2 Shell Bell - transitionMysteryEncounterIntroVisuals(scene); + await transitionMysteryEncounterIntroVisuals(scene); await tryApplyDigRewardItems(scene); const blackSludge = generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_BLACK_SLUDGE, [ SHOP_ITEM_COST_MULTIPLIER ]); @@ -136,7 +136,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = // Investigate garbage, battle Gmax Garbodor scene.setFieldScale(0.75); await showEncounterText(scene, `${namespace}:option.2.selected_2`); - transitionMysteryEncounterIntroVisuals(scene); + await transitionMysteryEncounterIntroVisuals(scene); const encounter = scene.currentBattle.mysteryEncounter!; @@ -222,7 +222,7 @@ async function tryApplyDigRewardItems(scene: BattleScene) { await showEncounterText(scene, i18next.t("battle:rewardGainCount", { modifierName: shellBell.name, count: 2 }), null, undefined, true); } -async function doGarbageDig(scene: BattleScene) { +function doGarbageDig(scene: BattleScene) { scene.playSound("battle_anims/PRSFX- Dig2"); scene.time.delayedCall(SOUND_EFFECT_WAIT_TIME, () => { scene.playSound("battle_anims/PRSFX- Dig2"); diff --git a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index bbb4f249feb..a3a43815ec6 100644 --- a/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -124,10 +124,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); }); - it("should start battle against the trainer", async () => { + it("should start battle against the trainer with correctly loaded assets", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + let successfullyLoaded = false; + vi.spyOn(scene, "getEnemyParty").mockImplementation(() => { + const ace = scene.currentBattle?.enemyParty[0]; + if (ace) { + // Pretend that loading assets takes an extra 500ms + vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => { + setTimeout(() => { + successfullyLoaded = true; + resolve(); + }, 500); + })); + } + + return scene.currentBattle?.enemyParty ?? []; + }); + await runMysteryEncounterToEnd(game, 1, undefined, true); + // Check that assets are successfully loaded + expect(successfullyLoaded).toBe(true); + + // Check usual battle stuff expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); @@ -182,10 +203,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); }); - it("should start battle against the trainer", async () => { + it("should start battle against the trainer with correctly loaded assets", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + let successfullyLoaded = false; + vi.spyOn(scene, "getEnemyParty").mockImplementation(() => { + const ace = scene.currentBattle?.enemyParty[0]; + if (ace) { + // Pretend that loading assets takes an extra 500ms + vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => { + setTimeout(() => { + successfullyLoaded = true; + resolve(); + }, 500); + })); + } + + return scene.currentBattle?.enemyParty ?? []; + }); + await runMysteryEncounterToEnd(game, 2, undefined, true); + // Check that assets are successfully loaded + expect(successfullyLoaded).toBe(true); + + // Check usual battle stuff expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); @@ -240,10 +282,31 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); }); - it("should start battle against the trainer", async () => { + it("should start battle against the trainer with correctly loaded assets", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); + + let successfullyLoaded = false; + vi.spyOn(scene, "getEnemyParty").mockImplementation(() => { + const ace = scene.currentBattle?.enemyParty[0]; + if (ace) { + // Pretend that loading assets takes an extra 500ms + vi.spyOn(ace, "loadAssets").mockImplementation(() => new Promise(resolve => { + setTimeout(() => { + successfullyLoaded = true; + resolve(); + }, 500); + })); + } + + return scene.currentBattle?.enemyParty ?? []; + }); + await runMysteryEncounterToEnd(game, 3, undefined, true); + // Check that assets are successfully loaded + expect(successfullyLoaded).toBe(true); + + // Check usual battle stuff expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(scene.currentBattle.trainer).toBeDefined(); expect(scene.currentBattle.mysteryEncounter?.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); From 2caa09f246e157329fe3c73130c814db2d4410f6 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Wed, 16 Oct 2024 07:38:12 -0700 Subject: [PATCH 14/24] [Move] Fully Implement Secret Power (#4647) * initial work * move go * biomes for damo * more cleanup * added effect for space * test * balance change 1 * i'm silly * fixed effect cahnce * secret power atr * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * got tests to work + added final balance biomes * added documentation * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/data/move.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: frutescens Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/move.ts | 160 +++++++++++++++++++++++++++- src/test/moves/secret_power.test.ts | 89 ++++++++++++++++ 2 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 src/test/moves/secret_power.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index 6d0701b79a2..448008b733c 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1024,7 +1024,7 @@ export class MoveEffectAttr extends MoveAttr { applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility); - if (!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) { + if ((!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) && !move.hasAttr(SecretPowerAttr)) { const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; user.scene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance); } @@ -2875,6 +2875,162 @@ export class StatStageChangeAttr extends MoveEffectAttr { } } +/** + * Attribute used to determine the Biome/Terrain-based secondary effect of Secret Power + */ +export class SecretPowerAttr extends MoveEffectAttr { + constructor() { + super(false); + } + + /** + * Used to determine if the move should apply a secondary effect based on Secret Power's 30% chance + * @returns `true` if the move's secondary effect should apply + */ + override canApply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean { + this.effectChanceOverride = move.chance; + const moveChance = this.getMoveChance(user, target, move, this.selfTarget); + if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { + // effectChanceOverride used in the application of the actual secondary effect + this.effectChanceOverride = 100; + return true; + } else { + return false; + } + } + + /** + * Used to apply the secondary effect to the target Pokemon + * @returns `true` if a secondary effect is successfully applied + */ + override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean | Promise { + if (!super.apply(user, target, move, args)) { + return false; + } + let secondaryEffect: MoveEffectAttr; + const terrain = user.scene.arena.getTerrainType(); + if (terrain !== TerrainType.NONE) { + secondaryEffect = this.determineTerrainEffect(terrain); + } else { + const biome = user.scene.arena.biomeType; + secondaryEffect = this.determineBiomeEffect(biome); + } + return secondaryEffect.apply(user, target, move, []); + } + + /** + * Determines the secondary effect based on terrain. + * Takes precedence over biome-based effects. + * ``` + * Electric Terrain | Paralysis + * Misty Terrain | SpAtk -1 + * Grassy Terrain | Sleep + * Psychic Terrain | Speed -1 + * ``` + * @param terrain - {@linkcode TerrainType} The current terrain + * @returns the chosen secondary effect {@linkcode MoveEffectAttr} + */ + private determineTerrainEffect(terrain: TerrainType): MoveEffectAttr { + let secondaryEffect: MoveEffectAttr; + switch (terrain) { + case TerrainType.ELECTRIC: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; + case TerrainType.MISTY: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case TerrainType.GRASSY: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case TerrainType.PSYCHIC: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; + } + return secondaryEffect; + } + + /** + * Determines the secondary effect based on biome + * ``` + * Town, Metropolis, Slum, Dojo, Laboratory, Power Plant + Default | Paralysis + * Plains, Grass, Tall Grass, Forest, Jungle, Meadow | Sleep + * Swamp, Mountain, Temple, Ruins | Speed -1 + * Ice Cave, Snowy Forest | Freeze + * Volcano | Burn + * Fairy Cave | SpAtk -1 + * Desert, Construction Site, Beach, Island, Badlands | Accuracy -1 + * Sea, Lake, Seabed | Atk -1 + * Cave, Wasteland, Graveyard, Abyss, Space | Flinch + * End | Def -1 + * ``` + * @param biome - The current {@linkcode Biome} the battle is set in + * @returns the chosen secondary effect {@linkcode MoveEffectAttr} + */ + private determineBiomeEffect(biome: Biome): MoveEffectAttr { + let secondaryEffect: MoveEffectAttr; + switch (biome) { + case Biome.PLAINS: + case Biome.GRASS: + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.JUNGLE: + case Biome.MEADOW: + secondaryEffect = new StatusEffectAttr(StatusEffect.SLEEP, false); + break; + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.TEMPLE: + case Biome.RUINS: + secondaryEffect = new StatStageChangeAttr([ Stat.SPD ], -1, false); + break; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + secondaryEffect = new StatusEffectAttr(StatusEffect.FREEZE, false); + break; + case Biome.VOLCANO: + secondaryEffect = new StatusEffectAttr(StatusEffect.BURN, false); + break; + case Biome.FAIRY_CAVE: + secondaryEffect = new StatStageChangeAttr([ Stat.SPATK ], -1, false); + break; + case Biome.DESERT: + case Biome.CONSTRUCTION_SITE: + case Biome.BEACH: + case Biome.ISLAND: + case Biome.BADLANDS: + secondaryEffect = new StatStageChangeAttr([ Stat.ACC ], -1, false); + break; + case Biome.SEA: + case Biome.LAKE: + case Biome.SEABED: + secondaryEffect = new StatStageChangeAttr([ Stat.ATK ], -1, false); + break; + case Biome.CAVE: + case Biome.WASTELAND: + case Biome.GRAVEYARD: + case Biome.ABYSS: + case Biome.SPACE: + secondaryEffect = new AddBattlerTagAttr(BattlerTagType.FLINCHED, false, true); + break; + case Biome.END: + secondaryEffect = new StatStageChangeAttr([ Stat.DEF ], -1, false); + break; + case Biome.TOWN: + case Biome.METROPOLIS: + case Biome.SLUM: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.LABORATORY: + case Biome.POWER_PLANT: + default: + secondaryEffect = new StatusEffectAttr(StatusEffect.PARALYSIS, false); + break; + } + return secondaryEffect; + } +} + export class PostVictoryStatStageChangeAttr extends MoveAttr { private stats: BattleStat[]; private stages: number; @@ -7898,7 +8054,7 @@ export function initMoves() { .unimplemented(), new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3) .makesContact(false) - .partial(), // No effect implemented + .attr(SecretPowerAttr), 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) diff --git a/src/test/moves/secret_power.test.ts b/src/test/moves/secret_power.test.ts new file mode 100644 index 00000000000..ff0b5ae8c24 --- /dev/null +++ b/src/test/moves/secret_power.test.ts @@ -0,0 +1,89 @@ +import { Abilities } from "#enums/abilities"; +import { Biome } from "#enums/biome"; +import { Moves } from "#enums/moves"; +import { Stat } from "#enums/stat"; +import { allMoves, SecretPowerAttr } from "#app/data/move"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { ArenaTagSide } from "#app/data/arena-tag"; + +describe("Moves - Secret Power", () => { + 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.SECRET_POWER ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyLevel(60) + .enemyAbility(Abilities.BALL_FETCH); + }); + + it("Secret Power checks for an active terrain first then looks at the biome for its secondary effect", async () => { + game.override + .startingBiome(Biome.VOLCANO) + .enemyMoveset([ Moves.SPLASH, Moves.MISTY_TERRAIN ]); + vi.spyOn(allMoves[Moves.SECRET_POWER], "chance", "get").mockReturnValue(100); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemyPokemon = game.scene.getEnemyPokemon()!; + + // No Terrain + Biome.VOLCANO --> Burn + game.move.select(Moves.SECRET_POWER); + await game.forceEnemyMove(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN); + + // Misty Terrain --> SpAtk -1 + game.move.select(Moves.SECRET_POWER); + await game.forceEnemyMove(Moves.MISTY_TERRAIN); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(-1); + }); + + it("the 'rainbow' effect of fire+water pledge does not double the chance of secret power's secondary effect", + async () => { + game.override + .moveset([ Moves.FIRE_PLEDGE, Moves.WATER_PLEDGE, Moves.SECRET_POWER, Moves.SPLASH ]) + .enemyMoveset([ Moves.SPLASH ]) + .battleType("double"); + await game.classicMode.startBattle([ Species.BLASTOISE, Species.CHARIZARD ]); + + const secretPowerAttr = allMoves[Moves.SECRET_POWER].getAttrs(SecretPowerAttr)[0]; + vi.spyOn(secretPowerAttr, "getMoveChance"); + + game.move.select(Moves.WATER_PLEDGE, 0, BattlerIndex.ENEMY); + game.move.select(Moves.FIRE_PLEDGE, 1, BattlerIndex.ENEMY_2); + + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.arena.getTagOnSide(ArenaTagType.WATER_FIRE_PLEDGE, ArenaTagSide.PLAYER)).toBeDefined(); + + game.move.select(Moves.SECRET_POWER, 0, BattlerIndex.ENEMY); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + expect(secretPowerAttr.getMoveChance).toHaveLastReturnedWith(30); + } + ); +}); From 72c08e5cfdc64b39c46a95997df3a4ba5373e227 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:09:48 -0400 Subject: [PATCH 15/24] [Refactor] Clean up commented safari zone code from #4661 (#4671) --- .../encounters/safari-zone-encounter.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 0fec305333e..01dc29f9821 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -303,18 +303,7 @@ async function summonSafariPokemon(scene: BattleScene) { scene.unshiftPhase(new SummonPhase(scene, 0, false)); encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); - // TODO: If we await this showEncounterText, then the text will display without - // the wild Pokemon on screen, but if we don't await it, then the text never - // shows up and the IV scanner breaks. For now, we place the IV scanner code - // separately so that at least the IV scanner works. - // - // showEncounterText(scene, getEncounterText(scene, "battle:singleWildAppeared") ?? "", null, 0, false) - // .then(() => { - // const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); - // if (ivScannerModifier) { - // scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); - // } - // }); + const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); From 1907824670c211ea2cf133ba0b42cab9a689c851 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Wed, 16 Oct 2024 11:10:35 -0400 Subject: [PATCH 16/24] [P1] Fix party UI crash from unsanitized `lastCursor` pointing to empty Pokemon slot (#4672) --- src/ui/party-ui-handler.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index e7c1b02cf01..cfc5e146f08 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -671,6 +671,9 @@ export default class PartyUiHandler extends MessageUiHandler { } else if (this.cursor === 6) { this.partyCancelButton.select(); } + if (this.lastCursor < 6 && this.lastCursor >= party.length) { + this.lastCursor = party.length - 1; + } for (const p in party) { const slotIndex = parseInt(p); From 3ea459746a06a6827901dd6138002141efe76ee3 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Wed, 16 Oct 2024 20:53:25 +0200 Subject: [PATCH 17/24] [Localization] [UI/UX] Italian Type and Status icons (#4673) --- public/images/statuses_it.png | Bin 441 -> 2463 bytes public/images/types_it.png | Bin 4467 -> 6358 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/statuses_it.png b/public/images/statuses_it.png index d372b989be966007e5fccac5f54fe341457e598b..af3107018f86ccdae9b586c5bfe25268caeb70b2 100644 GIT binary patch literal 2463 zcmbVO3v|=g85VC!m$O1wU?mSBD3C*CNq$SQ^RkmU0f#Jc)&v?7S(0Nlwq+y*J6Tea zc5K*#u#M4_#4VvcC4sf9fkH~)l!Q|tD>Qx30tv}kPho+L(^6iA78tvB9QQGx-F%L$ zE9tx6|9$`e-zx?3^L`OG<tc!#+zOf%?3}{y_5w5n8Pfb-nyKVu*v?h(9wTyb-vI>XStIg< z#)@0LCeF!QYDBKECeOyyR5B!sq@}_JKLrTfoJ_-hx630@ej^g$rNB6R8$;j-M6NU< z8R39%u{9qy2_gq;6k3!a)Jj-KDhMr3D%Fp}N}SMPxDF#!C{9qgmQs;$^oM|GBI}?E z%-PXc;L3949x0k8kTA?odoea(liz`YhGA)${y{LyP!2(K)4(q8rYkti>#0ECSQ06d z;1Wa-7^qW4R9LN4jz^Md55wh{jRMqllD|_VAIBsu$4$nM+$;{zgCfF-`WaC;b4Ca4`UlUxQM+PI1iu}!N#BW9t>VC z9)|T=3gO8UNn?cUp~8`_nqLBw*$Rx z)cim8TH=>{p)JikxWZd^<=>n7=C-`s#Pu)ghoFx#s@y}LCQb{a*I1sdHytm0D&Dpw zyQTI5JHN#;J~O4DXhC~s99($epUDaR16^6?gTc^Oj?Hf-tqS)3L;u)}-+VHt4W62? zbM}t5j^i_e=Ed158-=&}#yu2Bhi)8Qckb(@LCe-bsI_ik*H+%)zM9;~tht@sTHU?n z(lS22r7o418h>thV~+bvvbipG?0~jOUupBavFWAa?Nd)(f3q`jP zo*g_EXiPeI`%}I4Owz*6)FWN1+fO#kK3o3io|`B0$`ps;qwkQzf8sPtk2gIyaJ#ai zJN_x*MD_6Q3kK<6+>PnyQSq754+baH{?tELH|xgb+RxHcO2u7c-Y@%WUBa@B9cFP? zga78WI``P_Z6CgHq9&m@_h*}KO+5Yn(4IRbiD}I}A^Dc`_0xwPLzC9K7EJo^)Pxm* zUHglA=zVTO@K$s7b)rFE>v{XhzBNttuB?LG4YO(pZK$p1TEXAuy`tU&Bu|DC4^3Sf zI?<3tJU=NnfqL!N&gVXMY}r0dH7}*V_UVG@mgA;7r5X937Z(NkL)^9A;mhNozUFIX zHG%9ZSDLo7^h(S4eerGLqHQ}L$t_vCcG01d+t-P|tkvXipIl#Xsbh8fuA=g}W4n76 zo60upJ|_0w8t7V0wO_m#d?jJqh4j9!JE-M9dFzCg?CF$qmcKr9Zg1;HNBXA@JRQ>X zpBqB{bxQcG&UY-mspSA9@y?-8yVJThfPxlP( zG&I)lS$BAFA{5%)K6oBZ+>4&`O_-6znYOleuU%8R?fI82xwLrZ_nsYoZakE}u_Jpw zQh$JKT-Ejm-;D-u+~;SmoNOy;B|7^nYim1io~rufPF+%((U9`;i{m#RNWFCRi~U!R tB_8|xye!)EUfJg1^+&(FIsf{lq^;e^<1cLQ(1$;vaTQ>G}+I zIn7o0!+Dp;xZJZk++_QIf*BQ_vO9cL>_x7%KzeP1y+B2_I7G_xHE-s8-)5{aFy1?z zvW;$NS#fE(5J*QH>;TO#qj-;m3y@V^jDKUiXB4T~am+que5?h|bD2QvJlYc8%_G~a zG}z~@!Q2dSmt873PQXLUg+O|T!QM`p^A|K=@Suc&?b@)04>kGLv3=K17yh?|Mhx#G)+hhQwMYUUT0RT>1-XbQdPI;dWdHyG07*qoM6N<$g1zg# A#sB~S diff --git a/public/images/types_it.png b/public/images/types_it.png index 8b644f1041c4236c9e729b30c529149e209a286c..3be03aeea6851671fe4360380bf760f3b04b6f8e 100644 GIT binary patch literal 6358 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 literal 4467 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 Date: Wed, 16 Oct 2024 14:55:23 -0400 Subject: [PATCH 18/24] [Refactor] Add friendship related constants (#4657) * Add constants for friendship * Absolute path in battle-scene.ts * Address nits * Apply negative to constant --- src/battle-scene.ts | 3 ++- src/data/balance/starters.ts | 6 ++++++ src/field/pokemon.ts | 4 ++-- src/modifier/modifier.ts | 3 ++- src/phases/faint-phase.ts | 3 ++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 6a70688dbf1..ffba8e98d34 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -95,6 +95,7 @@ import { ExpPhase } from "#app/phases/exp-phase"; import { ShowPartyExpBarPhase } from "#app/phases/show-party-exp-bar-phase"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; +import { FRIENDSHIP_GAIN_FROM_BATTLE } from "#app/data/balance/starters"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -3054,7 +3055,7 @@ export default class BattleScene extends SceneBase { const pId = partyMember.id; const participated = participantIds.has(pId); if (participated && pokemonDefeated) { - partyMember.addFriendship(2); + partyMember.addFriendship(FRIENDSHIP_GAIN_FROM_BATTLE); const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier); if (machoBraceModifier && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this)) { machoBraceModifier.stackCount++; diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index 0fadd992309..bf3a1f7ad56 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -2,6 +2,12 @@ import { Species } from "#enums/species"; export const POKERUS_STARTER_COUNT = 5; +// #region Friendship constants +export const CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER = 2; +export const FRIENDSHIP_GAIN_FROM_BATTLE = 2; +export const FRIENDSHIP_GAIN_FROM_RARE_CANDY = 5; +export const FRIENDSHIP_LOSS_FROM_FAINT = 10; + /** * Function to get the cumulative friendship threshold at which a candy is earned * @param starterCost The cost of the starter, found in {@linkcode speciesStarterCosts} diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index f3e9c66ed15..9aaadf29657 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -5,7 +5,7 @@ import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "#app/ui/battle-info"; import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, OneHitKOAccuracyAttr, RespectAttackTypeImmunityAttr, MoveTarget, CombinedPledgeStabBoostAttr } from "#app/data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; -import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; +import { CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER, getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters"; import { starterPassiveAbilities } from "#app/data/balance/passives"; import { Constructor, isNullOrUndefined, randSeedInt } from "#app/utils"; import * as Utils from "#app/utils"; @@ -4082,7 +4082,7 @@ export class PlayerPokemon extends Pokemon { fusionStarterSpeciesId ? this.scene.gameData.starterData[fusionStarterSpeciesId] : null ].filter(d => !!d); const amount = new Utils.IntegerHolder(friendship); - const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? 2 : 1) / (fusionStarterSpeciesId ? 2 : 1))); + const starterAmount = new Utils.IntegerHolder(Math.floor(friendship * (this.scene.gameMode.isClassic && friendship > 0 ? CLASSIC_CANDY_FRIENDSHIP_MULTIPLIER : 1) / (fusionStarterSpeciesId ? 2 : 1))); if (amount.value > 0) { this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, amount); this.scene.applyModifier(PokemonFriendshipBoosterModifier, true, this, starterAmount); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 689b81be82f..35d1a304461 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -30,6 +30,7 @@ import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type"; import { Color, ShadowColor } from "#enums/color"; +import { FRIENDSHIP_GAIN_FROM_RARE_CANDY } from "#app/data/balance/starters"; export type ModifierPredicate = (modifier: Modifier) => boolean; @@ -2213,7 +2214,7 @@ export class PokemonLevelIncrementModifier extends ConsumablePokemonModifier { playerPokemon.levelExp = 0; } - playerPokemon.addFriendship(5); + playerPokemon.addFriendship(FRIENDSHIP_GAIN_FROM_RARE_CANDY); playerPokemon.scene.unshiftPhase(new LevelUpPhase(playerPokemon.scene, playerPokemon.scene.getParty().indexOf(playerPokemon), playerPokemon.level - levelCount.value, playerPokemon.level)); diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 60dbbbfea0f..95105337f60 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -20,6 +20,7 @@ import { VictoryPhase } from "./victory-phase"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SwitchType } from "#enums/switch-type"; import { isNullOrUndefined } from "#app/utils"; +import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; export class FaintPhase extends PokemonPhase { /** @@ -147,7 +148,7 @@ export class FaintPhase extends PokemonPhase { pokemon.faintCry(() => { if (pokemon instanceof PlayerPokemon) { - pokemon.addFriendship(-10); + pokemon.addFriendship(-FRIENDSHIP_LOSS_FROM_FAINT); } pokemon.hideInfo(); this.scene.playSound("se/faint"); From d92d63e81fc80dd08f7631af22316a61cdabe776 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:10:19 -0700 Subject: [PATCH 19/24] [Misc] Restore info comment that was accidentally removed (#4674) --- .../mystery-encounters/encounters/safari-zone-encounter.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index 01dc29f9821..0ee3c57b0a2 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -304,6 +304,11 @@ async function summonSafariPokemon(scene: BattleScene) { encounter.setDialogueToken("pokemonName", getPokemonNameWithAffix(pokemon)); + // TODO: If we await showEncounterText here, then the text will display without + // the wild Pokemon on screen, but if we don't await it, then the text never + // shows up and the IV scanner breaks. For now, we place the IV scanner code + // separately so that at least the IV scanner works. + const ivScannerModifier = scene.findModifier(m => m instanceof IvScannerModifier); if (ivScannerModifier) { scene.pushPhase(new ScanIvsPhase(scene, pokemon.getBattlerIndex(), Math.min(ivScannerModifier.getStackCount() * 2, 6))); From afebecd43c5bced779736dd51ec5369be8810afb Mon Sep 17 00:00:00 2001 From: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:16:10 -0500 Subject: [PATCH 20/24] [P2 Bug] Fix pool entry for Jynx not using baby species (#4675) --- .../encounters/the-expert-pokemon-breeder-encounter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts index 7bba603728b..945e7ee188d 100644 --- a/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-expert-pokemon-breeder-encounter.ts @@ -61,7 +61,7 @@ const POOL_1_POKEMON: (Species | BreederSpeciesEvolution)[][] = [ const POOL_2_POKEMON: (Species | BreederSpeciesEvolution)[][] = [ [ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ], [ Species.PICHU, new BreederSpeciesEvolution(Species.PIKACHU, FIRST_STAGE_EVOLUTION_WAVE), new BreederSpeciesEvolution(Species.ALOLA_RAICHU, FINAL_STAGE_EVOLUTION_WAVE) ], - [ Species.JYNX ], + [ Species.SMOOCHUM, new BreederSpeciesEvolution(Species.JYNX, SECOND_STAGE_EVOLUTION_WAVE) ], [ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONLEE, SECOND_STAGE_EVOLUTION_WAVE) ], [ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONCHAN, SECOND_STAGE_EVOLUTION_WAVE) ], [ Species.TYROGUE, new BreederSpeciesEvolution(Species.HITMONTOP, SECOND_STAGE_EVOLUTION_WAVE) ], From 85b8ca6467bc0a79e0eabcbb81ef7c677b327241 Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:48:28 -0400 Subject: [PATCH 21/24] [Dev] Bump Game Version, Overhaul Version Migration (#4388) * Bump Version, Remove "Outdated" Message * Fix `src/ui/ui.ts` * Fix `src/system/game-data.ts` * Clean Up & Organize Version Migration * Rename Methods & Session Migration Adjustment * Collapse Version Migrators to Single File as Arrays * Address NITs * Restructure Migration Initialization * Fix Spacing, Increment to v1.6.0 * Revert Back to v1.1.0 * Add `gameVersion` to Mocked Game * Add More Documentation --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- package-lock.json | 4 +- package.json | 4 +- src/phases/outdated-phase.ts | 13 -- src/system/game-data.ts | 19 +- src/system/version-converter.ts | 157 --------------- .../version_migration/version_converter.ts | 182 ++++++++++++++++++ .../version_migration/versions/v1_0_4.ts | 135 +++++++++++++ src/test/utils/gameWrapper.ts | 2 + src/ui/outdated-modal-ui-handler.ts | 47 ----- src/ui/ui.ts | 4 - 10 files changed, 329 insertions(+), 238 deletions(-) delete mode 100644 src/phases/outdated-phase.ts delete mode 100644 src/system/version-converter.ts create mode 100644 src/system/version_migration/version_converter.ts create mode 100644 src/system/version_migration/versions/v1_0_4.ts delete mode 100644 src/ui/outdated-modal-ui-handler.ts diff --git a/package-lock.json b/package-lock.json index ee2708b38f5..be946306471 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pokemon-rogue-battle", - "version": "1.0.4", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pokemon-rogue-battle", - "version": "1.0.4", + "version": "1.1.0", "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", diff --git a/package.json b/package.json index d8d7f5e8db8..a31296d1644 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.0.4", + "version": "1.1.0", "type": "module", "scripts": { "start": "vite", @@ -20,7 +20,7 @@ "depcruise": "depcruise src", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "create-test": "node ./create-test-boilerplate.js", - "postinstall": "npx lefthook install && npx lefthook run post-merge" + "postinstall": "npx lefthook install && npx lefthook run post-merge" }, "devDependencies": { "@eslint/js": "^9.3.0", diff --git a/src/phases/outdated-phase.ts b/src/phases/outdated-phase.ts deleted file mode 100644 index 4baf16d2f56..00000000000 --- a/src/phases/outdated-phase.ts +++ /dev/null @@ -1,13 +0,0 @@ -import BattleScene from "#app/battle-scene"; -import { Phase } from "#app/phase"; -import { Mode } from "#app/ui/ui"; - -export class OutdatedPhase extends Phase { - constructor(scene: BattleScene) { - super(scene); - } - - start(): void { - this.scene.ui.setMode(Mode.OUTDATED); - } -} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index b162962fac6..41746957d49 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -43,10 +43,9 @@ import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { WeatherType } from "#enums/weather-type"; import { TerrainType } from "#app/data/terrain"; -import { OutdatedPhase } from "#app/phases/outdated-phase"; import { ReloadSessionPhase } from "#app/phases/reload-session-phase"; import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; -import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "#app/system/version-converter"; +import { applySessionVersionMigration, applySystemVersionMigration, applySettingsVersionMigration } from "./version_migration/version_converter"; 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"; @@ -403,10 +402,7 @@ export class GameData { .then(error => { this.scene.ui.savingIcon.hide(); if (error) { - if (error.startsWith("client version out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new OutdatedPhase(this.scene)); - } else if (error.startsWith("session out of date")) { + if (error.startsWith("session out of date")) { this.scene.clearPhaseQueue(); this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); } @@ -482,7 +478,7 @@ export class GameData { localStorage.setItem(lsItemKey, ""); } - applySystemDataPatches(systemData); + applySystemVersionMigration(systemData); this.trainerId = systemData.trainerId; this.secretId = systemData.secretId; @@ -857,7 +853,7 @@ export class GameData { const settings = JSON.parse(localStorage.getItem("settings")!); // TODO: is this bang correct? - applySettingsDataPatches(settings); + applySettingsVersionMigration(settings); for (const setting of Object.keys(settings)) { setSetting(this.scene, setting, settings[setting]); @@ -1313,7 +1309,7 @@ export class GameData { return v; }) as SessionSaveData; - applySessionDataPatches(sessionData); + applySessionVersionMigration(sessionData); return sessionData; } @@ -1354,10 +1350,7 @@ export class GameData { this.scene.ui.savingIcon.hide(); } if (error) { - if (error.startsWith("client version out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new OutdatedPhase(this.scene)); - } else if (error.startsWith("session out of date")) { + if (error.startsWith("session out of date")) { this.scene.clearPhaseQueue(); this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); } diff --git a/src/system/version-converter.ts b/src/system/version-converter.ts deleted file mode 100644 index 3a4416a975e..00000000000 --- a/src/system/version-converter.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { allSpecies } from "#app/data/pokemon-species"; -import { AbilityAttr, defaultStarterSpecies, DexAttr, SessionSaveData, SystemSaveData } from "./game-data"; -import { SettingKeys } from "./settings/settings"; - -const LATEST_VERSION = "1.0.5"; - -export function applySessionDataPatches(data: SessionSaveData) { - const curVersion = data.gameVersion; - - // Always sanitize money as a safeguard - data.money = Math.floor(data.money); - - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- PATCHES --- - - // Fix Battle Items, Vitamins, and Lures - data.modifiers.forEach((m) => { - if (m.className === "PokemonBaseStatModifier") { - m.className = "BaseStatModifier"; - } else if (m.className === "PokemonResetNegativeStatStageModifier") { - m.className = "ResetNegativeStatStageModifier"; - } else if (m.className === "TempBattleStatBoosterModifier") { - // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator - if (m.typeId !== "DIRE_HIT") { - m.className = "TempStatStageBoosterModifier"; - m.typeId = "TEMP_STAT_STAGE_BOOSTER"; - - // Migration from TempBattleStat to Stat - const newStat = m.typePregenArgs[0] + 1; - m.typePregenArgs[0] = newStat; - - // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ] - m.args = [ newStat, 5, m.args[1] ]; - } else { - m.className = "TempCritBoosterModifier"; - m.typePregenArgs = []; - - // From [ stat, battlesLeft ] to [ maxBattles, battleCount ] - m.args = [ 5, m.args[1] ]; - } - - } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) { - let maxBattles: number; - switch (m.typeId) { - case "MAX_LURE": - maxBattles = 30; - break; - case "SUPER_LURE": - maxBattles = 15; - break; - default: - maxBattles = 10; - break; - } - - // From [ battlesLeft ] to [ maxBattles, battleCount ] - m.args = [ maxBattles, m.args[0] ]; - } - }); - - data.enemyModifiers.forEach((m) => { - if (m.className === "PokemonBaseStatModifier") { - m.className = "BaseStatModifier"; - } else if (m.className === "PokemonResetNegativeStatStageModifier") { - m.className = "ResetNegativeStatStageModifier"; - } - }); - } - - data.gameVersion = LATEST_VERSION; - } -} - -export function applySystemDataPatches(data: SystemSaveData) { - const curVersion = data.gameVersion; - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- LEGACY PATCHES --- - if (data.starterData && data.dexData) { - // Migrate ability starter data if empty for caught species - Object.keys(data.starterData).forEach(sd => { - if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) { - data.starterData[sd].abilityAttr = 1; - } - }); - } - - // Fix Legendary Stats - if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) { - data.gameStats.subLegendaryPokemonSeen = 0; - data.gameStats.subLegendaryPokemonCaught = 0; - data.gameStats.subLegendaryPokemonHatched = 0; - allSpecies.filter(s => s.subLegendary).forEach(s => { - const dexEntry = data.dexData[s.speciesId]; - data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount; - data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0); - data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount; - data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0); - data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount; - data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0); - }); - data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught); - data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught); - data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught); - } - - // --- PATCHES --- - - // Fix Starter Data - if (data.starterData && data.dexData) { - for (const starterId of defaultStarterSpecies) { - if (data.starterData[starterId]?.abilityAttr) { - data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; - } - if (data.dexData[starterId]?.caughtAttr) { - data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; - } - } - } - } - - data.gameVersion = LATEST_VERSION; - } -} - -export function applySettingsDataPatches(settings: Object) { - const curVersion = settings.hasOwnProperty("gameVersion") ? settings["gameVersion"] : "1.0.0"; - if (curVersion !== LATEST_VERSION) { - switch (curVersion) { - case "1.0.0": - case "1.0.1": - case "1.0.2": - case "1.0.3": - case "1.0.4": - // --- PATCHES --- - - // Fix Reward Cursor Target - if (settings.hasOwnProperty("REROLL_TARGET") && !settings.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { - settings[SettingKeys.Shop_Cursor_Target] = settings["REROLL_TARGET"]; - delete settings["REROLL_TARGET"]; - localStorage.setItem("settings", JSON.stringify(settings)); - } - } - // Note that the current game version will be written at `saveSettings` - } -} diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts new file mode 100644 index 00000000000..f93e09b7a90 --- /dev/null +++ b/src/system/version_migration/version_converter.ts @@ -0,0 +1,182 @@ +import { SessionSaveData, SystemSaveData } from "../game-data"; +import { version } from "../../../package.json"; + +// --- v1.0.4 (and below) PATCHES --- // +import * as v1_0_4 from "./versions/v1_0_4"; + +const LATEST_VERSION = version.split(".").map(value => parseInt(value)); + +/** + * Converts incoming {@linkcode SystemSaveData} that has a version below the + * current version number listed in `package.json`. + * + * Note that no transforms act on the {@linkcode data} if its version matches + * the current version or if there are no migrations made between its version up + * to the current version. + * @param data {@linkcode SystemSaveData} + * @see {@link SystemVersionConverter} + */ +export function applySystemVersionMigration(data: SystemSaveData) { + const curVersion = data.gameVersion.split(".").map(value => parseInt(value)); + + if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { + const converter = new SystemVersionConverter(); + converter.applyStaticPreprocessors(data); + converter.applyMigration(data, curVersion); + } +} + +/** + * Converts incoming {@linkcode SessionSavaData} that has a version below the + * current version number listed in `package.json`. + * + * Note that no transforms act on the {@linkcode data} if its version matches + * the current version or if there are no migrations made between its version up + * to the current version. + * @param data {@linkcode SessionSaveData} + * @see {@link SessionVersionConverter} + */ +export function applySessionVersionMigration(data: SessionSaveData) { + const curVersion = data.gameVersion.split(".").map(value => parseInt(value)); + + if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { + const converter = new SessionVersionConverter(); + converter.applyStaticPreprocessors(data); + converter.applyMigration(data, curVersion); + } +} + +/** + * Converts incoming settings data that has a version below the + * current version number listed in `package.json`. + * + * Note that no transforms act on the {@linkcode data} if its version matches + * the current version or if there are no migrations made between its version up + * to the current version. + * @param data Settings data object + * @see {@link SettingsVersionConverter} + */ +export function applySettingsVersionMigration(data: Object) { + const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0"; + const curVersion = gameVersion.split(".").map(value => parseInt(value)); + + if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { + const converter = new SettingsVersionConverter(); + converter.applyStaticPreprocessors(data); + converter.applyMigration(data, curVersion); + } +} + +/** + * Abstract class encapsulating the logic for migrating data from a given version up to + * the current version listed in `package.json`. + * + * Note that, for any version converter, the corresponding `applyMigration` + * function would only need to be changed once when the first migration for a + * given version is introduced. Similarly, a version file (within the `versions` + * folder) would only need to be created for a version once with the appropriate + * array nomenclature. + */ +abstract class VersionConverter { + /** + * Iterates through an array of designated migration functions that are each + * called one by one to transform the data. + * @param data The data to be operated on + * @param migrationArr An array of functions that will transform the incoming data + */ + callMigrators(data: any, migrationArr: readonly any[]) { + for (const migrate of migrationArr) { + migrate(data); + } + } + + /** + * Applies any version-agnostic data sanitation as defined within the function + * body. + * @param data The data to be operated on + */ + applyStaticPreprocessors(_data: any): void { + } + + /** + * Uses the current version the incoming data to determine the starting point + * of the migration which will cascade up to the latest version, calling the + * necessary migration functions in the process. + * @param data The data to be operated on + * @param curVersion [0] Current major version + * [1] Current minor version + * [2] Current patch version + */ + abstract applyMigration(data: any, curVersion: number[]): void; +} + +/** + * Class encapsulating the logic for migrating {@linkcode SessionSaveData} from + * a given version up to the current version listed in `package.json`. + * @extends VersionConverter + */ +class SessionVersionConverter extends VersionConverter { + override applyStaticPreprocessors(data: SessionSaveData): void { + // Always sanitize money as a safeguard + data.money = Math.floor(data.money); + } + + override applyMigration(data: SessionSaveData, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + if (curMajor === 1) { + if (curMinor === 0) { + if (curPatch <= 4) { + console.log("Applying v1.0.4 session data migration!"); + this.callMigrators(data, v1_0_4.sessionMigrators); + } + } + } + + console.log(`Session data successfully migrated to v${version}!`); + } +} + +/** + * Class encapsulating the logic for migrating {@linkcode SystemSaveData} from + * a given version up to the current version listed in `package.json`. + * @extends VersionConverter + */ +class SystemVersionConverter extends VersionConverter { + override applyMigration(data: SystemSaveData, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + if (curMajor === 1) { + if (curMinor === 0) { + if (curPatch <= 4) { + console.log("Applying v1.0.4 system data migraton!"); + this.callMigrators(data, v1_0_4.systemMigrators); + } + } + } + + console.log(`System data successfully migrated to v${version}!`); + } +} + +/** + * Class encapsulating the logic for migrating settings data from + * a given version up to the current version listed in `package.json`. + * @extends VersionConverter + */ +class SettingsVersionConverter extends VersionConverter { + override applyMigration(data: Object, curVersion: number[]): void { + const [ curMajor, curMinor, curPatch ] = curVersion; + + if (curMajor === 1) { + if (curMinor === 0) { + if (curPatch <= 4) { + console.log("Applying v1.0.4 settings data migraton!"); + this.callMigrators(data, v1_0_4.settingsMigrators); + } + } + } + + console.log(`System data successfully migrated to v${version}!`); + } +} diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts new file mode 100644 index 00000000000..c20e2a281e7 --- /dev/null +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -0,0 +1,135 @@ +import { SettingKeys } from "../../settings/settings"; +import { AbilityAttr, defaultStarterSpecies, DexAttr, SystemSaveData, SessionSaveData } from "../../game-data"; +import { allSpecies } from "../../../data/pokemon-species"; + +export const systemMigrators = [ + /** + * Migrate ability starter data if empty for caught species. + * @param data {@linkcode SystemSaveData} + */ + function migrateAbilityData(data: SystemSaveData) { + if (data.starterData && data.dexData) { + Object.keys(data.starterData).forEach(sd => { + if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) { + data.starterData[sd].abilityAttr = 1; + } + }); + } + }, + + /** + * Populate legendary Pokémon statistics if they are missing. + * @param data {@linkcode SystemSaveData} + */ + function fixLegendaryStats(data: SystemSaveData) { + if (data.gameStats && (data.gameStats.legendaryPokemonCaught !== undefined && data.gameStats.subLegendaryPokemonCaught === undefined)) { + data.gameStats.subLegendaryPokemonSeen = 0; + data.gameStats.subLegendaryPokemonCaught = 0; + data.gameStats.subLegendaryPokemonHatched = 0; + allSpecies.filter(s => s.subLegendary).forEach(s => { + const dexEntry = data.dexData[s.speciesId]; + data.gameStats.subLegendaryPokemonSeen += dexEntry.seenCount; + data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen - dexEntry.seenCount, 0); + data.gameStats.subLegendaryPokemonCaught += dexEntry.caughtCount; + data.gameStats.legendaryPokemonCaught = Math.max(data.gameStats.legendaryPokemonCaught - dexEntry.caughtCount, 0); + data.gameStats.subLegendaryPokemonHatched += dexEntry.hatchedCount; + data.gameStats.legendaryPokemonHatched = Math.max(data.gameStats.legendaryPokemonHatched - dexEntry.hatchedCount, 0); + }); + data.gameStats.subLegendaryPokemonSeen = Math.max(data.gameStats.subLegendaryPokemonSeen, data.gameStats.subLegendaryPokemonCaught); + data.gameStats.legendaryPokemonSeen = Math.max(data.gameStats.legendaryPokemonSeen, data.gameStats.legendaryPokemonCaught); + data.gameStats.mythicalPokemonSeen = Math.max(data.gameStats.mythicalPokemonSeen, data.gameStats.mythicalPokemonCaught); + } + }, + + /** + * Unlock all starters' first ability and female gender option. + * @param data {@linkcode SystemSaveData} + */ + function fixStarterData(data: SystemSaveData) { + for (const starterId of defaultStarterSpecies) { + if (data.starterData[starterId]?.abilityAttr) { + data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1; + } + if (data.dexData[starterId]?.caughtAttr) { + data.dexData[starterId].caughtAttr |= DexAttr.FEMALE; + } + } + } +] as const; + +export const settingsMigrators = [ + /** + * Migrate from "REROLL_TARGET" property to {@linkcode + * SettingKeys.Shop_Cursor_Target}. + * @param data the `settings` object + */ + function fixRerollTarget(data: Object) { + if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { + data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"]; + delete data["REROLL_TARGET"]; + localStorage.setItem("settings", JSON.stringify(data)); + } + } +] as const; + +export const sessionMigrators = [ + /** + * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and + * other miscellaneous modifiers (vitamins, White Herb) to any new class + * names and/or change in reload arguments. + * @param data {@linkcode SessionSaveData} + */ + function migrateModifiers(data: SessionSaveData) { + data.modifiers.forEach((m) => { + if (m.className === "PokemonBaseStatModifier") { + m.className = "BaseStatModifier"; + } else if (m.className === "PokemonResetNegativeStatStageModifier") { + m.className = "ResetNegativeStatStageModifier"; + } else if (m.className === "TempBattleStatBoosterModifier") { + const maxBattles = 5; + // Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator + if (m.typeId !== "DIRE_HIT") { + m.className = "TempStatStageBoosterModifier"; + m.typeId = "TEMP_STAT_STAGE_BOOSTER"; + + // Migration from TempBattleStat to Stat + const newStat = m.typePregenArgs[0] + 1; + m.typePregenArgs[0] = newStat; + + // From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ] + m.args = [ newStat, maxBattles, Math.min(m.args[1], maxBattles) ]; + } else { + m.className = "TempCritBoosterModifier"; + m.typePregenArgs = []; + + // From [ stat, battlesLeft ] to [ maxBattles, battleCount ] + m.args = [ maxBattles, Math.min(m.args[1], maxBattles) ]; + } + } else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) { + let maxBattles: number; + switch (m.typeId) { + case "MAX_LURE": + maxBattles = 30; + break; + case "SUPER_LURE": + maxBattles = 15; + break; + default: + maxBattles = 10; + break; + } + + // From [ battlesLeft ] to [ maxBattles, battleCount ] + m.args = [ maxBattles, Math.min(m.args[0], maxBattles) ]; + } + }); + + data.enemyModifiers.forEach((m) => { + if (m.className === "PokemonBaseStatModifier") { + m.className = "BaseStatModifier"; + } else if (m.className === "PokemonResetNegativeStatStageModifier") { + m.className = "ResetNegativeStatStageModifier"; + } + }); + } +] as const; diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts index 0ef5c4d4611..48c0007118b 100644 --- a/src/test/utils/gameWrapper.ts +++ b/src/test/utils/gameWrapper.ts @@ -23,6 +23,7 @@ import KeyboardPlugin = Phaser.Input.Keyboard.KeyboardPlugin; import GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin; import EventEmitter = Phaser.Events.EventEmitter; import UpdateList = Phaser.GameObjects.UpdateList; +import { version } from "../../../package.json"; Object.defineProperty(window, "localStorage", { value: mockLocalStorage(), @@ -101,6 +102,7 @@ export default class GameWrapper { injectMandatory() { this.game.config = { seed: ["test"], + gameVersion: version }; this.scene.game = this.game; this.game.renderer = { diff --git a/src/ui/outdated-modal-ui-handler.ts b/src/ui/outdated-modal-ui-handler.ts deleted file mode 100644 index fc4b93f9b8a..00000000000 --- a/src/ui/outdated-modal-ui-handler.ts +++ /dev/null @@ -1,47 +0,0 @@ -import BattleScene from "../battle-scene"; -import { ModalConfig, ModalUiHandler } from "./modal-ui-handler"; -import { addTextObject, TextStyle } from "./text"; -import { Mode } from "./ui"; - -export default class OutdatedModalUiHandler extends ModalUiHandler { - constructor(scene: BattleScene, mode: Mode | null = null) { - super(scene, mode); - } - - getModalTitle(): string { - return ""; - } - - getWidth(): number { - return 160; - } - - getHeight(): number { - return 64; - } - - getMargin(): [number, number, number, number] { - return [ 0, 0, 48, 0 ]; - } - - getButtonLabels(): string[] { - return [ ]; - } - - setup(): void { - super.setup(); - - const label = addTextObject(this.scene, this.getWidth() / 2, this.getHeight() / 2, "Your client is currently outdated.\nPlease reload to update the game.\n\nIf this error persists, please clear your browser cache.", TextStyle.WINDOW, { fontSize: "48px", align: "center" }); - label.setOrigin(0.5, 0.5); - - this.modalContainer.add(label); - } - - show(args: any[]): boolean { - const config: ModalConfig = { - buttonActions: [] - }; - - return super.show([ config ]); - } -} diff --git a/src/ui/ui.ts b/src/ui/ui.ts index 373930c5d84..63cd48ab1cd 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -34,7 +34,6 @@ import SaveSlotSelectUiHandler from "./save-slot-select-ui-handler"; import TitleUiHandler from "./title-ui-handler"; import SavingIconHandler from "./saving-icon-handler"; import UnavailableModalUiHandler from "./unavailable-modal-ui-handler"; -import OutdatedModalUiHandler from "./outdated-modal-ui-handler"; import SessionReloadModalUiHandler from "./session-reload-modal-ui-handler"; import { Button } from "#enums/buttons"; import i18next from "i18next"; @@ -90,7 +89,6 @@ export enum Mode { LOADING, SESSION_RELOAD, UNAVAILABLE, - OUTDATED, CHALLENGE_SELECT, RENAME_POKEMON, RUN_HISTORY, @@ -134,7 +132,6 @@ const noTransitionModes = [ Mode.LOADING, Mode.SESSION_RELOAD, Mode.UNAVAILABLE, - Mode.OUTDATED, Mode.RENAME_POKEMON, Mode.TEST_DIALOGUE, Mode.AUTO_COMPLETE, @@ -200,7 +197,6 @@ export default class UI extends Phaser.GameObjects.Container { new LoadingModalUiHandler(scene), new SessionReloadModalUiHandler(scene), new UnavailableModalUiHandler(scene), - new OutdatedModalUiHandler(scene), new GameChallengesUiHandler(scene), new RenameFormUiHandler(scene), new RunHistoryUiHandler(scene), From 2f212f52eb8808bd60c4271835ba38d89158501a Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:31:30 -0700 Subject: [PATCH 22/24] fixed effectChanceOVerrride location + removed ability tags (#4677) Co-authored-by: frutescens --- src/data/ability.ts | 6 ++---- src/data/move.ts | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 6c4dededa04..5d2ccfc9d36 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -4913,8 +4913,7 @@ export function initAbilities() { .attr(TypeImmunityAddBattlerTagAbAttr, Type.FIRE, BattlerTagType.FIRE_BOOST, 1) .ignorable(), new Ability(Abilities.SHIELD_DUST, 3) - .attr(IgnoreMoveEffectsAbAttr) - .edgeCase(), // Does not work with secret power (unimplemented) + .attr(IgnoreMoveEffectsAbAttr), new Ability(Abilities.OWN_TEMPO, 3) .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) .attr(IntimidateImmunityAbAttr) @@ -4958,8 +4957,7 @@ export function initAbilities() { .attr(TypeImmunityStatStageChangeAbAttr, Type.ELECTRIC, Stat.SPATK, 1) .ignorable(), new Ability(Abilities.SERENE_GRACE, 3) - .attr(MoveEffectChanceMultiplierAbAttr, 2) - .edgeCase(), // does not work with secret power (unimplemented) + .attr(MoveEffectChanceMultiplierAbAttr, 2), new Ability(Abilities.SWIFT_SWIM, 3) .attr(StatMultiplierAbAttr, Stat.SPD, 2) .condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)), diff --git a/src/data/move.ts b/src/data/move.ts index 448008b733c..57307b49061 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -2891,8 +2891,6 @@ export class SecretPowerAttr extends MoveEffectAttr { this.effectChanceOverride = move.chance; const moveChance = this.getMoveChance(user, target, move, this.selfTarget); if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) { - // effectChanceOverride used in the application of the actual secondary effect - this.effectChanceOverride = 100; return true; } else { return false; @@ -2915,6 +2913,8 @@ export class SecretPowerAttr extends MoveEffectAttr { const biome = user.scene.arena.biomeType; secondaryEffect = this.determineBiomeEffect(biome); } + // effectChanceOverride used in the application of the actual secondary effect + secondaryEffect.effectChanceOverride = 100; return secondaryEffect.apply(user, target, move, []); } From c5b3220b86579c40bac954e4d481df6787c1928b Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:46:51 +0200 Subject: [PATCH 23/24] [Beta P3][UI] Fix item/cursor placement in reward screen (#4678) --- src/ui/modifier-select-ui-handler.ts | 31 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 0bae56c03b4..1948d75ac4c 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -17,6 +17,9 @@ import { IntegerHolder } from "./../utils"; import Phaser from "phaser"; export const SHOP_OPTIONS_ROW_LIMIT = 7; +const SINGLE_SHOP_ROW_YOFFSET = 12; +const DOUBLE_SHOP_ROW_YOFFSET = 24; +const OPTION_BUTTON_YPOSITION = -62; export default class ModifierSelectUiHandler extends AwaitableUiHandler { private modifierContainer: Phaser.GameObjects.Container; @@ -68,7 +71,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.checkButtonWidth = context.measureText(i18next.t("modifierSelectUiHandler:checkTeam")).width; } - this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, -64); + this.transferButtonContainer = this.scene.add.container((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 21, OPTION_BUTTON_YPOSITION); this.transferButtonContainer.setName("transfer-btn"); this.transferButtonContainer.setVisible(false); ui.add(this.transferButtonContainer); @@ -78,7 +81,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { transferButtonText.setOrigin(1, 0); this.transferButtonContainer.add(transferButtonText); - this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, -64); + this.checkButtonContainer = this.scene.add.container((this.scene.game.canvas.width) / 6 - 1, OPTION_BUTTON_YPOSITION); this.checkButtonContainer.setName("use-btn"); this.checkButtonContainer.setVisible(false); ui.add(this.checkButtonContainer); @@ -88,7 +91,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { checkButtonText.setOrigin(1, 0); this.checkButtonContainer.add(checkButtonText); - this.rerollButtonContainer = this.scene.add.container(16, -64); + this.rerollButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION); this.rerollButtonContainer.setName("reroll-brn"); this.rerollButtonContainer.setVisible(false); ui.add(this.rerollButtonContainer); @@ -104,7 +107,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.rerollCostText.setPositionRelative(rerollButtonText, rerollButtonText.displayWidth + 5, 1); this.rerollButtonContainer.add(this.rerollCostText); - this.lockRarityButtonContainer = this.scene.add.container(16, -64); + this.lockRarityButtonContainer = this.scene.add.container(16, OPTION_BUTTON_YPOSITION); this.lockRarityButtonContainer.setVisible(false); ui.add(this.lockRarityButtonContainer); @@ -191,7 +194,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const shopTypeOptions = !removeHealShop ? getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, baseShopCost.value) : []; - const optionsYOffset = shopTypeOptions.length >= SHOP_OPTIONS_ROW_LIMIT ? -8 : -24; + const optionsYOffset = shopTypeOptions.length > SHOP_OPTIONS_ROW_LIMIT ? -SINGLE_SHOP_ROW_YOFFSET : -DOUBLE_SHOP_ROW_YOFFSET; for (let m = 0; m < typeOptions.length; m++) { const sliceWidth = (this.scene.game.canvas.width / 6) / (typeOptions.length + 2); @@ -212,7 +215,7 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { const col = m < SHOP_OPTIONS_ROW_LIMIT ? m : m - SHOP_OPTIONS_ROW_LIMIT; const rowOptions = shopTypeOptions.slice(row ? SHOP_OPTIONS_ROW_LIMIT : 0, row ? undefined : SHOP_OPTIONS_ROW_LIMIT); const sliceWidth = (this.scene.game.canvas.width / 6) / (rowOptions.length + 2); - const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (40 - (28 * row - 1))), shopTypeOptions[m]); + const option = new ModifierOption(this.scene, sliceWidth * (col + 1) + (sliceWidth * 0.5), ((-this.scene.game.canvas.height / 12) - (this.scene.game.canvas.height / 32) - (42 - (28 * row - 1))), shopTypeOptions[m]); option.setScale(0.375); this.scene.add.existing(option); this.modifierContainer.add(option); @@ -456,16 +459,18 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { if (this.rowCursor === 1 && options.length === 0) { // Continue button when no shop items this.cursorObj.setScale(1.25); - this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22)); + this.cursorObj.setPosition((this.scene.game.canvas.width / 18) + 23, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); ui.showText(i18next.t("modifierSelectUiHandler:continueNextWaveDescription")); return ret; } const sliceWidth = (this.scene.game.canvas.width / 6) / (options.length + 2); if (this.rowCursor < 2) { - this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? 6 : 22)); + // Cursor on free items + this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 20, (-this.scene.game.canvas.height / 12) - (this.shopOptionsRows.length > 1 ? SINGLE_SHOP_ROW_YOFFSET - 2 : DOUBLE_SHOP_ROW_YOFFSET - 2)); } else { - this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-16 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1)))); + // Cursor on paying items + this.cursorObj.setPosition(sliceWidth * (cursor + 1) + (sliceWidth * 0.5) - 16, (-this.scene.game.canvas.height / 12 - this.scene.game.canvas.height / 32) - (-14 + 28 * (this.rowCursor - (this.shopOptionsRows.length - 1)))); } const type = options[this.cursor].modifierTypeOption.type; @@ -475,16 +480,16 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { this.moveInfoOverlay.show(allMoves[type.moveId]); } } else if (cursor === 0) { - this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? -72 : -60); + this.cursorObj.setPosition(6, this.lockRarityButtonContainer.visible ? OPTION_BUTTON_YPOSITION - 8 : OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:rerollDesc")); } else if (cursor === 1) { - this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, -60); + this.cursorObj.setPosition((this.scene.game.canvas.width - this.transferButtonWidth - this.checkButtonWidth) / 6 - 30, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:transferDesc")); } else if (cursor === 2) { - this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, -60); + this.cursorObj.setPosition((this.scene.game.canvas.width - this.checkButtonWidth) / 6 - 10, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:checkTeamDesc")); } else { - this.cursorObj.setPosition(6, -60); + this.cursorObj.setPosition(6, OPTION_BUTTON_YPOSITION + 4); ui.showText(i18next.t("modifierSelectUiHandler:lockRaritiesDesc")); } From de64fd77203e0c3040cb84e3afb3fa68a9073306 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Thu, 17 Oct 2024 23:52:46 -0400 Subject: [PATCH 24/24] [Misc] [Beta] Fix crash when loading save preview with Mystery Encounter Override active (#4683) --- src/battle-scene.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index ffba8e98d34..c6fff1e209a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3177,6 +3177,9 @@ export default class BattleScene extends SceneBase { let encounter: MysteryEncounter | null; if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; + if (canBypass) { + return encounter; + } } else if (canBypass) { encounter = allMysteryEncounters[encounterType ?? -1]; return encounter;