From 160b730921d8d58b925ae0c2c5d353d5ee3b4505 Mon Sep 17 00:00:00 2001 From: pom-eranian Date: Tue, 1 Oct 2024 15:24:08 -0400 Subject: [PATCH 01/75] [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 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 literal 6495 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 Date: Sun, 6 Oct 2024 23:29:57 -0400 Subject: [PATCH 02/75] [Beta][Test] Fix Scale Shot flaky test (#4564) Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- src/test/moves/scale_shot.test.ts | 57 ++++++++++++++++++------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/test/moves/scale_shot.test.ts b/src/test/moves/scale_shot.test.ts index 2730d05306d..e4d768fa13a 100644 --- a/src/test/moves/scale_shot.test.ts +++ b/src/test/moves/scale_shot.test.ts @@ -1,3 +1,5 @@ +import { BattlerIndex } from "#app/battle"; +import { allMoves } from "#app/data/move"; import { DamagePhase } from "#app/phases/damage-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; @@ -8,7 +10,7 @@ import { Species } from "#enums/species"; import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; describe("Moves - Scale Shot", () => { let phaserGame: Phaser.Game; @@ -30,45 +32,54 @@ describe("Moves - Scale Shot", () => { .moveset([ Moves.SCALE_SHOT ]) .battleType("single") .disableCrits() - .starterSpecies(Species.MINCCINO) .ability(Abilities.NO_GUARD) .passiveAbility(Abilities.SKILL_LINK) - .enemyAbility(Abilities.SHEER_FORCE) - .enemyPassiveAbility(Abilities.STALL) - .enemyMoveset(Moves.SKILL_SWAP) - .enemyLevel(5); + .enemyMoveset(Moves.SPLASH) + .enemyLevel(3); }); it("applies stat changes after last hit", async () => { - await game.classicMode.startBattle([ Species.FORRETRESS ]); + game.override.enemySpecies(Species.FORRETRESS); + + await game.classicMode.startBattle([ Species.MINCCINO ]); const minccino = game.scene.getPlayerPokemon()!; game.move.select(Moves.SCALE_SHOT); + + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.phaseInterceptor.to(MoveEffectPhase); await game.phaseInterceptor.to(DamagePhase); + + //check that stats haven't changed after one or two hits have occurred await game.phaseInterceptor.to(MoveEffectPhase); - expect (minccino?.getStatStage(Stat.DEF)).toBe(0); - expect (minccino?.getStatStage(Stat.SPD)).toBe(0); + expect(minccino.getStatStage(Stat.DEF)).toBe(0); + expect(minccino.getStatStage(Stat.SPD)).toBe(0); + + //check that stats changed on last hit await game.phaseInterceptor.to(MoveEndPhase); - expect (minccino.getStatStage(Stat.DEF)).toBe(-1); - expect (minccino.getStatStage(Stat.SPD)).toBe(1); + expect(minccino.getStatStage(Stat.DEF)).toBe(-1); + expect(minccino.getStatStage(Stat.SPD)).toBe(1); }); it("unaffected by sheer force", async () => { - await game.classicMode.startBattle([ Species.WOBBUFFET ]); + const moveToCheck = allMoves[Moves.SCALE_SHOT]; + const basePower = moveToCheck.power; + + game.override.enemySpecies(Species.WOBBUFFET); + + vi.spyOn(moveToCheck, "calculateBattlePower"); + + await game.classicMode.startBattle([ Species.MINCCINO ]); const minccino = game.scene.getPlayerPokemon()!; - const wobbuffet = game.scene.getEnemyPokemon()!; - wobbuffet.setStat(Stat.HP, 100, true); - wobbuffet.hp = 100; + game.move.select(Moves.SCALE_SHOT); await game.phaseInterceptor.to(TurnEndPhase); - const hpafter1 = wobbuffet.hp; + //effect not nullified by sheer force - expect (minccino.getStatStage(Stat.DEF)).toBe(-1); - expect (minccino.getStatStage(Stat.SPD)).toBe(1); - game.move.select(Moves.SCALE_SHOT); - await game.phaseInterceptor.to(MoveEndPhase); - const hpafter2 = wobbuffet.hp; - //check damage not boosted- make damage before sheer force a little lower than theoretical boosted sheer force damage - expect (100 - hpafter1).toBe(hpafter1 - hpafter2); + expect(minccino.getStatStage(Stat.DEF)).toBe(-1); + expect(minccino.getStatStage(Stat.SPD)).toBe(1); + + //power not boosted by sheer force + expect(moveToCheck.calculateBattlePower).toHaveReturnedWith(basePower); }); }); From f5fa478eb8afbfda7f7468e6fc535abe7fd1e03c Mon Sep 17 00:00:00 2001 From: Acelynn Zhang <102631387+acelynnzhang@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:01:15 -0500 Subject: [PATCH 03/75] [P1] Fix crash when starting a challenge run after revisiting challenge select screen (#4603) Ensure EncounterPhase initializes correctly at the start of the game after revisting the challenge selection screen. Fixes #4520 --- src/ui/starter-select-ui-handler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 98a563301e4..9623d78c51e 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -46,6 +46,7 @@ import { StarterContainer } from "#app/ui/starter-container"; import { DropDownColumn, FilterBar } from "#app/ui/filter-bar"; import { ScrollBar } from "#app/ui/scroll-bar"; import { SelectChallengePhase } from "#app/phases/select-challenge-phase"; +import { EncounterPhase } from "#app/phases/encounter-phase"; import { TitlePhase } from "#app/phases/title-phase"; import { Abilities } from "#enums/abilities"; import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; @@ -3468,6 +3469,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.scene.clearPhaseQueue(); if (this.scene.gameMode.isChallenge) { this.scene.pushPhase(new SelectChallengePhase(this.scene)); + this.scene.pushPhase(new EncounterPhase(this.scene, false)); } else { this.scene.pushPhase(new TitlePhase(this.scene)); } From 8980513a83b257b4a6d0723dcc30d13cfc9b4c04 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:03:00 -0400 Subject: [PATCH 04/75] [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 05/75] 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 a1ca7e632b7054fb7f03fae6b4f5fe2d560dbd2c Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Tue, 8 Oct 2024 05:32:51 -0700 Subject: [PATCH 06/75] [Move] Triple Arrows effect chance for stat change is now 50% (#4543) * Triple Arrows effect chance for stat change is now properly 50% * Add tsdocs to `StatStageChangeAttr` * Add test for Serene Grace interaction * Fix linting --------- Co-authored-by: Mumble <171087428+frutescens@users.noreply.github.com> --- src/data/move.ts | 49 +++++++++++++---------- src/test/moves/triple_arrows.test.ts | 60 ++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 src/test/moves/triple_arrows.test.ts diff --git a/src/data/move.ts b/src/data/move.ts index f795d265336..08c00829b48 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -970,13 +970,16 @@ export class MoveEffectAttr extends MoveAttr { public lastHitOnly: boolean; /** Should this effect only apply on the first target hit? */ public firstTargetOnly: boolean; + /** Overrides the secondary effect chance for this attr if set. */ + public effectChanceOverride?: number; - constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false) { + constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false, effectChanceOverride?: number) { super(selfTarget); - this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY; + this.trigger = trigger ?? MoveEffectTrigger.POST_APPLY; this.firstHitOnly = firstHitOnly; this.lastHitOnly = lastHitOnly; this.firstTargetOnly = firstTargetOnly; + this.effectChanceOverride = effectChanceOverride; } /** @@ -1001,15 +1004,15 @@ export class MoveEffectAttr extends MoveAttr { /** * Gets the used move's additional effect chance. - * If user's ability has MoveEffectChanceMultiplierAbAttr or IgnoreMoveEffectsAbAttr modifies the base chance. + * Chance is modified by {@linkcode MoveEffectChanceMultiplierAbAttr} and {@linkcode IgnoreMoveEffectsAbAttr}. * @param user {@linkcode Pokemon} using this move - * @param target {@linkcode Pokemon} target of this move + * @param target {@linkcode Pokemon | Target} of this move * @param move {@linkcode Move} being used - * @param selfEffect {@linkcode Boolean} if move targets user. - * @returns Move chance value. + * @param selfEffect `true` if move targets user. + * @returns Move effect chance value. */ getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): integer { - const moveChance = new Utils.NumberHolder(move.chance); + const moveChance = new Utils.NumberHolder(this.effectChanceOverride ?? move.chance); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, false, moveChance, move, target, selfEffect, showAbility); @@ -2752,14 +2755,17 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { /** * Attribute used for moves that change stat stages - * @param stats {@linkcode BattleStat} array of stats to be changed - * @param stages stages by which to change the stats, from -6 to 6 - * @param selfTarget whether the changes are applied to the user (true) or the target (false) - * @param condition {@linkcode MoveConditionFunc} optional condition to trigger the stat change - * @param firstHitOnly whether the stat change only applies on the first hit of a multi hit move - * @param moveEffectTrigger {@linkcode MoveEffectTrigger} the trigger for the effect to take place - * @param firstTargetOnly whether, if this is a multi target move, to only apply the effect after the first target is hit, rather than once for each target - * @param lastHitOnly whether the effect should only apply after the last hit of a multi hit move + * + * @param stats {@linkcode BattleStat} Array of stat(s) to change + * @param stages How many stages to change the stat(s) by, [-6, 6] + * @param selfTarget `true` if the move is self-targetting + * @param condition {@linkcode MoveConditionFunc} Optional condition to be checked in order to apply the changes + * @param showMessage `true` to display a message; default `true` + * @param firstHitOnly `true` if only the first hit of a multi hit move should cause a stat stage change; default `false` + * @param moveEffectTrigger {@linkcode MoveEffectTrigger} When the stat change should trigger; default {@linkcode MoveEffectTrigger.HIT} + * @param firstTargetOnly `true` if a move that hits multiple pokemon should only trigger the stat change if it hits at least one pokemon, rather than once per hit pokemon; default `false` + * @param lastHitOnly `true` if the effect should only apply after the last hit of a multi hit move; default `false` + * @param effectChanceOverride Will override the move's normal secondary effect chance if specified * * @extends MoveEffectAttr * @see {@linkcode apply} @@ -2767,14 +2773,14 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr { export class StatStageChangeAttr extends MoveEffectAttr { public stats: BattleStat[]; public stages: integer; - private condition: MoveConditionFunc | null; + private condition?: MoveConditionFunc | null; private showMessage: boolean; - constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false) { - super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly); + constructor(stats: BattleStat[], stages: integer, selfTarget?: boolean, condition?: MoveConditionFunc | null, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false, lastHitOnly: boolean = false, effectChanceOverride?: number) { + super(selfTarget, moveEffectTrigger, firstHitOnly, lastHitOnly, firstTargetOnly, effectChanceOverride); this.stats = stats; this.stages = stages; - this.condition = condition!; // TODO: is this bang correct? + this.condition = condition; this.showMessage = showMessage; } @@ -9556,9 +9562,8 @@ export function initMoves() { new AttackMove(Moves.TRIPLE_ARROWS, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8) .makesContact(false) .attr(HighCritAttr) - .attr(StatStageChangeAttr, [ Stat.DEF ], -1) - .attr(FlinchAttr) - .partial(), + .attr(StatStageChangeAttr, [ Stat.DEF ], -1, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 50) + .attr(FlinchAttr), new AttackMove(Moves.INFERNAL_PARADE, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8) .attr(StatusEffectAttr, StatusEffect.BURN) .attr(MovePowerMultiplierAttr, (user, target, move) => target.status ? 2 : 1), diff --git a/src/test/moves/triple_arrows.test.ts b/src/test/moves/triple_arrows.test.ts new file mode 100644 index 00000000000..98ad29997df --- /dev/null +++ b/src/test/moves/triple_arrows.test.ts @@ -0,0 +1,60 @@ +import { allMoves, FlinchAttr, StatStageChangeAttr } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; + +describe("Moves - Triple Arrows", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + const tripleArrows = allMoves[Moves.TRIPLE_ARROWS]; + const flinchAttr = tripleArrows.getAttrs(FlinchAttr)[0]; + const defDropAttr = tripleArrows.getAttrs(StatStageChangeAttr)[0]; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(Abilities.BALL_FETCH) + .moveset([ Moves.TRIPLE_ARROWS ]) + .battleType("single") + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.STURDY) + .enemyMoveset(Moves.SPLASH); + + vi.spyOn(flinchAttr, "getMoveChance"); + vi.spyOn(defDropAttr, "getMoveChance"); + }); + + it("has a 30% flinch chance and 50% defense drop chance", async () => { + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.TRIPLE_ARROWS); + await game.phaseInterceptor.to("BerryPhase"); + + expect(flinchAttr.getMoveChance).toHaveReturnedWith(30); + expect(defDropAttr.getMoveChance).toHaveReturnedWith(50); + }); + + it("is affected normally by Serene Grace", async () => { + game.override.ability(Abilities.SERENE_GRACE); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.TRIPLE_ARROWS); + await game.phaseInterceptor.to("BerryPhase"); + + expect(flinchAttr.getMoveChance).toHaveReturnedWith(60); + expect(defDropAttr.getMoveChance).toHaveReturnedWith(100); + }); +}); From 9bb6398385847ac5594100597b87e87ed09fe279 Mon Sep 17 00:00:00 2001 From: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:03:47 -0400 Subject: [PATCH 07/75] [Sprite] Fix stray pixels in Kirlia's animation (#4612) * [Sprite] Fix Kirlia padding bleedover * [Sprite] Fix shiny Kirlia padding bleedover --- public/images/pokemon/281.json | 8 ++++---- public/images/pokemon/shiny/281.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/images/pokemon/281.json b/public/images/pokemon/281.json index 8e865cdc935..cb1a43f256f 100644 --- a/public/images/pokemon/281.json +++ b/public/images/pokemon/281.json @@ -399,13 +399,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { @@ -420,13 +420,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { diff --git a/public/images/pokemon/shiny/281.json b/public/images/pokemon/shiny/281.json index 684be77edf9..64e10c7f9a6 100644 --- a/public/images/pokemon/shiny/281.json +++ b/public/images/pokemon/shiny/281.json @@ -399,13 +399,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { @@ -420,13 +420,13 @@ "x": 0, "y": 6, "w": 36, - "h": 55 + "h": 54 }, "frame": { "x": 72, "y": 55, "w": 36, - "h": 55 + "h": 54 } }, { From 6e10f6600fe30793c7ff56ce98f27ae1b3486d5e Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:08:25 +0200 Subject: [PATCH 08/75] [P2] Fix damage achievements not awarding (#4613) --- src/field/pokemon.ts | 2 +- src/phases/level-up-phase.ts | 2 +- src/system/achv.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index a0ad4e8a52f..241524df1b9 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2758,7 +2758,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (damage > 0) { if (source.isPlayer()) { - this.scene.validateAchvs(DamageAchv, damage); + this.scene.validateAchvs(DamageAchv, new Utils.NumberHolder(damage)); if (damage > this.scene.gameData.gameStats.highestDamage) { this.scene.gameData.gameStats.highestDamage = damage; } diff --git a/src/phases/level-up-phase.ts b/src/phases/level-up-phase.ts index a99e038acba..a2fa8a16533 100644 --- a/src/phases/level-up-phase.ts +++ b/src/phases/level-up-phase.ts @@ -28,7 +28,7 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { this.scene.gameData.gameStats.highestLevel = this.level; } - this.scene.validateAchvs(LevelAchv, new Utils.IntegerHolder(this.level)); + this.scene.validateAchvs(LevelAchv, new Utils.NumberHolder(this.level)); const pokemon = this.getPokemon(); const prevStats = pokemon.stats.slice(0); diff --git a/src/system/achv.ts b/src/system/achv.ts index 7bac631d7aa..7329dd41fd5 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -109,7 +109,7 @@ export class DamageAchv extends Achv { damageAmount: integer; constructor(localizationKey: string, name: string, damageAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.damageAmount); + super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.damageAmount); this.damageAmount = damageAmount; } } @@ -118,7 +118,7 @@ export class HealAchv extends Achv { healAmount: integer; constructor(localizationKey: string, name: string, healAmount: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] as Utils.NumberHolder).value >= this.healAmount); + super(localizationKey, name, "", iconImage, score, (_scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.healAmount); this.healAmount = healAmount; } } @@ -127,7 +127,7 @@ export class LevelAchv extends Achv { level: integer; constructor(localizationKey: string, name: string, level: integer, iconImage: string, score: integer) { - super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] as Utils.IntegerHolder).value >= this.level); + super(localizationKey, name, "", iconImage, score, (scene: BattleScene, args: any[]) => (args[0] instanceof Utils.NumberHolder ? args[0].value : args[0]) >= this.level); this.level = level; } } From 0ede7b057d9699e6c3a1d00ae2b2d55e6363f927 Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:10:54 +0200 Subject: [PATCH 09/75] [P3][UI] Fix egg gacha overlay not getting cleared properly (#4600) --- src/overrides.ts | 1 + src/ui/egg-gacha-ui-handler.ts | 49 ++++++++++++++++++++--------- src/ui/starter-select-ui-handler.ts | 3 +- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/overrides.ts b/src/overrides.ts index 27886ded2f2..211d430a835 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -156,6 +156,7 @@ class DefaultOverrides { readonly EGG_VARIANT_OVERRIDE: VariantTier | null = null; readonly EGG_FREE_GACHA_PULLS_OVERRIDE: boolean = false; readonly EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0; + readonly UNLIMITED_EGG_COUNT_OVERRIDE: boolean = false; // ------------------------- // MYSTERY ENCOUNTER OVERRIDES diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 56cd5299949..366f1604740 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -34,6 +34,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { private cursorObj: Phaser.GameObjects.Image; private transitioning: boolean; private transitionCancelled: boolean; + private summaryFinished: boolean; private defaultText: string; private scale: number = 0.1666666667; @@ -479,7 +480,12 @@ export default class EggGachaUiHandler extends MessageUiHandler { } showSummary(eggs: Egg[]): void { - this.transitioning = false; + // the overlay will appear faster if the egg pulling animation was skipped + const overlayEaseInDuration = this.getDelayValue(750); + + this.summaryFinished = false; + this.transitionCancelled = false; + this.setTransitioning(true); this.eggGachaSummaryContainer.setVisible(true); const eggScale = eggs.length < 20 ? 1 : 0.5; @@ -488,12 +494,14 @@ export default class EggGachaUiHandler extends MessageUiHandler { targets: this.eggGachaOverlay, alpha: 0.5, ease: "Sine.easeOut", - duration: 750, + duration: overlayEaseInDuration, onComplete: () => { const rowItems = 5; const rows = Math.ceil(eggs.length / rowItems); const cols = Math.min(eggs.length, rowItems); const height = this.eggGachaOverlay.displayHeight - this.eggGachaMessageBox.displayHeight; + + // Create sprites for each egg const eggContainers = eggs.map((egg, t) => { const col = t % rowItems; const row = Math.floor(t / rowItems); @@ -515,14 +523,24 @@ export default class EggGachaUiHandler extends MessageUiHandler { return ret; }); - eggContainers.forEach((eggContainer, e) => { - this.scene.tweens.add({ - targets: eggContainer, - delay: this.getDelayValue(e * 100), - duration: this.getDelayValue(350), - scale: eggScale, - ease: "Sine.easeOut" - }); + // If action/cancel was pressed when the overlay was easing in, show all eggs at once + // Otherwise show the eggs one by one with a small delay between each + eggContainers.forEach((eggContainer, index) => { + const delay = !this.transitionCancelled ? this.getDelayValue(index * 100) : 0; + this.scene.time.delayedCall(delay, () => + this.scene.tweens.add({ + targets: eggContainer, + duration: this.getDelayValue(350), + scale: eggScale, + ease: "Sine.easeOut", + onComplete: () => { + if (index === eggs.length - 1) { + this.setTransitioning(false); + this.summaryFinished = true; + } + } + })); + }); } }); @@ -540,6 +558,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { this.eggGachaSummaryContainer.setAlpha(1); this.eggGachaSummaryContainer.removeAll(true); this.setTransitioning(false); + this.summaryFinished = false; this.eggGachaOptionsContainer.setVisible(true); } }); @@ -613,7 +632,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { } else { if (this.eggGachaSummaryContainer.visible) { - if (button === Button.ACTION || button === Button.CANCEL) { + if (this.summaryFinished && (button === Button.ACTION || button === Button.CANCEL)) { this.hideSummary(); success = true; } @@ -625,7 +644,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 99) { + } else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.REGULAR, 1); } @@ -640,7 +659,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 95) { + } else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.PLUS, 1); } @@ -657,7 +676,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { || (this.cursor === 3 && !this.scene.gameData.voucherCounts[VoucherType.PREMIUM] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE)) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 90) { + } else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (this.cursor === 3) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.PREMIUM, 1); @@ -678,7 +697,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { error = true; this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 75) { + } else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { this.consumeVouchers(VoucherType.GOLDEN, 1); } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 9623d78c51e..5bbe765947e 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1789,7 +1789,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { options.push({ label: `x${sameSpeciesEggCost} ${i18next.t("starterSelectUiHandler:sameSpeciesEgg")}`, handler: () => { - if (this.scene.gameData.eggs.length < 99 && (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) { + if ((this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) + && (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost)) { if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) { starterData.candyCount -= sameSpeciesEggCost; } From 57a967890a40e2625f2de3b8ba6a97dcc7688388 Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:11:21 +0200 Subject: [PATCH 10/75] [Offline P1] Fix wrong local save being deleted when creating a new run (#4598) --- src/system/game-data.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/system/game-data.ts b/src/system/game-data.ts index eada3d270ef..0d2f35ae728 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -1125,10 +1125,16 @@ export class GameData { }); } + /** + * Delete the session data at the given slot when overwriting a save file + * For deleting the session of a finished run, use {@linkcode tryClearSession} + * @param slotId the slot to clear + * @returns Promise with result `true` if the session was deleted successfully, `false` otherwise + */ deleteSession(slotId: integer): Promise { return new Promise(resolve => { if (bypassLogin) { - localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); return resolve(true); } @@ -1139,7 +1145,7 @@ export class GameData { Utils.apiFetch(`savedata/session/delete?slot=${slotId}&clientSessionId=${clientSessionId}`, true).then(response => { if (response.ok) { loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? - localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); resolve(true); } return response.text(); @@ -1190,7 +1196,9 @@ export class GameData { /** - * Attempt to clear session data. After session data is removed, attempt to update user info so the menu updates + * Attempt to clear session data after the end of a run + * After session data is removed, attempt to update user info so the menu updates + * To delete an unfinished run instead, use {@linkcode deleteSession} */ async tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> { let result: [boolean, boolean] = [ false, false ]; @@ -1204,7 +1212,7 @@ export class GameData { if (response.ok) { loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? - localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); } const jsonResponse: PokerogueApiClearSessionData = await response.json(); From 39cebb76d01f801377841bdc1a65161cee4835b6 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:30:48 -0700 Subject: [PATCH 11/75] [Bug] i18n messages files fix (#4611) * fix matching for i18n messages files * update public/locales head --- public/locales | 2 +- src/plugins/i18n.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/locales b/public/locales index b44ee217378..fc4a1effd51 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit b44ee2173788018ffd5dc6b7b7fa159be5b9d514 +Subproject commit fc4a1effd5170def3c8314208a52cd0d8e6913ef diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index be4c6983c0a..84c12b91df3 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -81,6 +81,8 @@ const namespaceMap = { miscDialogue: "dialogue-misc", battleSpecDialogue: "dialogue-final-boss", doubleBattleDialogue: "dialogue-double-battle", + splashMessages: "splash-texts", + mysteryEncounterMessages: "mystery-encounter-messages", }; //#region Functions From d8c914c7686c0fa4c74609ebed97c03f65a46f3e Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:44:23 -0700 Subject: [PATCH 12/75] [Beta P3] Fix i18n namespaces map for `mysteryEncounterMessages` from `mystery-encounter-messages` -> `mystery-encounter-texts` (#4617) Something I missed in #4611 --- src/plugins/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 84c12b91df3..d24484bbf9d 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -82,7 +82,7 @@ const namespaceMap = { battleSpecDialogue: "dialogue-final-boss", doubleBattleDialogue: "dialogue-double-battle", splashMessages: "splash-texts", - mysteryEncounterMessages: "mystery-encounter-messages", + mysteryEncounterMessages: "mystery-encounter-texts", }; //#region Functions From deb2035610091eedb884c60ee5f2f4cd14365f50 Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:30:28 +0200 Subject: [PATCH 13/75] [Beta][P2] Fix Grip Claw (#4614) * [Beta][P2] Fix Grip Claw * Add test for Grip Claw * [test] improve grip claw's test readability * PR feedback --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/modifier/modifier.ts | 7 ++- src/test/items/grip_claw.test.ts | 96 +++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index b658d3b5277..6c9b5db1bca 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3084,11 +3084,12 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier { * Steals an item from a set of target Pokemon. * This prioritizes high-tier held items when selecting the item to steal. * @param pokemon The {@linkcode Pokemon} holding this item + * @param target The {@linkcode Pokemon} to steal from (optional) * @param _args N/A * @returns `true` if an item was stolen; false otherwise. */ - override apply(pokemon: Pokemon, ..._args: unknown[]): boolean { - const opponents = this.getTargets(pokemon); + override apply(pokemon: Pokemon, target?: Pokemon, ..._args: unknown[]): boolean { + const opponents = this.getTargets(pokemon, target); if (!opponents.length) { return false; @@ -3187,7 +3188,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { * @see {@linkcode HeldItemTransferModifier} */ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModifier { - private chance: number; + public readonly chance: number; constructor(type: ModifierType, pokemonId: number, chancePercent: number, stackCount?: number) { super(type, pokemonId, stackCount); diff --git a/src/test/items/grip_claw.test.ts b/src/test/items/grip_claw.test.ts index 9d44a9e4672..2909549af87 100644 --- a/src/test/items/grip_claw.test.ts +++ b/src/test/items/grip_claw.test.ts @@ -1,16 +1,14 @@ import { BattlerIndex } from "#app/battle"; -import { allMoves } from "#app/data/move"; -import { Abilities } from "#app/enums/abilities"; -import { BerryType } from "#app/enums/berry-type"; -import { Moves } from "#app/enums/moves"; -import { Species } from "#app/enums/species"; -import { MoveEndPhase } from "#app/phases/move-end-phase"; +import Pokemon from "#app/field/pokemon"; +import { ContactHeldItemTransferChanceModifier } from "#app/modifier/modifier"; +import { Abilities } from "#enums/abilities"; +import { BerryType } from "#enums/berry-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phase from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -// 20 seconds - describe("Items - Grip Claw", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -30,39 +28,85 @@ describe("Items - Grip Claw", () => { game.override .battleType("double") - .moveset([ Moves.POPULATION_BOMB, Moves.SPLASH ]) + .moveset([ Moves.TACKLE, Moves.SPLASH, Moves.ATTRACT ]) .startingHeldItems([ - { name: "GRIP_CLAW", count: 5 }, // TODO: Find a way to mock the steal chance of grip claw - { name: "MULTI_LENS", count: 3 }, + { name: "GRIP_CLAW", count: 1 }, ]) .enemySpecies(Species.SNORLAX) - .ability(Abilities.KLUTZ) + .enemyAbility(Abilities.UNNERVE) + .ability(Abilities.UNNERVE) .enemyMoveset(Moves.SPLASH) .enemyHeldItems([ { name: "BERRY", type: BerryType.SITRUS, count: 2 }, { name: "BERRY", type: BerryType.LUM, count: 2 }, ]) - .startingLevel(100) .enemyLevel(100); - vi.spyOn(allMoves[Moves.POPULATION_BOMB], "accuracy", "get").mockReturnValue(100); }); - it( - "should only steal items from the attack target", - async () => { - await game.startBattle([ Species.PANSEAR, Species.ROWLET ]); + it("should steal items on contact and only from the attack target", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); - const enemyPokemon = game.scene.getEnemyField(); + const [ playerPokemon, ] = game.scene.getPlayerField(); - const enemyHeldItemCt = enemyPokemon.map(p => p.getHeldItems.length); + const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; + vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); - game.move.select(Moves.POPULATION_BOMB, 0, BattlerIndex.ENEMY); - game.move.select(Moves.SPLASH, 1); + const enemyPokemon = game.scene.getEnemyField(); - await game.phaseInterceptor.to(MoveEndPhase, false); + const playerHeldItemCount = getHeldItemCount(playerPokemon); + const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + expect(enemy2HeldItemCount).toBeGreaterThan(0); - expect(enemyPokemon[1].getHeldItems.length).toBe(enemyHeldItemCt[1]); - } - ); + game.move.select(Moves.TACKLE, 0, BattlerIndex.ENEMY_2); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); + const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + + expect(playerHeldItemCountAfter).toBe(playerHeldItemCount + 1); + expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); + expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount - 1); + }); + + it("should not steal items when using a targetted, non attack move", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + const [ playerPokemon, ] = game.scene.getPlayerField(); + + const gripClaw = playerPokemon.getHeldItems()[0] as ContactHeldItemTransferChanceModifier; + vi.spyOn(gripClaw, "chance", "get").mockReturnValue(100); + + const enemyPokemon = game.scene.getEnemyField(); + + const playerHeldItemCount = getHeldItemCount(playerPokemon); + const enemy1HeldItemCount = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCount = getHeldItemCount(enemyPokemon[1]); + expect(enemy2HeldItemCount).toBeGreaterThan(0); + + game.move.select(Moves.ATTRACT, 0, BattlerIndex.ENEMY_2); + game.move.select(Moves.SPLASH, 1); + + await game.phaseInterceptor.to("BerryPhase", false); + + const playerHeldItemCountAfter = getHeldItemCount(playerPokemon); + const enemy1HeldItemCountsAfter = getHeldItemCount(enemyPokemon[0]); + const enemy2HeldItemCountsAfter = getHeldItemCount(enemyPokemon[1]); + + expect(playerHeldItemCountAfter).toBe(playerHeldItemCount); + expect(enemy1HeldItemCountsAfter).toBe(enemy1HeldItemCount); + expect(enemy2HeldItemCountsAfter).toBe(enemy2HeldItemCount); + }); }); + +/* + * Gets the total number of items a Pokemon holds + */ +function getHeldItemCount(pokemon: Pokemon) { + return pokemon.getHeldItems().reduce((currentTotal, item) => currentTotal + item.getStackCount(), 0); +} + From d2c579cf2a2d22639ba1c3998f36e27f4e5d5b3a Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:32:20 +0200 Subject: [PATCH 14/75] [P2] Prevent generating Pokemon with duplicate IDs in daily runs (#4623) --- src/phases/title-phase.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/phases/title-phase.ts b/src/phases/title-phase.ts index 115e4f640a2..58683cf8ec8 100644 --- a/src/phases/title-phase.ts +++ b/src/phases/title-phase.ts @@ -196,7 +196,7 @@ export class TitlePhase extends Phase { this.scene.gameMode = getGameMode(GameModes.DAILY); this.scene.setSeed(seed); - this.scene.resetSeed(1); + this.scene.resetSeed(0); this.scene.money = this.scene.gameMode.getStartingMoney(); From ffe941d235f6f4923bc5b87e0d29701a609c4bba Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:04:13 -0700 Subject: [PATCH 15/75] [Feature][UI] Save Preview (#4410) * Making 3 Option UI real * idk anymore * Revert "Making 3 Option UI real" This reverts commit beaad44c1eb098a09cfd2d04043d878d24f494c1. * Let's see * Current issues - scrolling upwards and correct cursor landing * argh * Fixed reactive scrolling * Adding ME handling * set up descriptions * Cleaned up UI i think * stupid alder * Added double trainer handling + changed enum name * Apply suggestions from code review Thank you Moka! Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Arrow Visibility now depends on Session Slot hasData * documentation * Simplified calls to revertSessionSlot + changed function name per feedback * Fixed scrollCursor issue. * added comment * Update src/ui/save-slot-select-ui-handler.ts Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Fixed sound played + added better conditional * Balance Team.... * ME related changes * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Update src/data/mystery-encounters/mystery-encounter.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/data/mystery-encounters/mystery-encounter.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Sending Doubles-fix * eslint.. --------- Co-authored-by: frutescens Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/battle-scene.ts | 6 +- .../encounters/a-trainers-test-encounter.ts | 1 + .../encounters/absolute-avarice-encounter.ts | 1 + .../an-offer-you-cant-refuse-encounter.ts | 1 + .../encounters/berries-abound-encounter.ts | 1 + .../encounters/bug-type-superfan-encounter.ts | 1 + .../encounters/clowning-around-encounter.ts | 1 + .../encounters/dancing-lessons-encounter.ts | 1 + .../encounters/dark-deal-encounter.ts | 1 + .../encounters/delibirdy-encounter.ts | 1 + .../department-store-sale-encounter.ts | 1 + .../encounters/field-trip-encounter.ts | 1 + .../encounters/fiery-fallout-encounter.ts | 1 + .../encounters/fight-or-flight-encounter.ts | 1 + .../encounters/fun-and-games-encounter.ts | 1 + .../global-trade-system-encounter.ts | 1 + .../encounters/lost-at-sea-encounter.ts | 1 + .../mysterious-challengers-encounter.ts | 1 + .../encounters/mysterious-chest-encounter.ts | 1 + .../encounters/part-timer-encounter.ts | 1 + .../encounters/safari-zone-encounter.ts | 1 + .../shady-vitamin-dealer-encounter.ts | 1 + .../slumbering-snorlax-encounter.ts | 1 + .../teleporting-hijinks-encounter.ts | 1 + .../the-expert-pokemon-breeder-encounter.ts | 1 + .../the-pokemon-salesman-encounter.ts | 1 + .../encounters/the-strong-stuff-encounter.ts | 1 + .../the-winstrate-challenge-encounter.ts | 1 + .../encounters/training-session-encounter.ts | 1 + .../encounters/trash-to-treasure-encounter.ts | 1 + .../encounters/uncommon-breed-encounter.ts | 1 + .../encounters/weird-dream-encounter.ts | 1 + .../mystery-encounters/mystery-encounter.ts | 14 +- src/ui/run-history-ui-handler.ts | 3 +- src/ui/run-info-ui-handler.ts | 136 +++++++++++++++--- src/ui/save-slot-select-ui-handler.ts | 94 +++++++++--- 36 files changed, 246 insertions(+), 38 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index cc6934f20d1..a586b565e13 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3161,13 +3161,17 @@ export default class BattleScene extends SceneBase { /** * Loads or generates a mystery encounter * @param encounterType used to load session encounter when restarting game, etc. + * @param canBypass optional boolean to indicate that the request is coming from a function that needs to access a Mystery Encounter outside of gameplay requirements * @returns */ - getMysteryEncounter(encounterType?: MysteryEncounterType): MysteryEncounter { + getMysteryEncounter(encounterType?: MysteryEncounterType, canBypass?: boolean): MysteryEncounter { // Loading override or session encounter let encounter: MysteryEncounter | null; if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; + } else if (canBypass) { + encounter = allMysteryEncounters[encounterType ?? -1]; + return encounter; } else { encounter = !isNullOrUndefined(encounterType) ? allMysteryEncounters[encounterType] : null; } 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 13e187179d4..f3b886ac0ac 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -128,6 +128,7 @@ export const ATrainersTestEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index c98947a3f93..70b2d50fe99 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -166,6 +166,7 @@ export const AbsoluteAvariceEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index e445a8f481d..ab892ae00f2 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -64,6 +64,7 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 3e5d75727b1..095f8a8473b 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -110,6 +110,7 @@ export const BerriesAboundEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index 20c0569c725..b5d47cf6912 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -276,6 +276,7 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index 6c028d4619a..be52ab42c9d 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -148,6 +148,7 @@ export const ClowningAroundEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index cb07bf06a81..0f784739777 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -102,6 +102,7 @@ export const DancingLessonsEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index fc8c8088d58..5ad6630386f 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -117,6 +117,7 @@ export const DarkDealEncounter: MysteryEncounter = .withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withScenePartySizeRequirement(2, 6, true) // Must have at least 2 pokemon in party .withCatchAllowed(true) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index a11dc8cbe72..5686d0f6ce5 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -84,6 +84,7 @@ export const DelibirdyEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index 1505768f968..10034d19263 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -51,6 +51,7 @@ export const DepartmentStoreSaleEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index a75e5ef6a77..bf5fb28163b 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -52,6 +52,7 @@ export const FieldTripEncounter: MysteryEncounter = }, ]) .withAutoHideIntroVisuals(false) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 9e7652e24ea..d44e7bae596 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -122,6 +122,7 @@ export const FieryFalloutEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index a04521839fe..380662ca817 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -120,6 +120,7 @@ export const FightOrFlightEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 2b103e0a293..549faa01fa1 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -76,6 +76,7 @@ export const FunAndGamesEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 7b929ea5e7b..bafc1901e5e 100644 --- a/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts +++ b/src/data/mystery-encounters/encounters/global-trade-system-encounter.ts @@ -96,6 +96,7 @@ export const GlobalTradeSystemEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 6ca131543b4..8fd46982dc1 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -50,6 +50,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 08536f44245..fb25976ebd8 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -125,6 +125,7 @@ export const MysteriousChallengersEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 2b44f6ee33d..1eb1c4cb13e 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -61,6 +61,7 @@ export const MysteriousChestEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 2f41aa96677..17a3a366569 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -69,6 +69,7 @@ export const PartTimerEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index d029460e617..c6b04b7aca6 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -54,6 +54,7 @@ export const SafariZoneEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 89cb572962c..c70048ade07 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -62,6 +62,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index d0b4cc13301..3a4bf465a78 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -88,6 +88,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts index 63ab178c52a..01e241f63d4 100644 --- a/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts +++ b/src/data/mystery-encounters/encounters/teleporting-hijinks-encounter.ts @@ -58,6 +58,7 @@ export const TeleportingHijinksEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 aca46f1598b..4515736b30a 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 @@ -196,6 +196,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 2720653e654..95f359547e4 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -53,6 +53,7 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = speaker: `${namespace}:speaker`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 d1d5b484129..7ee57d36027 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -117,6 +117,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 ad4f2dd8498..c7cb23fe6f8 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -94,6 +94,7 @@ export const TheWinstrateChallengeEncounter: MysteryEncounter = return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index ff993f339cb..10bb956636b 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -52,6 +52,7 @@ export const TrainingSessionEncounter: MysteryEncounter = text: `${namespace}:intro`, } ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) 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 be2cd796386..c2a0426bceb 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -54,6 +54,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = text: `${namespace}:intro`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 51c1d5f963f..13594f273d9 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -125,6 +125,7 @@ export const UncommonBreedEncounter: MysteryEncounter = scene.time.delayedCall(500, () => scene.playSound("battle_anims/PRSFX- Spotlight2")); return true; }) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 17f33c27645..6e2f8352480 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -125,6 +125,7 @@ export const WeirdDreamEncounter: MysteryEncounter = text: `${namespace}:intro_dialogue`, }, ]) + .setLocalizationKey(`${namespace}`) .withTitle(`${namespace}:title`) .withDescription(`${namespace}:description`) .withQuery(`${namespace}:query`) diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index aabb0a3311a..7e175957e21 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -190,7 +190,7 @@ export default class MysteryEncounter implements IMysteryEncounter { secondaryPokemon?: PlayerPokemon[]; // #region Post-construct / Auto-populated params - + localizationKey: string; /** * Dialogue object containing all the dialogue, messages, tooltips, etc. for an encounter */ @@ -264,6 +264,7 @@ export default class MysteryEncounter implements IMysteryEncounter { Object.assign(this, encounter); } this.encounterTier = this.encounterTier ?? MysteryEncounterTier.COMMON; + this.localizationKey = this.localizationKey ?? ""; this.dialogue = this.dialogue ?? {}; this.spriteConfigs = this.spriteConfigs ? [ ...this.spriteConfigs ] : []; // Default max is 1 for ROGUE encounters, 2 for others @@ -528,6 +529,7 @@ export class MysteryEncounterBuilder implements Partial { options: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]]; enemyPartyConfigs: EnemyPartyConfig[] = []; + localizationKey: string = ""; dialogue: MysteryEncounterDialogue = {}; requirements: EncounterSceneRequirement[] = []; primaryPokemonRequirements: EncounterPokemonRequirement[] = []; @@ -632,6 +634,16 @@ export class MysteryEncounterBuilder implements Partial { return this.withIntroSpriteConfigs(spriteConfigs).withIntroDialogue(dialogue); } + /** + * Sets the localization key used by the encounter + * @param localizationKey the string used as the key + * @returns `this` + */ + setLocalizationKey(localizationKey: string): this { + this.localizationKey = localizationKey; + return this; + } + /** * OPTIONAL */ diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index f4de9b21963..20de7fd832c 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -12,6 +12,7 @@ import { BattleType } from "../battle"; import { RunEntry } from "../system/game-data"; import { PlayerGender } from "#enums/player-gender"; import { TrainerVariant } from "../field/trainer"; +import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; export type RunSelectCallback = (cursor: number) => void; @@ -104,7 +105,7 @@ export default class RunHistoryUiHandler extends MessageUiHandler { if (button === Button.ACTION) { const cursor = this.cursor + this.scrollCursor; if (this.runs[cursor]) { - this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, true); + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.runs[cursor].entryData, RunDisplayMode.RUN_HISTORY, true); } else { return false; } diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index d5f04f90e5b..39927f8e071 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -5,6 +5,7 @@ import { SessionSaveData } from "../system/game-data"; import { TextStyle, addTextObject, addBBCodeTextObject, getTextColor } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import { getPokeballAtlasKey } from "#app/data/pokeball"; import * as Utils from "../utils"; import PokemonData from "../system/pokemon-data"; import i18next from "i18next"; @@ -22,6 +23,8 @@ import * as Modifier from "../modifier/modifier"; import { Species } from "#enums/species"; import { PlayerGender } from "#enums/player-gender"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; +import { getBiomeName } from "#app/data/balance/biomes"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; /** * RunInfoUiMode indicates possible overlays of RunInfoUiHandler. @@ -34,6 +37,11 @@ enum RunInfoUiMode { ENDING_ART } +export enum RunDisplayMode { + RUN_HISTORY, + SESSION_PREVIEW +} + /** * Some variables are protected because this UI class will most likely be extended in the future to display more information. * These variables will most likely be shared across 'classes' aka pages. @@ -41,6 +49,7 @@ enum RunInfoUiMode { * For now, I leave as is. */ export default class RunInfoUiHandler extends UiHandler { + protected runDisplayMode: RunDisplayMode; protected runInfo: SessionSaveData; protected isVictory: boolean; protected pageMode: RunInfoUiMode; @@ -66,6 +75,7 @@ export default class RunInfoUiHandler extends UiHandler { // The import of the modifiersModule is loaded here to sidestep async/await issues. this.modifiersModule = Modifier; this.runContainer.setVisible(false); + this.scene.loadImage("encounter_exclaim", "mystery-encounters"); } /** @@ -87,9 +97,15 @@ export default class RunInfoUiHandler extends UiHandler { this.runContainer.add(gameStatsBg); const run = args[0]; + this.runDisplayMode = args[1]; + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); + this.isVictory = run.isVictory ?? false; + } else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) { + this.runInfo = args[0]; + } // Assigning information necessary for the UI's creation - this.runInfo = this.scene.gameData.parseSessionData(JSON.stringify(run.entry)); - this.isVictory = run.isVictory; + this.pageMode = RunInfoUiMode.MAIN; // Creates Header and adds to this.runContainer @@ -102,7 +118,11 @@ export default class RunInfoUiHandler extends UiHandler { const runResultWindow = addWindow(this.scene, 0, 0, this.statsBgWidth - 11, 65); runResultWindow.setOrigin(0, 0); this.runResultContainer.add(runResultWindow); - this.parseRunResult(); + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + this.parseRunResult(); + } else if (this.runDisplayMode === RunDisplayMode.SESSION_PREVIEW) { + this.parseRunStatus(); + } // Creates Run Info Container this.runInfoContainer = this.scene.add.container(0, 89); @@ -226,6 +246,66 @@ export default class RunInfoUiHandler extends UiHandler { this.runContainer.add(this.runResultContainer); } + /** + * This function is used when the Run Info UI is used to preview a Session. + * It edits {@linkcode runResultContainer}, but most importantly - does not display the negative results of a Mystery Encounter or any details of a trainer's party. + * Trainer Parties are replaced with their sprites, names, and their party size. + * Mystery Encounters contain sprites associated with MEs + the title of the specific ME. + */ + private parseRunStatus() { + const runStatusText = addTextObject(this.scene, 6, 5, `${i18next.t("saveSlotSelectUiHandler:wave")} ${this.runInfo.waveIndex} - ${getBiomeName(this.runInfo.arena.biome)}`, TextStyle.WINDOW, { fontSize : "65px", lineSpacing: 0.1 }); + + const enemyContainer = this.scene.add.container(0, 0); + this.runResultContainer.add(enemyContainer); + if (this.runInfo.battleType === BattleType.WILD) { + if (this.runInfo.enemyParty.length === 1) { + this.parseWildSingleDefeat(enemyContainer); + } else if (this.runInfo.enemyParty.length === 2) { + this.parseWildDoubleDefeat(enemyContainer); + } + } else if (this.runInfo.battleType === BattleType.TRAINER) { + this.showTrainerSprites(enemyContainer); + const row_limit = 3; + this.runInfo.enemyParty.forEach((p, i) => { + const pokeball = this.scene.add.sprite(0, 0, "pb"); + pokeball.setFrame(getPokeballAtlasKey(p.pokeball)); + pokeball.setScale(0.5); + pokeball.setPosition(52 + ((i % row_limit) * 8), (i <= 2) ? 18 : 25); + enemyContainer.add(pokeball); + }); + const trainerObj = this.runInfo.trainer.toTrainer(this.scene); + const RIVAL_TRAINER_ID_THRESHOLD = 375; + let trainerName = ""; + if (this.runInfo.trainer.trainerType >= RIVAL_TRAINER_ID_THRESHOLD) { + trainerName = (trainerObj.variant === TrainerVariant.FEMALE) ? i18next.t("trainerNames:rival_female") : i18next.t("trainerNames:rival"); + } else { + trainerName = trainerObj.getName(0, true); + } + const boxString = i18next.t(trainerObj.variant !== TrainerVariant.DOUBLE ? "battle:trainerAppeared" : "battle:trainerAppearedDouble", { trainerName: trainerName }).replace(/\n/g, " "); + const descContainer = this.scene.add.container(0, 0); + const textBox = addTextObject(this.scene, 0, 0, boxString, TextStyle.WINDOW, { fontSize : "35px", wordWrap: { width: 200 }}); + descContainer.add(textBox); + descContainer.setPosition(52, 29); + this.runResultContainer.add(descContainer); + } else if (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER) { + const encounterExclaim = this.scene.add.sprite(0, 0, "encounter_exclaim"); + encounterExclaim.setPosition(34, 26); + encounterExclaim.setScale(0.65); + const subSprite = this.scene.add.sprite(56, -106, "pkmn__sub"); + subSprite.setScale(0.65); + subSprite.setPosition(34, 46); + const mysteryEncounterTitle = i18next.t(this.scene.getMysteryEncounter(this.runInfo.mysteryEncounterType as MysteryEncounterType, true).localizationKey + ":title"); + const descContainer = this.scene.add.container(0, 0); + const textBox = addTextObject(this.scene, 0, 0, mysteryEncounterTitle, TextStyle.WINDOW, { fontSize : "45px", wordWrap: { width: 160 }}); + descContainer.add(textBox); + descContainer.setPosition(47, 37); + this.runResultContainer.add([ encounterExclaim, subSprite, descContainer ]); + } + + this.runResultContainer.add(runStatusText); + this.runContainer.add(this.runResultContainer); + } + /** * This function is called to edit an enemyContainer to represent a loss from a defeat by a wild single Pokemon battle. * @param enemyContainer - container holding enemy visual and level information @@ -278,40 +358,58 @@ export default class RunInfoUiHandler extends UiHandler { } /** - * This edits a container to represent a loss from a defeat by a trainer battle. - * @param enemyContainer - container holding enemy visuals and level information - * The trainers are placed to the left of their party. - * Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...) - * - * Party Pokemon have their icons, terastalization status, and level shown. + * This loads the enemy sprites, positions, and scales them according to the current display mode of the RunInfo UI and then adds them to the container parameter. + * Used by {@linkcode parseRunStatus} and {@linkcode parseTrainerDefeat} + * @param enemyContainer a Phaser Container that should hold enemy sprites */ - private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { + private showTrainerSprites(enemyContainer: Phaser.GameObjects.Container) { // Creating the trainer sprite and adding it to enemyContainer const tObj = this.runInfo.trainer.toTrainer(this.scene); - // Loads trainer assets on demand, as they are not loaded by default in the scene tObj.config.loadAssets(this.scene, this.runInfo.trainer.variant).then(() => { const tObjSpriteKey = tObj.config.getSpriteKey(this.runInfo.trainer.variant === TrainerVariant.FEMALE, false); const tObjSprite = this.scene.add.sprite(0, 5, tObjSpriteKey); - if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE) { + if (this.runInfo.trainer.variant === TrainerVariant.DOUBLE && !tObj.config.doubleOnly) { const doubleContainer = this.scene.add.container(5, 8); tObjSprite.setPosition(-3, -3); const tObjPartnerSpriteKey = tObj.config.getSpriteKey(true, true); const tObjPartnerSprite = this.scene.add.sprite(5, -3, tObjPartnerSpriteKey); // Double Trainers have smaller sprites than Single Trainers - tObjPartnerSprite.setScale(0.20); - tObjSprite.setScale(0.20); - doubleContainer.add(tObjSprite); - doubleContainer.add(tObjPartnerSprite); - doubleContainer.setPosition(12, 38); + if (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) { + tObjPartnerSprite.setScale(0.20); + tObjSprite.setScale(0.20); + doubleContainer.add(tObjSprite); + doubleContainer.add(tObjPartnerSprite); + doubleContainer.setPosition(12, 38); + } else { + tObjSprite.setScale(0.55); + tObjSprite.setPosition(-9, -3); + tObjPartnerSprite.setScale(0.55); + doubleContainer.add([ tObjSprite, tObjPartnerSprite ]); + doubleContainer.setPosition(28, 40); + } enemyContainer.add(doubleContainer); } else { - tObjSprite.setScale(0.35, 0.35); - tObjSprite.setPosition(12, 28); + const scale = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? 0.35 : 0.65; + const position = (this.runDisplayMode === RunDisplayMode.RUN_HISTORY) ? [ 12, 28 ] : [ 32, 36 ]; + tObjSprite.setScale(scale, scale); + tObjSprite.setPosition(position[0], position[1]); enemyContainer.add(tObjSprite); } }); + } + /** + * This edits a container to represent a loss from a defeat by a trainer battle. + * The trainers are placed to the left of their party. + * Depending on the trainer icon, there may be overlap between the edges of the box or their party. (Capes...) + * + * Party Pokemon have their icons, terastalization status, and level shown. + * @param enemyContainer - container holding enemy visuals and level information + */ + private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { + // Loads and adds trainer sprites to the UI + this.showTrainerSprites(enemyContainer); // Determining which Terastallize Modifier belongs to which Pokemon // Creates a dictionary {PokemonId: TeraShardType} const teraPokemon = {}; diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 89b20322a68..bd1a7dd9ac4 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -10,6 +10,7 @@ import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; const sessionSlotCount = 5; @@ -33,7 +34,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { private scrollCursor: integer = 0; - private cursorObj: Phaser.GameObjects.NineSlice | null; + private cursorObj: Phaser.GameObjects.Container | null; private sessionSlotsContainerInitialY: number; @@ -83,9 +84,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.saveSlotSelectCallback = args[1] as SaveSlotSelectCallback; this.saveSlotSelectContainer.setVisible(true); - this.populateSessionSlots(); - this.setScrollCursor(0); - this.setCursor(0); + this.populateSessionSlots() + .then(() => { + this.setScrollCursor(0); + this.setCursor(0); + }); return true; } @@ -147,21 +150,28 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { success = true; } } else { + const cursorPosition = this.cursor + this.scrollCursor; switch (button) { case Button.UP: if (this.cursor) { - success = this.setCursor(this.cursor - 1); + // Check to prevent cursor from accessing a negative index + success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); + success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); } break; case Button.DOWN: if (this.cursor < 2) { - success = this.setCursor(this.cursor + 1); + success = this.setCursor(this.cursor + 1, this.cursor); } else if (this.scrollCursor < sessionSlotCount - 3) { - success = this.setScrollCursor(this.scrollCursor + 1); + success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); } break; + case Button.RIGHT: + if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); + success = true; + } } } @@ -174,10 +184,10 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { return success || error; } - populateSessionSlots() { + async populateSessionSlots() { for (let s = 0; s < sessionSlotCount; s++) { const sessionSlot = new SessionSlot(this.scene, s); - sessionSlot.load(); + await sessionSlot.load(); this.scene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); @@ -198,25 +208,74 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.saveSlotSelectMessageBoxContainer.setVisible(!!text?.length); } - setCursor(cursor: integer): boolean { + /** + * setCursor takes user navigation as an input and positions the cursor accordingly + * @param cursor the index provided to the cursor + * @param prevCursor the previous index occupied by the cursor - optional + * @returns `true` if the cursor position has changed | `false` if it has not + */ + override setCursor(cursor: integer, prevCursor?: integer): boolean { const changed = super.setCursor(cursor); if (!this.cursorObj) { - this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); - this.cursorObj.setOrigin(0, 0); + this.cursorObj = this.scene.add.container(0, 0); + const cursorBox = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", undefined, 296, 44, 6, 6, 6, 6); + const rightArrow = this.scene.add.image(0, 0, "cursor"); + rightArrow.setPosition(160, 0); + rightArrow.setName("rightArrow"); + this.cursorObj.add([ cursorBox, rightArrow ]); this.sessionSlotsContainer.add(this.cursorObj); } - this.cursorObj.setPosition(4, 4 + (cursor + this.scrollCursor) * 56); + const cursorPosition = cursor + this.scrollCursor; + const cursorIncrement = cursorPosition * 56; + if (this.sessionSlots[cursorPosition] && this.cursorObj) { + const hasData = this.sessionSlots[cursorPosition].hasData; + // If the session slot lacks session data, it does not move from its default, central position. + // Only session slots with session data will move leftwards and have a visible arrow. + if (!hasData) { + this.cursorObj.setPosition(151, 26 + cursorIncrement); + this.sessionSlots[cursorPosition].setPosition(0, cursorIncrement); + } else { + this.cursorObj.setPosition(145, 26 + cursorIncrement); + this.sessionSlots[cursorPosition].setPosition(-6, cursorIncrement); + } + this.setArrowVisibility(hasData); + } + if (!Utils.isNullOrUndefined(prevCursor)) { + this.revertSessionSlot(prevCursor); + } return changed; } - setScrollCursor(scrollCursor: integer): boolean { + /** + * Helper function that resets the session slot position to its default central position + * @param prevCursor the previous location of the cursor + */ + revertSessionSlot(prevCursor: integer): void { + const sessionSlot = this.sessionSlots[prevCursor]; + if (sessionSlot) { + sessionSlot.setPosition(0, prevCursor * 56); + } + } + + /** + * Helper function that checks if the session slot involved holds data or not + * @param hasData `true` if session slot contains data | 'false' if not + */ + setArrowVisibility(hasData: boolean): void { + if (this.cursorObj) { + const rightArrow = this.cursorObj?.getByName("rightArrow") as Phaser.GameObjects.Image; + rightArrow.setVisible(hasData); + } + } + + setScrollCursor(scrollCursor: integer, priorCursor?: integer): boolean { const changed = scrollCursor !== this.scrollCursor; if (changed) { this.scrollCursor = scrollCursor; - this.setCursor(this.cursor); + this.setCursor(this.cursor, priorCursor); this.scene.tweens.add({ targets: this.sessionSlotsContainer, y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, @@ -254,6 +313,8 @@ class SessionSlot extends Phaser.GameObjects.Container { public hasData: boolean; private loadingLabel: Phaser.GameObjects.Text; + public saveData: SessionSaveData; + constructor(scene: BattleScene, slotId: integer) { super(scene, 0, slotId * 56); @@ -337,6 +398,7 @@ class SessionSlot extends Phaser.GameObjects.Container { return; } this.hasData = true; + this.saveData = sessionData; await this.setupWithData(sessionData); resolve(true); }); From f180b6070e8f61ca06fc50a12665bab961070653 Mon Sep 17 00:00:00 2001 From: flx-sta <50131232+flx-sta@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:01:49 -0700 Subject: [PATCH 16/75] [Qol] Load i18n en locales during tests (#4553) * add: i18n backend support the backend is being supported by using msw which will import the correct file from the local locales folder * fix: tests to no longer rely on static i18n keys * Update src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/test/ui/type-hints.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> * Fix typos Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Fix linting * update locales submodule update reference to `56eeb809eb5a2de40cfc5bc6128a78bef14deea9` (from `3ccef8472dd7cc7c362538489954cb8fdad27e5f`) --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- global.d.ts | 14 +++ src/test/abilities/ability_timing.test.ts | 10 +- src/test/items/toxic_orb.test.ts | 15 ++- .../a-trainers-test-encounter.test.ts | 4 +- .../absolute-avarice-encounter.test.ts | 3 +- ...an-offer-you-cant-refuse-encounter.test.ts | 5 +- .../encounters/field-trip-encounter.test.ts | 32 +++---- .../fiery-fallout-encounter.test.ts | 3 +- .../encounters/lost-at-sea-encounter.test.ts | 5 +- .../teleporting-hijinks-encounter.test.ts | 33 +++---- .../phases/mystery-encounter-phase.test.ts | 7 +- src/test/system/game_data.test.ts | 5 +- src/test/ui/starter-select.test.ts | 91 ++++++++++--------- src/test/ui/type-hints.test.ts | 7 +- src/test/vitest.setup.ts | 58 +++++++----- 15 files changed, 166 insertions(+), 126 deletions(-) create mode 100644 global.d.ts diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 00000000000..f4dfa7d4cb2 --- /dev/null +++ b/global.d.ts @@ -0,0 +1,14 @@ +import type { SetupServerApi } from "msw/node"; + +export {}; + +declare global { + /** + * Only used in testing. + * Can technically be undefined/null but for ease of use we are going to assume it is always defined. + * Used to load i18n files exclusively. + * + * To set up your own server in a test see `game_data.test.ts` + */ + var i18nServer: SetupServerApi; +} diff --git a/src/test/abilities/ability_timing.test.ts b/src/test/abilities/ability_timing.test.ts index 1472f9eb429..e3264c2c1a8 100644 --- a/src/test/abilities/ability_timing.test.ts +++ b/src/test/abilities/ability_timing.test.ts @@ -1,13 +1,13 @@ import { BattleStyle } from "#app/enums/battle-style"; import { CommandPhase } from "#app/phases/command-phase"; import { TurnInitPhase } from "#app/phases/turn-init-phase"; -import i18next, { initI18n } from "#app/plugins/i18n"; +import i18next from "#app/plugins/i18n"; import { Mode } from "#app/ui/ui"; import { Abilities } from "#enums/abilities"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Ability Timing", () => { @@ -32,11 +32,10 @@ describe("Ability Timing", () => { .enemySpecies(Species.MAGIKARP) .enemyAbility(Abilities.INTIMIDATE) .ability(Abilities.BALL_FETCH); + vi.spyOn(i18next, "t"); }); it("should trigger after switch check", async () => { - initI18n(); - i18next.changeLanguage("en"); game.settings.battleStyle = BattleStyle.SWITCH; await game.classicMode.runToSummon([ Species.EEVEE, Species.FEEBAS ]); @@ -46,7 +45,6 @@ describe("Ability Timing", () => { }, () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)); await game.phaseInterceptor.to("MessagePhase"); - const message = game.textInterceptor.getLatestMessage(); - expect(message).toContain("battle:statFell"); + expect(i18next.t).toHaveBeenCalledWith("battle:statFell", expect.objectContaining({ count: 1 })); }, 5000); }); diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index 35d6e77b209..a83fd3655e5 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -2,13 +2,13 @@ import { StatusEffect } from "#app/data/status-effect"; import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; import { MessagePhase } from "#app/phases/message-phase"; import { TurnEndPhase } from "#app/phases/turn-end-phase"; -import i18next, { initI18n } from "#app/plugins/i18n"; +import i18next from "#app/plugins/i18n"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; -import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Items - Toxic orb", () => { @@ -39,11 +39,11 @@ describe("Items - Toxic orb", () => { game.override.startingHeldItems([{ name: "TOXIC_ORB", }]); + + vi.spyOn(i18next, "t"); }); it("TOXIC ORB", async () => { - initI18n(); - i18next.changeLanguage("en"); const moveToUse = Moves.GROWTH; await game.startBattle([ Species.MIGHTYENA, @@ -57,11 +57,10 @@ describe("Items - Toxic orb", () => { await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); // Toxic orb should trigger here await game.phaseInterceptor.run(MessagePhase); - const message = game.textInterceptor.getLatestMessage(); - expect(message).toContain("statusEffect:toxic.obtainSource"); + expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything()); + await game.phaseInterceptor.run(MessagePhase); - const message2 = game.textInterceptor.getLatestMessage(); - expect(message2).toBe("statusEffect:toxic.activation"); + expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.activation", expect.anything()); expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC); }, 20000); }); diff --git a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index f24800eaa71..b1aa378d82a 100644 --- a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -16,6 +16,7 @@ import { EggTier } from "#enums/egg-type"; import { CommandPhase } from "#app/phases/command-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import { PartyHealPhase } from "#app/phases/party-heal-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/aTrainersTest"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -106,7 +107,8 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); expect(enemyField.length).toBe(1); expect(scene.currentBattle.trainer).toBeDefined(); - expect([ "trainerNames:buck", "trainerNames:cheryl", "trainerNames:marley", "trainerNames:mira", "trainerNames:riley" ].includes(scene.currentBattle.trainer!.config.name)).toBeTruthy(); + expect([ i18next.t("trainerNames:buck"), i18next.t("trainerNames:cheryl"), i18next.t("trainerNames:marley"), i18next.t("trainerNames:mira"), i18next.t("trainerNames:riley") ] + .map(name => name.toLowerCase()).includes(scene.currentBattle.trainer!.config.name)).toBeTruthy(); expect(enemyField[0]).toBeDefined(); }); diff --git a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts index 99a835cb6ae..a72a9fbb5a3 100644 --- a/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/absolute-avarice-encounter.test.ts @@ -16,6 +16,7 @@ import { Moves } from "#enums/moves"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/absoluteAvarice"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -146,7 +147,7 @@ describe("Absolute Avarice - Mystery Encounter", () => { const pokemonId = partyPokemon.id; const pokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === pokemonId, true) as PokemonHeldItemModifier[]; - const revSeed = pokemonItems.find(i => i.type.name === "modifierType:ModifierType.REVIVER_SEED.name"); + const revSeed = pokemonItems.find(i => i.type.name === i18next.t("modifierType:ModifierType.REVIVER_SEED.name")); expect(revSeed).toBeDefined; expect(revSeed?.stackCount).toBe(1); } diff --git a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 77d5a842b47..9883b4332b9 100644 --- a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -17,6 +17,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/anOfferYouCantRefuse"; /** Gyarados for Indimidate */ @@ -93,8 +94,8 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.strongestPokemon).toBeDefined(); expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.price).toBeDefined(); - expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe("ability:intimidate.name"); - expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe("ability:intimidate.name"); + expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.option2PrimaryAbility).toBe(i18next.t("ability:intimidate.name")); + expect(AnOfferYouCantRefuseEncounter.dialogueTokens?.moveOrAbility).toBe(i18next.t("ability:intimidate.name")); expect(AnOfferYouCantRefuseEncounter.misc.pokemon instanceof PlayerPokemon).toBeTruthy(); expect(AnOfferYouCantRefuseEncounter.misc?.price?.toString()).toBe(AnOfferYouCantRefuseEncounter.dialogueTokens?.price); expect(onInitResult).toBe(true); diff --git a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts index 232bad3c2b8..a6f925274c3 100644 --- a/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/field-trip-encounter.test.ts @@ -103,11 +103,11 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_attack"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_defense"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_attack")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_defense")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { @@ -150,11 +150,11 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_atk"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_sp_def"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.DIRE_HIT.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_atk")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_sp_def")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.DIRE_HIT.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { @@ -198,12 +198,12 @@ describe("Field Trip - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; expect(modifierSelectHandler.options.length).toEqual(5); - expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_accuracy"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe("modifierType:TempStatStageBoosterItem.x_speed"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe("modifierType:ModifierType.AddPokeballModifierType.name"); - expect(i18next.t).toHaveBeenCalledWith("modifierType:ModifierType.AddPokeballModifierType.name", expect.objectContaining({ modifierCount: 5 })); - expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe("modifierType:ModifierType.IV_SCANNER.name"); - expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe("modifierType:ModifierType.RARER_CANDY.name"); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_accuracy")); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.name).toBe(i18next.t("modifierType:TempStatStageBoosterItem.x_speed")); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.AddPokeballModifierType.name", { modifierCount: 5, pokeballName: i18next.t("pokeball:greatBall") })); + expect(i18next.t).toHaveBeenCalledWith(("modifierType:ModifierType.AddPokeballModifierType.name"), expect.objectContaining({ modifierCount: 5 })); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.IV_SCANNER.name")); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.name).toBe(i18next.t("modifierType:ModifierType.RARER_CANDY.name")); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts index 3d2533c0817..a4f303d121f 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -22,6 +22,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/fieryFallout"; /** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ @@ -205,7 +206,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { const burnablePokemon = party.filter((pkm) => pkm.isAllowedInBattle() && !pkm.getTypes().includes(Type.FIRE)); const notBurnablePokemon = party.filter((pkm) => !pkm.isAllowedInBattle() || pkm.getTypes().includes(Type.FIRE)); - expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe("pokemon:gengar"); + expect(scene.currentBattle.mysteryEncounter?.dialogueTokens["burnedPokemon"]).toBe(i18next.t("pokemon:gengar")); burnablePokemon.forEach((pkm) => { expect(pkm.hp, `${pkm.name} should have received 20% damage: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp() - Math.floor(pkm.getMaxHp() * 0.2)); }); diff --git a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts index 456c18c572d..dec14d46cc8 100644 --- a/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/lost-at-sea-encounter.test.ts @@ -14,6 +14,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import BattleScene from "#app/battle-scene"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import i18next from "i18next"; const namespace = "mysteryEncounters/lostAtSea"; @@ -86,8 +87,8 @@ describe("Lost at Sea - Mystery Encounter", () => { const onInitResult = onInit!(scene); expect(LostAtSeaEncounter.dialogueTokens?.damagePercentage).toBe("25"); - expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe("move:surf.name"); - expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe("move:fly.name"); + expect(LostAtSeaEncounter.dialogueTokens?.option1RequiredMove).toBe(i18next.t("move:surf.name")); + expect(LostAtSeaEncounter.dialogueTokens?.option2RequiredMove).toBe(i18next.t("move:fly.name")); expect(onInitResult).toBe(true); }); diff --git a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts index 2411752baa7..02375d83b98 100644 --- a/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/teleporting-hijinks-encounter.test.ts @@ -1,21 +1,22 @@ -import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; -import { Biome } from "#app/enums/biome"; -import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; -import { Species } from "#app/enums/species"; -import GameManager from "#app/test/utils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import BattleScene from "#app/battle-scene"; +import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter"; +import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; +import { Abilities } from "#enums/abilities"; +import { Biome } from "#enums/biome"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { Species } from "#enums/species"; +import { CommandPhase } from "#app/phases/command-phase"; +import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; +import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import GameManager from "#test/utils/gameManager"; +import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import { Mode } from "#app/ui/ui"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; -import { CommandPhase } from "#app/phases/command-phase"; -import { TeleportingHijinksEncounter } from "#app/data/mystery-encounters/encounters/teleporting-hijinks-encounter"; -import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; -import { Mode } from "#app/ui/ui"; -import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import { Abilities } from "#app/enums/abilities"; +import i18next from "i18next"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const namespace = "mysteryEncounters/teleportingHijinks"; const defaultParty = [ Species.LAPRAS, Species.GENGAR, Species.ABRA ]; @@ -300,8 +301,8 @@ describe("Teleporting Hijinks - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.metal_coat")).toBe(true); - expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === "modifierType:AttackTypeBoosterItem.magnet")).toBe(true); + expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.metal_coat"))).toBe(true); + expect(modifierSelectHandler.options.some(opt => opt.modifierTypeOption.type.name === i18next.t("modifierType:AttackTypeBoosterItem.magnet"))).toBe(true); }); }); }); diff --git a/src/test/phases/mystery-encounter-phase.test.ts b/src/test/phases/mystery-encounter-phase.test.ts index 4468045756b..32e31ce1c94 100644 --- a/src/test/phases/mystery-encounter-phase.test.ts +++ b/src/test/phases/mystery-encounter-phase.test.ts @@ -9,6 +9,7 @@ import MysteryEncounterUiHandler from "#app/ui/mystery-encounter-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import MessageUiHandler from "#app/ui/message-ui-handler"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import i18next from "i18next"; describe("Mystery Encounter Phases", () => { let phaserGame: Phaser.Game; @@ -78,9 +79,9 @@ describe("Mystery Encounter Phases", () => { expect(ui.getMode()).toBe(Mode.MESSAGE); expect(ui.showDialogue).toHaveBeenCalledTimes(1); expect(ui.showText).toHaveBeenCalledTimes(2); - expect(ui.showDialogue).toHaveBeenCalledWith("battle:mysteryEncounterAppeared", "???", null, expect.any(Function)); - expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:intro", null, expect.any(Function), 750, true); - expect(ui.showText).toHaveBeenCalledWith("mysteryEncounters/mysteriousChallengers:option.selected", null, expect.any(Function), 300, true); + expect(ui.showDialogue).toHaveBeenCalledWith(i18next.t("battle:mysteryEncounterAppeared"), "???", null, expect.any(Function)); + expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:intro"), null, expect.any(Function), 750, true); + expect(ui.showText).toHaveBeenCalledWith(i18next.t("mysteryEncounters/mysteriousChallengers:option.selected"), null, expect.any(Function), 300, true); }); }); diff --git a/src/test/system/game_data.test.ts b/src/test/system/game_data.test.ts index 5eb4dea3910..fcb7e9067a3 100644 --- a/src/test/system/game_data.test.ts +++ b/src/test/system/game_data.test.ts @@ -11,13 +11,15 @@ import * as account from "../../account"; const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001"; -export const server = setupServer(); +/** We need a custom server. For some reasons I can't extend the listeners of {@linkcode global.i18nServer} with {@linkcode global.i18nServer.use} */ +const server = setupServer(); describe("System - Game Data", () => { let phaserGame: Phaser.Game; let game: GameManager; beforeAll(() => { + global.i18nServer.close(); server.listen(); phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -26,6 +28,7 @@ describe("System - Game Data", () => { afterAll(() => { server.close(); + global.i18nServer.listen(); }); beforeEach(() => { diff --git a/src/test/ui/starter-select.test.ts b/src/test/ui/starter-select.test.ts index dd0761be392..94370ca1b74 100644 --- a/src/test/ui/starter-select.test.ts +++ b/src/test/ui/starter-select.test.ts @@ -14,6 +14,7 @@ import { Abilities } from "#enums/abilities"; import { Button } from "#enums/buttons"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; +import i18next from "i18next"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -66,11 +67,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -127,11 +128,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -191,11 +192,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -254,11 +255,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -315,11 +316,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -376,11 +377,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -436,11 +437,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); await new Promise((resolve) => { @@ -496,11 +497,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); let starterSelectUiHandler: StarterSelectUiHandler; @@ -561,11 +562,11 @@ describe("UI - Starter select", () => { resolve(); }); }); - expect(options.some(option => option.label === "starterSelectUiHandler:addToParty")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:toggleIVs")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:manageMoves")).toBe(true); - expect(options.some(option => option.label === "starterSelectUiHandler:useCandies")).toBe(true); - expect(options.some(option => option.label === "menu:cancel")).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:addToParty"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:toggleIVs"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:manageMoves"))).toBe(true); + expect(options.some(option => option.label === i18next.t("starterSelectUiHandler:useCandies"))).toBe(true); + expect(options.some(option => option.label === i18next.t("menu:cancel"))).toBe(true); optionSelectUiHandler?.processInput(Button.ACTION); let starterSelectUiHandler: StarterSelectUiHandler | undefined; diff --git a/src/test/ui/type-hints.test.ts b/src/test/ui/type-hints.test.ts index 450f43f1263..2977262dda7 100644 --- a/src/test/ui/type-hints.test.ts +++ b/src/test/ui/type-hints.test.ts @@ -7,7 +7,8 @@ import { Mode } from "#app/ui/ui"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -import MockText from "../utils/mocks/mocksContainer/mockText"; +import MockText from "#test/utils/mocks/mocksContainer/mockText"; +import i18next from "i18next"; describe("UI - Type Hints", () => { let phaserGame: Phaser.Game; @@ -53,7 +54,7 @@ describe("UI - Type Hints", () => { const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); const dragonClawText = movesContainer .getAll() - .find((text) => text.text === "move:dragonClaw.name")! as unknown as MockText; + .find((text) => text.text === i18next.t("move:dragonClaw.name"))! as unknown as MockText; expect.soft(dragonClawText.color).toBe("#929292"); ui.getHandler().processInput(Button.ACTION); @@ -78,7 +79,7 @@ describe("UI - Type Hints", () => { const movesContainer = ui.getByName(FightUiHandler.MOVES_CONTAINER_NAME); const growlText = movesContainer .getAll() - .find((text) => text.text === "move:growl.name")! as unknown as MockText; + .find((text) => text.text === i18next.t("move:growl.name"))! as unknown as MockText; expect.soft(growlText.color).toBe(undefined); ui.getHandler().processInput(Button.ACTION); diff --git a/src/test/vitest.setup.ts b/src/test/vitest.setup.ts index 0d67d6787c4..8438f607db2 100644 --- a/src/test/vitest.setup.ts +++ b/src/test/vitest.setup.ts @@ -4,16 +4,17 @@ import { initLoggedInUser } from "#app/account"; import { initAbilities } from "#app/data/ability"; import { initBiomes } from "#app/data/balance/biomes"; import { initEggMoves } from "#app/data/balance/egg-moves"; +import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { initMoves } from "#app/data/move"; import { initMysteryEncounters } from "#app/data/mystery-encounters/mystery-encounters"; -import { initPokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { initPokemonForms } from "#app/data/pokemon-forms"; import { initSpecies } from "#app/data/pokemon-species"; import { initAchievements } from "#app/system/achv"; import { initVouchers } from "#app/system/voucher"; import { initStatsKeys } from "#app/ui/game-stats-ui-handler"; -import { beforeAll, vi } from "vitest"; +import { afterAll, beforeAll, vi } from "vitest"; +/** Set the timezone to UTC for tests. */ process.env.TZ = "UTC"; /** Mock the override import to always return default values, ignoring any custom overrides. */ @@ -26,26 +27,36 @@ vi.mock("#app/overrides", async (importOriginal) => { } satisfies typeof import("#app/overrides"); }); -vi.mock("i18next", () => ({ - default: { - use: () => {}, - t: (key: string) => key, - changeLanguage: () => Promise.resolve(), - init: () => Promise.resolve(), - resolvedLanguage: "en", - exists: () => true, - getDataByLanguage:() => ({ - en: { - keys: [ "foo" ] - }, - }), - services: { - formatter: { - add: () => {}, +/** + * This is a hacky way to mock the i18n backend requests (with the help of {@link https://mswjs.io/ | msw}). + * The reason to put it inside of a mock is to elevate it. + * This is necessary because how our code is structured. + * Do NOT try to put any of this code into external functions, it won't work as it's elevated during runtime. + */ +vi.mock("i18next", async (importOriginal) => { + console.log("Mocking i18next"); + const { setupServer } = await import("msw/node"); + const { http, HttpResponse } = await import("msw"); + + global.i18nServer = setupServer( + http.get("/locales/en/*", async (req) => { + const filename = req.params[0]; + + try { + const json = await import(`../../public/locales/en/${req.params[0]}`); + console.log("Loaded locale", filename); + return HttpResponse.json(json); + } catch (err) { + console.log(`Failed to load locale ${filename}!`, err); + return HttpResponse.json({}); } - }, - }, -})); + }) + ); + global.i18nServer.listen({ onUnhandledRequest: "error" }); + console.log("i18n MSW server listening!"); + + return await importOriginal(); +}); initVouchers(); initAchievements(); @@ -70,3 +81,8 @@ beforeAll(() => { }, }); }); + +afterAll(() => { + global.i18nServer.close(); + console.log("Closing i18n MSW server!"); +}); From ca3cc3c9c6a569572942516f72edd8610c76b6c5 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:28:26 -0400 Subject: [PATCH 17/75] [P1 Bug] Fix infinite recursion from abilities disabled by Sheer Force (#4631) --- src/field/pokemon.ts | 4 ++-- src/test/abilities/sheer_force.test.ts | 27 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 241524df1b9..35f389b58a4 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1417,10 +1417,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns {boolean} Whether the ability is present and active */ hasAbility(ability: Abilities, canApply: boolean = true, ignoreOverride?: boolean): boolean { - if ((!canApply || this.canApplyAbility()) && this.getAbility(ignoreOverride).id === ability) { + if (this.getAbility(ignoreOverride).id === ability && (!canApply || this.canApplyAbility())) { return true; } - if (this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().id === ability) { + if (this.getPassiveAbility().id === ability && this.hasPassive() && (!canApply || this.canApplyAbility(true))) { return true; } return false; diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index a3add0a9964..a2600476d6d 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -9,6 +9,7 @@ import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { allMoves } from "#app/data/move"; describe("Abilities - Sheer Force", () => { @@ -174,5 +175,31 @@ describe("Abilities - Sheer Force", () => { }, 20000); + it("Two Pokemon with abilities disabled by Sheer Force hitting each other should not cause a crash", async () => { + const moveToUse = Moves.CRUNCH; + game.override.enemyAbility(Abilities.COLOR_CHANGE) + .ability(Abilities.COLOR_CHANGE) + .moveset(moveToUse) + .enemyMoveset(moveToUse); + + await game.classicMode.startBattle([ + Species.PIDGEOT + ]); + + const pidgeot = game.scene.getParty()[0]; + const onix = game.scene.getEnemyParty()[0]; + + pidgeot.stats[Stat.DEF] = 10000; + onix.stats[Stat.DEF] = 10000; + + game.move.select(moveToUse); + await game.toNextTurn(); + + // Check that both Pokemon's Color Change activated + const expectedTypes = [ allMoves[moveToUse].type ]; + expect(pidgeot.getTypes()).toStrictEqual(expectedTypes); + expect(onix.getTypes()).toStrictEqual(expectedTypes); + }); + //TODO King's Rock Interaction Unit Test }); From 52257def2fa65aa09018800b29e89864572fc8b0 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:30:19 -0700 Subject: [PATCH 18/75] [P3] Fix enemy used PP flyout, fixes #4622 (#4629) Also add missing function return types --- src/phases/move-phase.ts | 41 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 10cc062ea3b..94093188571 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -8,10 +8,6 @@ import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { Type } from "#app/data/type"; import { getTerrainBlockMessage } from "#app/data/weather"; -import { Abilities } from "#app/enums/abilities"; -import { BattlerTagType } from "#app/enums/battler-tag-type"; -import { Moves } from "#app/enums/moves"; -import { StatusEffect } from "#app/enums/status-effect"; import { MoveUsedEvent } from "#app/events/battle-scene"; import Pokemon, { MoveResult, PokemonMove, TurnMove } from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; @@ -20,7 +16,11 @@ import { CommonAnimPhase } from "#app/phases/common-anim-phase"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; -import * as Utils from "#app/utils"; +import { BooleanHolder, NumberHolder } from "#app/utils"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { Moves } from "#enums/moves"; +import { StatusEffect } from "#enums/status-effect"; import i18next from "i18next"; export class MovePhase extends BattlePhase { @@ -89,7 +89,7 @@ export class MovePhase extends BattlePhase { this.cancelled = true; } - public start() { + public start(): void { super.start(); console.log(Moves[this.move.moveId]); @@ -140,7 +140,7 @@ export class MovePhase extends BattlePhase { } /** Check for cancellation edge cases - no targets remaining, or {@linkcode Moves.NONE} is in the queue */ - protected resolveFinalPreMoveCancellationChecks() { + protected resolveFinalPreMoveCancellationChecks(): void { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); @@ -150,14 +150,14 @@ export class MovePhase extends BattlePhase { } } - public getActiveTargetPokemon() { + public getActiveTargetPokemon(): Pokemon[] { return this.scene.getField(true).filter(p => this.targets.includes(p.getBattlerIndex())); } /** * Handles {@link StatusEffect.SLEEP Sleep}/{@link StatusEffect.PARALYSIS Paralysis}/{@link StatusEffect.FREEZE Freeze} rolls and side effects. */ - protected resolvePreMoveStatusEffects() { + protected resolvePreMoveStatusEffects(): void { if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) { this.pokemon.status.incrementTurn(); let activated = false; @@ -198,7 +198,7 @@ export class MovePhase extends BattlePhase { * Lapse {@linkcode BattlerTagLapseType.PRE_MOVE PRE_MOVE} tags that trigger before a move is used, regardless of whether or not it failed. * Also lapse {@linkcode BattlerTagLapseType.MOVE MOVE} tags if the move should be successful. */ - protected lapsePreMoveAndMoveTags() { + protected lapsePreMoveAndMoveTags(): void { this.pokemon.lapseTags(BattlerTagLapseType.PRE_MOVE); // TODO: does this intentionally happen before the no targets/Moves.NONE on queue cancellation case is checked? @@ -207,7 +207,7 @@ export class MovePhase extends BattlePhase { } } - protected useMove() { + protected useMove(): void { const targets = this.getActiveTargetPokemon(); const moveQueue = this.pokemon.getMoveQueue(); @@ -217,7 +217,8 @@ export class MovePhase extends BattlePhase { this.showMoveText(); // TODO: Clean up implementation of two-turn moves. - if (moveQueue.length > 0) { // Using .shift here clears out two turn moves once they've been used + if (moveQueue.length > 0) { + // Using .shift here clears out two turn moves once they've been used this.ignorePp = moveQueue.shift()?.ignorePP ?? false; } @@ -226,7 +227,7 @@ export class MovePhase extends BattlePhase { const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); this.move.usePp(ppUsed); - this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); + this.scene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), this.move.ppUsed)); } // Update the battle's "last move" pointer, unless we're currently mimicking a move. @@ -275,7 +276,7 @@ export class MovePhase extends BattlePhase { this.pokemon.pushMoveHistory({ move: this.move.moveId, targets: this.targets, result: MoveResult.FAIL, virtual: this.move.virtual }); let failedText: string | undefined; - const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new Utils.BooleanHolder(false)); + const failureMessage = move.getFailedText(this.pokemon, targets[0], move, new BooleanHolder(false)); if (failureMessage) { failedText = failureMessage; @@ -299,7 +300,7 @@ export class MovePhase extends BattlePhase { * Queues a {@linkcode MoveEndPhase} if the move wasn't a {@linkcode followUp} and {@linkcode canMove()} returns `true`, * then ends the phase. */ - public end() { + public end(): void { if (!this.followUp && this.canMove()) { this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.getBattlerIndex())); } @@ -313,7 +314,7 @@ export class MovePhase extends BattlePhase { * * TODO: This hardcodes the PP increase at 1 per opponent, rather than deferring to the ability. */ - public getPpIncreaseFromPressure(targets: Pokemon[]) { + public getPpIncreaseFromPressure(targets: Pokemon[]): number { const foesWithPressure = this.pokemon.getOpponents().filter(o => targets.includes(o) && o.isActive(true) && o.hasAbilityWithAttr(IncreasePpAbAttr)); return foesWithPressure.length; } @@ -323,10 +324,10 @@ export class MovePhase extends BattlePhase { * - Move redirection abilities, effects, etc. * - Counterattacks, which pass a special value into the `targets` constructor param (`[`{@linkcode BattlerIndex.ATTACKER}`]`). */ - protected resolveRedirectTarget() { + protected resolveRedirectTarget(): void { if (this.targets.length === 1) { const currentTarget = this.targets[0]; - const redirectTarget = new Utils.NumberHolder(currentTarget); + const redirectTarget = new NumberHolder(currentTarget); // check move redirection abilities of every pokemon *except* the user. this.scene.getField(true).filter(p => p !== this.pokemon).forEach(p => applyAbAttrs(RedirectMoveAbAttr, p, null, false, this.move.moveId, redirectTarget)); @@ -372,7 +373,7 @@ export class MovePhase extends BattlePhase { * If there is no last attacker, or they are no longer on the field, a message is displayed and the * move is marked for failure. */ - protected resolveCounterAttackTarget() { + protected resolveCounterAttackTarget(): void { if (this.targets.length === 1 && this.targets[0] === BattlerIndex.ATTACKER) { if (this.pokemon.turnData.attacksReceived.length) { this.targets[0] = this.pokemon.turnData.attacksReceived[0].sourceBattlerIndex; @@ -411,7 +412,7 @@ export class MovePhase extends BattlePhase { * * TODO: handle charge moves more gracefully */ - protected handlePreMoveFailures() { + protected handlePreMoveFailures(): void { if (this.cancelled || this.failed) { if (this.failed) { const ppUsed = this.ignorePp ? 0 : 1; From e9906ea2293171aa8b32b81ee1d1a0a77254b3f2 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:31:10 -0700 Subject: [PATCH 19/75] [P2] Obstruct/Kings Shield/etc no longer reduce stats through Clear Body/etc (#4627) * bug fix * Add test --------- Co-authored-by: frutescens Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battler-tags.ts | 2 +- src/test/moves/obstruct.test.ts | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a54a8c5f519..6307b3d28be 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1376,7 +1376,7 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag { const effectPhase = pokemon.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { const attacker = effectPhase.getPokemon(); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), false, [ this.stat ], this.levels)); } } diff --git a/src/test/moves/obstruct.test.ts b/src/test/moves/obstruct.test.ts index fbb5437b43a..1649c199e32 100644 --- a/src/test/moves/obstruct.test.ts +++ b/src/test/moves/obstruct.test.ts @@ -1,6 +1,7 @@ -import { Moves } from "#app/enums/moves"; -import { Stat } from "#app/enums/stat"; import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -22,13 +23,15 @@ describe("Moves - Obstruct", () => { game = new GameManager(phaserGame); game.override .battleType("single") + .enemySpecies(Species.MAGIKARP) + .enemyMoveset(Moves.TACKLE) .enemyAbility(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH) - .moveset([ Moves.OBSTRUCT ]); + .moveset([ Moves.OBSTRUCT ]) + .starterSpecies(Species.FEEBAS); }); it("protects from contact damaging moves and lowers the opponent's defense by 2 stages", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH)); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -42,7 +45,6 @@ describe("Moves - Obstruct", () => { }); it("bypasses accuracy checks when applying protection and defense reduction", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.ICE_PUNCH)); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -59,7 +61,7 @@ describe("Moves - Obstruct", () => { ); it("protects from non-contact damaging moves and doesn't lower the opponent's defense by 2 stages", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.WATER_GUN)); + game.override.enemyMoveset(Moves.WATER_GUN); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -73,7 +75,7 @@ describe("Moves - Obstruct", () => { }); it("doesn't protect from status moves", async () => { - game.override.enemyMoveset(Array(4).fill(Moves.GROWL)); + game.override.enemyMoveset(Moves.GROWL); await game.classicMode.startBattle(); game.move.select(Moves.OBSTRUCT); @@ -83,4 +85,14 @@ describe("Moves - Obstruct", () => { expect(player.getStatStage(Stat.ATK)).toBe(-1); }); + + it("doesn't reduce the stats of an opponent with Clear Body/etc", async () => { + game.override.enemyAbility(Abilities.CLEAR_BODY); + await game.classicMode.startBattle(); + + game.move.select(Moves.OBSTRUCT); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(0); + }); }); From 51894d46c265116ee391146a10bedb16eccbc056 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:38:17 -0700 Subject: [PATCH 20/75] [P2] Pollen Puff ally behavior fixed (#4615) * pollen puff fix * bcvbvcbfd * integerholder to numberholder * moved it back --------- Co-authored-by: frutescens --- src/data/move.ts | 4 ++-- src/field/pokemon.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 08c00829b48..4924341870d 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4107,11 +4107,11 @@ export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr { * @param user {@linkcode Pokemon} using the move * @param target {@linkcode Pokemon} target of the move * @param move {@linkcode Move} with this attribute - * @param args [0] {@linkcode Utils.IntegerHolder} The category of the move + * @param args [0] {@linkcode Utils.NumberHolder} The category of the move * @returns true if the function succeeds */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const category = (args[0] as Utils.IntegerHolder); + const category = (args[0] as Utils.NumberHolder); if (user.getAlly() === target) { category.value = MoveCategory.STATUS; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 35f389b58a4..4d85d5b8e1e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -2684,7 +2684,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ apply(source: Pokemon, move: Move): HitResult { const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (move.category === MoveCategory.STATUS) { + const moveCategory = new Utils.NumberHolder(move.category); + applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, moveCategory); + if (moveCategory.value === MoveCategory.STATUS) { const cancelled = new Utils.BooleanHolder(false); const typeMultiplier = this.getMoveEffectiveness(source, move, false, false, cancelled); From 64147e44145faa8e8f14730279e764d146797841 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:40:14 -0400 Subject: [PATCH 21/75] [P2] Fix Battle Bond continuing to affect Water Shuriken after Greninja returns to base form (#4602) * [Bug] Fix Battle Bond continuing to buff Water Shuriken after Greninja returns to base form * Test cleanup * PR feedback * Update test to use getMultiHitType() * PR Feedback --- src/data/move.ts | 13 +++- src/test/abilities/battle_bond.test.ts | 94 +++++++++++++++++--------- 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index 4924341870d..bae8eea0d8a 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1938,12 +1938,21 @@ export class IncrementMovePriorityAttr extends MoveAttr { * @see {@linkcode apply} */ export class MultiHitAttr extends MoveAttr { + /** This move's intrinsic multi-hit type. It should never be modified. */ + private readonly intrinsicMultiHitType: MultiHitType; + /** This move's current multi-hit type. It may be temporarily modified by abilities (e.g., Battle Bond). */ private multiHitType: MultiHitType; constructor(multiHitType?: MultiHitType) { super(); - this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this.intrinsicMultiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5; + this.multiHitType = this.intrinsicMultiHitType; + } + + // Currently used by `battle_bond.test.ts` + getMultiHitType(): MultiHitType { + return this.multiHitType; } /** @@ -1957,7 +1966,7 @@ export class MultiHitAttr extends MoveAttr { * @returns True */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const hitType = new Utils.NumberHolder(this.multiHitType); + const hitType = new Utils.NumberHolder(this.intrinsicMultiHitType); applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); this.multiHitType = hitType.value; diff --git a/src/test/abilities/battle_bond.test.ts b/src/test/abilities/battle_bond.test.ts index c7dffeb150a..283fb0d0f14 100644 --- a/src/test/abilities/battle_bond.test.ts +++ b/src/test/abilities/battle_bond.test.ts @@ -1,17 +1,19 @@ +import { allMoves, MultiHitAttr, MultiHitType } from "#app/data/move"; import { Status, StatusEffect } from "#app/data/status-effect"; -import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import GameManager from "#test/utils/gameManager"; -import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; describe("Abilities - BATTLE BOND", () => { let phaserGame: Phaser.Game; let game: GameManager; + const baseForm = 1; + const ashForm = 2; + beforeAll(() => { phaserGame = new Phaser.Game({ type: Phaser.HEADLESS, @@ -24,40 +26,68 @@ describe("Abilities - BATTLE BOND", () => { beforeEach(() => { game = new GameManager(phaserGame); - const moveToUse = Moves.SPLASH; - game.override.battleType("single"); - game.override.ability(Abilities.BATTLE_BOND); - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); + game.override.battleType("single") + .startingWave(4) // Leads to arena reset on Wave 5 trainer battle + .ability(Abilities.BATTLE_BOND) + .starterForms({ [Species.GRENINJA]: ashForm, }) + .moveset([ Moves.SPLASH, Moves.WATER_SHURIKEN ]) + .enemySpecies(Species.BULBASAUR) + .enemyMoveset(Moves.SPLASH) + .startingLevel(100) // Avoid levelling up + .enemyLevel(1000); // Avoid opponent dying before `doKillOpponents()` }); - test( - "check if fainted pokemon switches to base form on arena reset", - async () => { - const baseForm = 1; - const ashForm = 2; - game.override.startingWave(4); - game.override.starterForms({ - [Species.GRENINJA]: ashForm, - }); + it("check if fainted pokemon switches to base form on arena reset", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); - await game.startBattle([ Species.MAGIKARP, Species.GRENINJA ]); + const greninja = game.scene.getParty()[1]; + expect(greninja.formIndex).toBe(ashForm); - const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA); - expect(greninja).toBeDefined(); - expect(greninja!.formIndex).toBe(ashForm); + greninja.hp = 0; + greninja.status = new Status(StatusEffect.FAINT); + expect(greninja.isFainted()).toBe(true); - greninja!.hp = 0; - greninja!.status = new Status(StatusEffect.FAINT); - expect(greninja!.isFainted()).toBe(true); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("TurnEndPhase"); + game.doSelectModifier(); + await game.phaseInterceptor.to("QuietFormChangePhase"); - game.move.select(Moves.SPLASH); - await game.doKillOpponents(); - await game.phaseInterceptor.to(TurnEndPhase); - game.doSelectModifier(); - await game.phaseInterceptor.to(QuietFormChangePhase); + expect(greninja.formIndex).toBe(baseForm); + }); - expect(greninja!.formIndex).toBe(baseForm); - }, - ); + it("should not keep buffing Water Shuriken after Greninja switches to base form", async () => { + await game.classicMode.startBattle([ Species.GRENINJA ]); + + const waterShuriken = allMoves[Moves.WATER_SHURIKEN]; + vi.spyOn(waterShuriken, "calculateBattlePower"); + + let actualMultiHitType: MultiHitType | null = null; + const multiHitAttr = waterShuriken.getAttrs(MultiHitAttr)[0]; + vi.spyOn(multiHitAttr, "getHitCount").mockImplementation(() => { + actualMultiHitType = multiHitAttr.getMultiHitType(); + return 3; + }); + + // Wave 4: Use Water Shuriken in Ash form + let expectedBattlePower = 20; + let expectedMultiHitType = MultiHitType._3; + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + + await game.doKillOpponents(); + await game.toNextWave(); + + // Wave 5: Use Water Shuriken in base form + expectedBattlePower = 15; + expectedMultiHitType = MultiHitType._2_TO_5; + + game.move.select(Moves.WATER_SHURIKEN); + await game.phaseInterceptor.to("BerryPhase", false); + expect(waterShuriken.calculateBattlePower).toHaveLastReturnedWith(expectedBattlePower); + expect(actualMultiHitType).toBe(expectedMultiHitType); + }); }); From a778537ccadcfe23a39766abcf8afee7b287ca09 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:43:50 -0700 Subject: [PATCH 22/75] [P2] Sketch Failure Bug involving multiple Sketch-s in a moveset (#4618) * Sketch bug fix * Added test --------- Co-authored-by: frutescens --- src/phases/turn-start-phase.ts | 2 +- src/test/moves/sketch.test.ts | 53 ++++++++++++++++++++++++++++++ src/test/utils/gameManagerUtils.ts | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/test/moves/sketch.test.ts diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 95d55986185..53623f933f2 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -158,7 +158,7 @@ export class TurnStartPhase extends FieldPhase { if (!queuedMove) { continue; } - const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); + const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); if (move.getMove().hasAttr(MoveHeaderAttr)) { this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); } diff --git a/src/test/moves/sketch.test.ts b/src/test/moves/sketch.test.ts new file mode 100644 index 00000000000..2e3eb97a76c --- /dev/null +++ b/src/test/moves/sketch.test.ts @@ -0,0 +1,53 @@ +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { MoveResult } from "#app/field/pokemon"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Sketch", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("Sketch should not fail even if a previous Sketch failed to retrieve a valid move and ran out of PP", async () => { + game.override.moveset([ Moves.SKETCH, Moves.SKETCH ]); + + await game.classicMode.startBattle([ Species.REGIELEKI ]); + const playerPokemon = game.scene.getPlayerPokemon(); + + game.move.select(Moves.SKETCH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.FAIL); + const moveSlot0 = playerPokemon?.getMoveset()[0]; + expect(moveSlot0?.moveId).toBe(Moves.SKETCH); + expect(moveSlot0?.getPpRatio()).toBe(0); + + await game.toNextTurn(); + game.move.select(Moves.SKETCH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(playerPokemon?.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + // Can't verify if the player Pokemon's moveset was successfully changed because of overrides. + }); +}); diff --git a/src/test/utils/gameManagerUtils.ts b/src/test/utils/gameManagerUtils.ts index 700d93082d8..543ee9627fe 100644 --- a/src/test/utils/gameManagerUtils.ts +++ b/src/test/utils/gameManagerUtils.ts @@ -86,7 +86,7 @@ export function waitUntil(truth) { export function getMovePosition(scene: BattleScene, pokemonIndex: 0 | 1, move: Moves) { const playerPokemon = scene.getPlayerField()[pokemonIndex]; const moveSet = playerPokemon.getMoveset(); - const index = moveSet.findIndex((m) => m?.moveId === move); + const index = moveSet.findIndex((m) => m?.moveId === move && m?.ppUsed < m?.getMovePp()); console.log(`Move position for ${Moves[move]} (=${move}):`, index); return index; } From ba7e26152e560032792e94257b11510160ea184f Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:45:02 -0700 Subject: [PATCH 23/75] [Bug] Fix substitute interactions with `PostDefendAbAttr`s (#4570) * Fixes some Substitute interactions Specifically with Disguise/Ice Face and Gulp Missile * Add tests * Fix linting * Add `hitsSubstitute()` checks to all `PostDefendAbAttr`s Also fix comment indentation in `MoveEffectPhase` * Revert `move-effect-phase.ts` changes --- src/data/ability.ts | 140 +++++++++++++----------- src/data/battler-tags.ts | 4 + src/data/move.ts | 12 +- src/test/abilities/disguise.test.ts | 16 ++- src/test/abilities/gulp_missile.test.ts | 31 +++++- src/test/abilities/ice_face.test.ts | 38 +++++-- 6 files changed, 152 insertions(+), 89 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 43d02da1733..6a391818866 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -634,15 +634,15 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr { * Examples include: Absorb, Draining Kiss, Bitter Blade, etc. * Also displays a message to show this ability was activated. * @param pokemon {@linkcode Pokemon} with this ability - * @param passive N/A + * @param _passive N/A * @param attacker {@linkcode Pokemon} that is attacking this Pokemon * @param move {@linkcode PokemonMove} that is being used - * @param hitResult N/A - * @args N/A + * @param _hitResult N/A + * @param _args N/A * @returns true if healing should be reversed on a healing move, false otherwise. */ - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.hasAttr(HitHealAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { pokemon.scene.queueMessage(i18next.t("abilityTriggers:reverseDrain", { pokemonNameWithAffix: getPokemonNameWithAffix(attacker) })); } @@ -669,8 +669,8 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr { this.allOthers = allOthers; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return true; } @@ -707,13 +707,13 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr { this.selfTarget = selfTarget; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - const hpGateFlat: integer = Math.ceil(pokemon.getMaxHp() * this.hpGate); + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate); const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1]; const damageReceived = lastAttackReceived?.damage || 0; - if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat)) { - if (!simulated ) { + if (this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon)) { + if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, (this.selfTarget ? pokemon : attacker).getBattlerIndex(), true, this.stats, this.stages)); } return true; @@ -734,8 +734,8 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { const tag = pokemon.scene.arena.getTag(this.tagType) as ArenaTrapTag; if (!pokemon.scene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers) { if (!simulated) { @@ -758,8 +758,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { this.tagType = tagType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (!pokemon.getTag(this.tagType) && !simulated) { pokemon.addTag(this.tagType, undefined, undefined, pokemon.id); pokemon.scene.queueMessage(i18next.t("abilityTriggers:windPowerCharged", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name })); @@ -771,8 +771,8 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr { } export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { + if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return true; } @@ -787,7 +787,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendTypeChange", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName, @@ -805,8 +805,8 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr { this.terrainType = terrainType; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (hitResult < HitResult.NO_EFFECT) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): boolean { + if (hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return pokemon.scene.arena.terrain?.terrainType !== (this.terrainType || undefined); } else { @@ -829,8 +829,9 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr { this.effects = effects; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.status + && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)) { const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)]; if (simulated) { return attacker.canSetStatus(effect, true, false, pokemon); @@ -869,8 +870,8 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr { this.turnCount = turnCount; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && pokemon.randSeedInt(100) < this.chance && !move.hitsSubstitute(attacker, pokemon)) { if (simulated) { return attacker.canAddTag(this.tagType); } else { @@ -893,7 +894,11 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { this.stages = stages; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.hitsSubstitute(attacker, pokemon)) { + return false; + } + if (!simulated) { pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ this.stat ], this.stages)); } @@ -901,7 +906,7 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr { return true; } - getCondition(): AbAttrCondition { + override getCondition(): AbAttrCondition { return (pokemon: Pokemon) => pokemon.turnData.attacksReceived.length !== 0 && pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1].critical; } } @@ -915,8 +920,9 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { this.damageRatio = damageRatio; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (!simulated && move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { attacker.damageAndUpdate(Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), HitResult.OTHER); attacker.turnData.damageTaken += Utils.toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)); return true; @@ -925,7 +931,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendContactDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName @@ -948,8 +954,8 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { this.turns = turns; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !move.hitsSubstitute(attacker, pokemon)) { if (pokemon.getTag(BattlerTagType.PERISH_SONG) || attacker.getTag(BattlerTagType.PERISH_SONG)) { return false; } else { @@ -963,24 +969,24 @@ export class PostDefendPerishSongAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:perishBody", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName }); } } export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr { private weatherType: WeatherType; - protected condition: PokemonDefendCondition | null; + protected condition?: PokemonDefendCondition; constructor(weatherType: WeatherType, condition?: PokemonDefendCondition) { super(); this.weatherType = weatherType; - this.condition = condition ?? null; + this.condition = condition; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (this.condition !== null && !this.condition(pokemon, attacker, move)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon)) { return false; } if (!pokemon.scene.arena.weather?.isImmutable()) { @@ -999,8 +1005,9 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { super(); } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) + && !attacker.getAbility().hasAttr(UnswappableAbilityAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { const tempAbilityId = attacker.getAbility().id; attacker.summonData.ability = pokemon.getAbility().id; @@ -1012,7 +1019,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, _abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendAbilitySwap", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } } @@ -1025,8 +1032,9 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { this.ability = ability; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr)) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && !attacker.getAbility().hasAttr(UnsuppressableAbilityAbAttr) + && !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { attacker.summonData.ability = this.ability; } @@ -1037,7 +1045,7 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr { return false; } - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + override getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:postDefendAbilityGive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName @@ -1056,8 +1064,8 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr { this.chance = chance; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean { - if (attacker.getTag(BattlerTagType.DISABLED) === null) { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): boolean { + if (attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)) { if (move.checkFlag(MoveFlags.MAKES_CONTACT, attacker, pokemon) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)) { if (simulated) { return true; @@ -1724,17 +1732,17 @@ export class PostAttackApplyBattlerTagAbAttr extends PostAttackAbAttr { } export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr { - private condition: PokemonDefendCondition | null; + private condition?: PokemonDefendCondition; constructor(condition?: PokemonDefendCondition) { super(); - this.condition = condition ?? null; + this.condition = condition; } - applyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): Promise { + override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): Promise { return new Promise(resolve => { - if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move))) { + if (!simulated && hitResult < HitResult.NO_EFFECT && (!this.condition || this.condition(pokemon, attacker, move)) && !move.hitsSubstitute(attacker, pokemon)) { const heldItems = this.getTargetHeldItems(attacker).filter(i => i.isTransferable); if (heldItems.length) { const stolenItem = heldItems[pokemon.randSeedInt(heldItems.length)]; @@ -4476,7 +4484,7 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { private multiplier: number; private tagType: BattlerTagType; - private recoilDamageFunc: ((pokemon: Pokemon) => number) | undefined; + private recoilDamageFunc?: ((pokemon: Pokemon) => number); private triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string; constructor(condition: PokemonDefendCondition, multiplier: number, tagType: BattlerTagType, triggerMessageFunc: (pokemon: Pokemon, abilityName: string) => string, recoilDamageFunc?: (pokemon: Pokemon) => number) { @@ -4492,16 +4500,16 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { * Applies the pre-defense ability to the Pokémon. * Removes the appropriate `BattlerTagType` when hit by an attack and is in its defense form. * - * @param {Pokemon} pokemon The Pokémon with the ability. - * @param {boolean} passive n/a - * @param {Pokemon} attacker The attacking Pokémon. - * @param {PokemonMove} move The move being used. - * @param {Utils.BooleanHolder} cancelled n/a - * @param {any[]} args Additional arguments. - * @returns {boolean} Whether the immunity was applied. + * @param pokemon The Pokémon with the ability. + * @param _passive n/a + * @param attacker The attacking Pokémon. + * @param move The move being used. + * @param _cancelled n/a + * @param args Additional arguments. + * @returns `true` if the immunity was applied. */ - applyPreDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean { - if (this.condition(pokemon, attacker, move)) { + override applyPreDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _cancelled: Utils.BooleanHolder, args: any[]): boolean { + if (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon)) { if (!simulated) { (args[0] as Utils.NumberHolder).value = this.multiplier; pokemon.removeTag(this.tagType); @@ -4517,12 +4525,12 @@ export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr { /** * Gets the message triggered when the Pokémon avoids damage using the form-changing ability. - * @param {Pokemon} pokemon The Pokémon with the ability. - * @param {string} abilityName The name of the ability. - * @param {...any} args n/a - * @returns {string} The trigger message. + * @param pokemon The Pokémon with the ability. + * @param abilityName The name of the ability. + * @param _args n/a + * @returns The trigger message. */ - getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string { + getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]): string { return this.triggerMessageFunc(pokemon, abilityName); } } @@ -5503,7 +5511,8 @@ export function initAbilities() { .attr(NoFusionAbilityAbAttr) // Add BattlerTagType.DISGUISE if the pokemon is in its disguised form .conditionalAttr(pokemon => pokemon.formIndex === 0, PostSummonAddBattlerTagAbAttr, BattlerTagType.DISGUISE, 0, false) - .attr(FormBlockDamageAbAttr, (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, + .attr(FormBlockDamageAbAttr, + (target, user, move) => !!target.getTag(BattlerTagType.DISGUISE) && target.getMoveEffectiveness(user, move) > 0, 0, BattlerTagType.DISGUISE, (pokemon, abilityName) => i18next.t("abilityTriggers:disguiseAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName }), (pokemon) => Utils.toDmgValue(pokemon.getMaxHp() / 8)) .attr(PostBattleInitFormChangeAbAttr, () => 0) @@ -5665,7 +5674,8 @@ export function initAbilities() { .conditionalAttr(getWeatherCondition(WeatherType.HAIL, WeatherType.SNOW), PostSummonAddBattlerTagAbAttr, BattlerTagType.ICE_FACE, 0) // When weather changes to HAIL or SNOW while pokemon is fielded, add BattlerTagType.ICE_FACE .attr(PostWeatherChangeAddBattlerTagAttr, BattlerTagType.ICE_FACE, 0, WeatherType.HAIL, WeatherType.SNOW) - .attr(FormBlockDamageAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, + .attr(FormBlockDamageAbAttr, + (target, user, move) => move.category === MoveCategory.PHYSICAL && !!target.getTag(BattlerTagType.ICE_FACE), 0, BattlerTagType.ICE_FACE, (pokemon, abilityName) => i18next.t("abilityTriggers:iceFaceAvoidedDamage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), abilityName: abilityName })) .attr(PostBattleInitFormChangeAbAttr, () => 0) .bypassFaint() diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 6307b3d28be..24c82e54427 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2139,6 +2139,10 @@ export class GulpMissileTag extends BattlerTag { return false; } + if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) { + return true; + } + const cancelled = new Utils.BooleanHolder(false); applyAbAttrs(BlockNonDirectDamageAbAttr, attacker, cancelled); diff --git a/src/data/move.ts b/src/data/move.ts index bae8eea0d8a..ff0c24f5032 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4844,14 +4844,14 @@ export class GulpMissileTagAttr extends MoveEffectAttr { /** * Adds BattlerTagType from GulpMissileTag based on the Pokemon's HP ratio. - * @param {Pokemon} user The Pokemon using the move. - * @param {Pokemon} target The Pokemon being targeted by the move. - * @param {Move} move The move being used. - * @param {any[]} args Additional arguments, if any. + * @param user The Pokemon using the move. + * @param _target N/A + * @param move The move being used. + * @param _args N/A * @returns Whether the BattlerTag is applied. */ - apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise { - if (!super.apply(user, target, move, args)) { + apply(user: Pokemon, _target: Pokemon, move: Move, _args: any[]): boolean { + if (!super.apply(user, _target, move, _args)) { return false; } diff --git a/src/test/abilities/disguise.test.ts b/src/test/abilities/disguise.test.ts index a295dd61443..0241aa4b9ea 100644 --- a/src/test/abilities/disguise.test.ts +++ b/src/test/abilities/disguise.test.ts @@ -1,8 +1,9 @@ +import { BattlerIndex } from "#app/battle"; +import { StatusEffect } from "#app/data/status-effect"; import { toDmgValue } from "#app/utils"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { StatusEffect } from "#app/data/status-effect"; import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; @@ -222,4 +223,17 @@ describe("Abilities - Disguise", () => { expect(mimikyu.formIndex).toBe(bustedForm); expect(mimikyu.hp).toBe(maxHp - disguiseDamage); }); + + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyMoveset(Moves.SUBSTITUTE) + .moveset(Moves.POWER_TRIP); + await game.classicMode.startBattle(); + + game.move.select(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.formIndex).toBe(disguisedForm); + }); }); diff --git a/src/test/abilities/gulp_missile.test.ts b/src/test/abilities/gulp_missile.test.ts index 1ca208996b5..01b68d0c89d 100644 --- a/src/test/abilities/gulp_missile.test.ts +++ b/src/test/abilities/gulp_missile.test.ts @@ -1,13 +1,14 @@ -import { BattlerTagType } from "#enums/battler-tag-type"; -import { StatusEffect } from "#enums/status-effect"; +import { BattlerIndex } from "#app/battle"; import Pokemon from "#app/field/pokemon"; -import GameManager from "#test/utils/gameManager"; import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import { StatusEffect } from "#enums/status-effect"; +import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { Stat } from "#enums/stat"; describe("Abilities - Gulp Missile", () => { let phaserGame: Phaser.Game; @@ -40,8 +41,9 @@ describe("Abilities - Gulp Missile", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override + .disableCrits() .battleType("single") - .moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH ]) + .moveset([ Moves.SURF, Moves.DIVE, Moves.SPLASH, Moves.SUBSTITUTE ]) .enemySpecies(Species.SNORLAX) .enemyAbility(Abilities.BALL_FETCH) .enemyMoveset(Moves.SPLASH) @@ -234,6 +236,25 @@ describe("Abilities - Gulp Missile", () => { expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.DEF)).toBe(-1); }); + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyAbility(Abilities.STURDY) + .enemyMoveset([ Moves.SPLASH, Moves.POWER_TRIP ]); + await game.classicMode.startBattle([ Species.CRAMORANT ]); + + game.move.select(Moves.SURF); + await game.forceEnemyMove(Moves.SPLASH); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM); + + game.move.select(Moves.SUBSTITUTE); + await game.forceEnemyMove(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.toNextTurn(); + + expect(game.scene.getPlayerPokemon()!.formIndex).toBe(GULPING_FORM); + }); it("cannot be suppressed", async () => { game.override.enemyMoveset(Moves.GASTRO_ACID); diff --git a/src/test/abilities/ice_face.test.ts b/src/test/abilities/ice_face.test.ts index 723d5e8d855..1c7f7bd6093 100644 --- a/src/test/abilities/ice_face.test.ts +++ b/src/test/abilities/ice_face.test.ts @@ -1,3 +1,4 @@ +import { BattlerIndex } from "#app/battle"; import { MoveEffectPhase } from "#app/phases/move-effect-phase"; import { MoveEndPhase } from "#app/phases/move-end-phase"; import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase"; @@ -36,7 +37,7 @@ describe("Abilities - Ice Face", () => { }); it("takes no damage from physical move and transforms to Noice", async () => { - await game.startBattle([ Species.HITMONLEE ]); + await game.classicMode.startBattle([ Species.HITMONLEE ]); game.move.select(Moves.TACKLE); @@ -52,7 +53,7 @@ describe("Abilities - Ice Face", () => { it("takes no damage from the first hit of multihit physical move and transforms to Noice", async () => { game.override.moveset([ Moves.SURGING_STRIKES ]); game.override.enemyLevel(1); - await game.startBattle([ Species.HITMONLEE ]); + await game.classicMode.startBattle([ Species.HITMONLEE ]); game.move.select(Moves.SURGING_STRIKES); @@ -78,7 +79,7 @@ describe("Abilities - Ice Face", () => { }); it("takes damage from special moves", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.ICE_BEAM); @@ -92,7 +93,7 @@ describe("Abilities - Ice Face", () => { }); it("takes effects from status moves", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.TOXIC_THREAD); @@ -108,7 +109,7 @@ describe("Abilities - Ice Face", () => { game.override.moveset([ Moves.QUICK_ATTACK ]); game.override.enemyMoveset([ Moves.HAIL, Moves.HAIL, Moves.HAIL, Moves.HAIL ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.QUICK_ATTACK); @@ -130,7 +131,7 @@ describe("Abilities - Ice Face", () => { game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); game.override.moveset([ Moves.SNOWSCAPE ]); - await game.startBattle([ Species.EISCUE, Species.NINJASK ]); + await game.classicMode.startBattle([ Species.EISCUE, Species.NINJASK ]); game.move.select(Moves.SNOWSCAPE); @@ -157,7 +158,7 @@ describe("Abilities - Ice Face", () => { game.override.enemySpecies(Species.SHUCKLE); game.override.enemyMoveset([ Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE ]); - await game.startBattle([ Species.EISCUE ]); + await game.classicMode.startBattle([ Species.EISCUE ]); game.move.select(Moves.HAIL); const eiscue = game.scene.getPlayerPokemon()!; @@ -176,7 +177,7 @@ describe("Abilities - Ice Face", () => { it("persists form change when switched out", async () => { game.override.enemyMoveset([ Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK ]); - await game.startBattle([ Species.EISCUE, Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.EISCUE, Species.MAGIKARP ]); game.move.select(Moves.ICE_BEAM); @@ -205,7 +206,7 @@ describe("Abilities - Ice Face", () => { [Species.EISCUE]: noiceForm, }); - await game.startBattle([ Species.EISCUE ]); + await game.classicMode.startBattle([ Species.EISCUE ]); const eiscue = game.scene.getPlayerPokemon()!; @@ -222,10 +223,23 @@ describe("Abilities - Ice Face", () => { expect(eiscue.getTag(BattlerTagType.ICE_FACE)).not.toBe(undefined); }); + it("doesn't trigger if user is behind a substitute", async () => { + game.override + .enemyMoveset(Moves.SUBSTITUTE) + .moveset(Moves.POWER_TRIP); + await game.classicMode.startBattle(); + + game.move.select(Moves.POWER_TRIP); + await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.formIndex).toBe(icefaceForm); + }); + it("cannot be suppressed", async () => { game.override.moveset([ Moves.GASTRO_ACID ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.GASTRO_ACID); @@ -241,7 +255,7 @@ describe("Abilities - Ice Face", () => { it("cannot be swapped with another ability", async () => { game.override.moveset([ Moves.SKILL_SWAP ]); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.SKILL_SWAP); @@ -257,7 +271,7 @@ describe("Abilities - Ice Face", () => { it("cannot be copied", async () => { game.override.ability(Abilities.TRACE); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); game.move.select(Moves.SIMPLE_BEAM); From 0996789ee6f232c45cf4d6597c78906d8e7c2cdc Mon Sep 17 00:00:00 2001 From: "Adrian T." <68144167+torranx@users.noreply.github.com> Date: Thu, 10 Oct 2024 23:54:43 +0800 Subject: [PATCH 24/75] [Refactor] Improve typing in `phaseInterceptor.ts` (#4560) * improve typing in phaseInterceptor * add more param typings --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/test/utils/phaseInterceptor.ts | 126 +++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index d108c4cb2ea..ec9309e2405 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -53,6 +53,7 @@ import { } from "#app/phases/mystery-encounter-phases"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { PartyExpPhase } from "#app/phases/party-exp-phase"; +import { ExpPhase } from "#app/phases/exp-phase"; export interface PromptHandler { phaseTarget?: string; @@ -61,7 +62,114 @@ export interface PromptHandler { expireFn?: () => void; awaitingActionInput?: boolean; } -import { ExpPhase } from "#app/phases/exp-phase"; + +type PhaseClass = + | typeof LoginPhase + | typeof TitlePhase + | typeof SelectGenderPhase + | typeof EncounterPhase + | typeof NewBiomeEncounterPhase + | typeof SelectStarterPhase + | typeof PostSummonPhase + | typeof SummonPhase + | typeof ToggleDoublePositionPhase + | typeof CheckSwitchPhase + | typeof ShowAbilityPhase + | typeof MessagePhase + | typeof TurnInitPhase + | typeof CommandPhase + | typeof EnemyCommandPhase + | typeof TurnStartPhase + | typeof MovePhase + | typeof MoveEffectPhase + | typeof DamagePhase + | typeof FaintPhase + | typeof BerryPhase + | typeof TurnEndPhase + | typeof BattleEndPhase + | typeof EggLapsePhase + | typeof SelectModifierPhase + | typeof NextEncounterPhase + | typeof NewBattlePhase + | typeof VictoryPhase + | typeof LearnMovePhase + | typeof MoveEndPhase + | typeof StatStageChangePhase + | typeof ShinySparklePhase + | typeof SelectTargetPhase + | typeof UnavailablePhase + | typeof QuietFormChangePhase + | typeof SwitchPhase + | typeof SwitchSummonPhase + | typeof PartyHealPhase + | typeof EvolutionPhase + | typeof EndEvolutionPhase + | typeof LevelCapPhase + | typeof AttemptRunPhase + | typeof SelectBiomePhase + | typeof MysteryEncounterPhase + | typeof MysteryEncounterOptionSelectedPhase + | typeof MysteryEncounterBattlePhase + | typeof MysteryEncounterRewardsPhase + | typeof PostMysteryEncounterPhase + | typeof ModifierRewardPhase + | typeof PartyExpPhase + | typeof ExpPhase; + +type PhaseString = + | "LoginPhase" + | "TitlePhase" + | "SelectGenderPhase" + | "EncounterPhase" + | "NewBiomeEncounterPhase" + | "SelectStarterPhase" + | "PostSummonPhase" + | "SummonPhase" + | "ToggleDoublePositionPhase" + | "CheckSwitchPhase" + | "ShowAbilityPhase" + | "MessagePhase" + | "TurnInitPhase" + | "CommandPhase" + | "EnemyCommandPhase" + | "TurnStartPhase" + | "MovePhase" + | "MoveEffectPhase" + | "DamagePhase" + | "FaintPhase" + | "BerryPhase" + | "TurnEndPhase" + | "BattleEndPhase" + | "EggLapsePhase" + | "SelectModifierPhase" + | "NextEncounterPhase" + | "NewBattlePhase" + | "VictoryPhase" + | "LearnMovePhase" + | "MoveEndPhase" + | "StatStageChangePhase" + | "ShinySparklePhase" + | "SelectTargetPhase" + | "UnavailablePhase" + | "QuietFormChangePhase" + | "SwitchPhase" + | "SwitchSummonPhase" + | "PartyHealPhase" + | "EvolutionPhase" + | "EndEvolutionPhase" + | "LevelCapPhase" + | "AttemptRunPhase" + | "SelectBiomePhase" + | "MysteryEncounterPhase" + | "MysteryEncounterOptionSelectedPhase" + | "MysteryEncounterBattlePhase" + | "MysteryEncounterRewardsPhase" + | "PostMysteryEncounterPhase" + | "ModifierRewardPhase" + | "PartyExpPhase" + | "ExpPhase"; + +type PhaseInterceptorPhase = PhaseClass | PhaseString; export default class PhaseInterceptor { public scene; @@ -172,7 +280,7 @@ export default class PhaseInterceptor { * @param phaseFrom - The phase to start from. * @returns The instance of the PhaseInterceptor. */ - runFrom(phaseFrom) { + runFrom(phaseFrom: PhaseInterceptorPhase): PhaseInterceptor { this.phaseFrom = phaseFrom; return this; } @@ -180,9 +288,10 @@ export default class PhaseInterceptor { /** * Method to transition to a target phase. * @param phaseTo - The phase to transition to. + * @param runTarget - Whether or not to run the target phase. * @returns A promise that resolves when the transition is complete. */ - async to(phaseTo, runTarget: boolean = true): Promise { + async to(phaseTo: PhaseInterceptorPhase, runTarget: boolean = true): Promise { return new Promise(async (resolve, reject) => { ErrorInterceptor.getInstance().add(this); if (this.phaseFrom) { @@ -219,7 +328,7 @@ export default class PhaseInterceptor { * @param skipFn - Optional skip function. * @returns A promise that resolves when the phase is run. */ - run(phaseTarget, skipFn?): Promise { + run(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => { @@ -253,7 +362,7 @@ export default class PhaseInterceptor { }); } - whenAboutToRun(phaseTarget, skipFn?): Promise { + whenAboutToRun(phaseTarget: PhaseInterceptorPhase, skipFn?: (className: PhaseClass) => boolean): Promise { const targetName = typeof phaseTarget === "string" ? phaseTarget : phaseTarget.name; this.scene.moveAnimations = null; // Mandatory to avoid crash return new Promise(async (resolve, reject) => { @@ -311,7 +420,7 @@ export default class PhaseInterceptor { * Method to start a phase and log it. * @param phase - The phase to start. */ - startPhase(phase) { + startPhase(phase: PhaseClass) { this.log.push(phase.name); const instance = this.scene.getCurrentPhase(); this.onHold.push({ @@ -340,9 +449,10 @@ export default class PhaseInterceptor { /** * m2m to set mode. - * @param phase - The phase to start. + * @param mode - The {@linkcode Mode} to set. + * @param args - Additional arguments to pass to the original method. */ - setMode(mode: Mode, ...args: any[]): Promise { + setMode(mode: Mode, ...args: unknown[]): Promise { const currentPhase = this.scene.getCurrentPhase(); const instance = this.scene.ui; console.log("setMode", `${Mode[mode]} (=${mode})`, args); From 6ad5ba972cc7eafc451ed264bae3e1c153463fd8 Mon Sep 17 00:00:00 2001 From: ImperialSympathizer <110984302+ben-lear@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:29:26 -0400 Subject: [PATCH 25/75] [Enhancement] Refactor Starter Species to use separate EggTier map (#4591) * creates table for tracking species egg tiers * creates table for tracking species egg tiers * rename EggTier enum values * replace clamp util function with Phaser function --------- Co-authored-by: ImperialSympathizer --- src/data/balance/species-egg-tiers.ts | 603 ++++++++++++++++++ src/data/egg.ts | 91 ++- .../encounters/a-trainers-test-encounter.ts | 4 +- .../the-expert-pokemon-breeder-encounter.ts | 2 +- src/enums/egg-type.ts | 6 +- src/field/pokemon.ts | 2 +- src/test/eggs/egg.test.ts | 36 +- .../a-trainers-test-encounter.test.ts | 4 +- .../the-expert-breeder-encounter.test.ts | 6 +- src/ui/battle-info.ts | 2 +- src/ui/egg-gacha-ui-handler.ts | 6 +- src/ui/text.ts | 6 +- src/utils.ts | 4 - 13 files changed, 676 insertions(+), 96 deletions(-) create mode 100644 src/data/balance/species-egg-tiers.ts diff --git a/src/data/balance/species-egg-tiers.ts b/src/data/balance/species-egg-tiers.ts new file mode 100644 index 00000000000..cd266dfcf54 --- /dev/null +++ b/src/data/balance/species-egg-tiers.ts @@ -0,0 +1,603 @@ +import { Species } from "#enums/species"; +import { EggTier } from "#enums/egg-type"; + +/** + * Map of all starters and their respective {@linkcode EggTier}, which determines the type of egg the starter hatches from. + */ +export const speciesEggTiers = { + [Species.BULBASAUR]: EggTier.COMMON, + [Species.CHARMANDER]: EggTier.COMMON, + [Species.SQUIRTLE]: EggTier.COMMON, + [Species.CATERPIE]: EggTier.COMMON, + [Species.WEEDLE]: EggTier.COMMON, + [Species.PIDGEY]: EggTier.COMMON, + [Species.RATTATA]: EggTier.COMMON, + [Species.SPEAROW]: EggTier.COMMON, + [Species.EKANS]: EggTier.COMMON, + [Species.PIKACHU]: EggTier.COMMON, + [Species.SANDSHREW]: EggTier.COMMON, + [Species.NIDORAN_F]: EggTier.COMMON, + [Species.NIDORAN_M]: EggTier.COMMON, + [Species.CLEFAIRY]: EggTier.COMMON, + [Species.VULPIX]: EggTier.COMMON, + [Species.JIGGLYPUFF]: EggTier.COMMON, + [Species.ZUBAT]: EggTier.COMMON, + [Species.ODDISH]: EggTier.COMMON, + [Species.PARAS]: EggTier.COMMON, + [Species.VENONAT]: EggTier.COMMON, + [Species.DIGLETT]: EggTier.COMMON, + [Species.MEOWTH]: EggTier.COMMON, + [Species.PSYDUCK]: EggTier.COMMON, + [Species.MANKEY]: EggTier.RARE, + [Species.GROWLITHE]: EggTier.RARE, + [Species.POLIWAG]: EggTier.COMMON, + [Species.ABRA]: EggTier.RARE, + [Species.MACHOP]: EggTier.COMMON, + [Species.BELLSPROUT]: EggTier.COMMON, + [Species.TENTACOOL]: EggTier.COMMON, + [Species.GEODUDE]: EggTier.COMMON, + [Species.PONYTA]: EggTier.COMMON, + [Species.SLOWPOKE]: EggTier.COMMON, + [Species.MAGNEMITE]: EggTier.RARE, + [Species.FARFETCHD]: EggTier.COMMON, + [Species.DODUO]: EggTier.COMMON, + [Species.SEEL]: EggTier.COMMON, + [Species.GRIMER]: EggTier.COMMON, + [Species.SHELLDER]: EggTier.RARE, + [Species.GASTLY]: EggTier.RARE, + [Species.ONIX]: EggTier.COMMON, + [Species.DROWZEE]: EggTier.COMMON, + [Species.KRABBY]: EggTier.COMMON, + [Species.VOLTORB]: EggTier.COMMON, + [Species.EXEGGCUTE]: EggTier.COMMON, + [Species.CUBONE]: EggTier.COMMON, + [Species.HITMONLEE]: EggTier.RARE, + [Species.HITMONCHAN]: EggTier.RARE, + [Species.LICKITUNG]: EggTier.COMMON, + [Species.KOFFING]: EggTier.COMMON, + [Species.RHYHORN]: EggTier.COMMON, + [Species.CHANSEY]: EggTier.COMMON, + [Species.TANGELA]: EggTier.COMMON, + [Species.KANGASKHAN]: EggTier.RARE, + [Species.HORSEA]: EggTier.COMMON, + [Species.GOLDEEN]: EggTier.COMMON, + [Species.STARYU]: EggTier.COMMON, + [Species.MR_MIME]: EggTier.COMMON, + [Species.SCYTHER]: EggTier.RARE, + [Species.JYNX]: EggTier.RARE, + [Species.ELECTABUZZ]: EggTier.RARE, + [Species.MAGMAR]: EggTier.RARE, + [Species.PINSIR]: EggTier.RARE, + [Species.TAUROS]: EggTier.RARE, + [Species.MAGIKARP]: EggTier.RARE, + [Species.LAPRAS]: EggTier.RARE, + [Species.DITTO]: EggTier.COMMON, + [Species.EEVEE]: EggTier.COMMON, + [Species.PORYGON]: EggTier.RARE, + [Species.OMANYTE]: EggTier.COMMON, + [Species.KABUTO]: EggTier.COMMON, + [Species.AERODACTYL]: EggTier.RARE, + [Species.SNORLAX]: EggTier.RARE, + [Species.ARTICUNO]: EggTier.EPIC, + [Species.ZAPDOS]: EggTier.EPIC, + [Species.MOLTRES]: EggTier.EPIC, + [Species.DRATINI]: EggTier.RARE, + [Species.MEWTWO]: EggTier.LEGENDARY, + [Species.MEW]: EggTier.EPIC, + + [Species.CHIKORITA]: EggTier.COMMON, + [Species.CYNDAQUIL]: EggTier.COMMON, + [Species.TOTODILE]: EggTier.COMMON, + [Species.SENTRET]: EggTier.COMMON, + [Species.HOOTHOOT]: EggTier.COMMON, + [Species.LEDYBA]: EggTier.COMMON, + [Species.SPINARAK]: EggTier.COMMON, + [Species.CHINCHOU]: EggTier.COMMON, + [Species.PICHU]: EggTier.COMMON, + [Species.CLEFFA]: EggTier.COMMON, + [Species.IGGLYBUFF]: EggTier.COMMON, + [Species.TOGEPI]: EggTier.COMMON, + [Species.NATU]: EggTier.COMMON, + [Species.MAREEP]: EggTier.COMMON, + [Species.MARILL]: EggTier.RARE, + [Species.SUDOWOODO]: EggTier.COMMON, + [Species.HOPPIP]: EggTier.COMMON, + [Species.AIPOM]: EggTier.COMMON, + [Species.SUNKERN]: EggTier.COMMON, + [Species.YANMA]: EggTier.COMMON, + [Species.WOOPER]: EggTier.COMMON, + [Species.MURKROW]: EggTier.COMMON, + [Species.MISDREAVUS]: EggTier.COMMON, + [Species.UNOWN]: EggTier.COMMON, + [Species.WOBBUFFET]: EggTier.COMMON, + [Species.GIRAFARIG]: EggTier.COMMON, + [Species.PINECO]: EggTier.COMMON, + [Species.DUNSPARCE]: EggTier.COMMON, + [Species.GLIGAR]: EggTier.COMMON, + [Species.SNUBBULL]: EggTier.COMMON, + [Species.QWILFISH]: EggTier.COMMON, + [Species.SHUCKLE]: EggTier.COMMON, + [Species.HERACROSS]: EggTier.RARE, + [Species.SNEASEL]: EggTier.RARE, + [Species.TEDDIURSA]: EggTier.RARE, + [Species.SLUGMA]: EggTier.COMMON, + [Species.SWINUB]: EggTier.COMMON, + [Species.CORSOLA]: EggTier.COMMON, + [Species.REMORAID]: EggTier.COMMON, + [Species.DELIBIRD]: EggTier.COMMON, + [Species.MANTINE]: EggTier.COMMON, + [Species.SKARMORY]: EggTier.RARE, + [Species.HOUNDOUR]: EggTier.COMMON, + [Species.PHANPY]: EggTier.COMMON, + [Species.STANTLER]: EggTier.COMMON, + [Species.SMEARGLE]: EggTier.COMMON, + [Species.TYROGUE]: EggTier.COMMON, + [Species.SMOOCHUM]: EggTier.COMMON, + [Species.ELEKID]: EggTier.COMMON, + [Species.MAGBY]: EggTier.COMMON, + [Species.MILTANK]: EggTier.RARE, + [Species.RAIKOU]: EggTier.EPIC, + [Species.ENTEI]: EggTier.EPIC, + [Species.SUICUNE]: EggTier.EPIC, + [Species.LARVITAR]: EggTier.RARE, + [Species.LUGIA]: EggTier.LEGENDARY, + [Species.HO_OH]: EggTier.LEGENDARY, + [Species.CELEBI]: EggTier.EPIC, + + [Species.TREECKO]: EggTier.COMMON, + [Species.TORCHIC]: EggTier.RARE, + [Species.MUDKIP]: EggTier.COMMON, + [Species.POOCHYENA]: EggTier.COMMON, + [Species.ZIGZAGOON]: EggTier.COMMON, + [Species.WURMPLE]: EggTier.COMMON, + [Species.LOTAD]: EggTier.COMMON, + [Species.SEEDOT]: EggTier.COMMON, + [Species.TAILLOW]: EggTier.COMMON, + [Species.WINGULL]: EggTier.COMMON, + [Species.RALTS]: EggTier.COMMON, + [Species.SURSKIT]: EggTier.COMMON, + [Species.SHROOMISH]: EggTier.COMMON, + [Species.SLAKOTH]: EggTier.RARE, + [Species.NINCADA]: EggTier.RARE, + [Species.WHISMUR]: EggTier.COMMON, + [Species.MAKUHITA]: EggTier.COMMON, + [Species.AZURILL]: EggTier.RARE, + [Species.NOSEPASS]: EggTier.COMMON, + [Species.SKITTY]: EggTier.COMMON, + [Species.SABLEYE]: EggTier.COMMON, + [Species.MAWILE]: EggTier.COMMON, + [Species.ARON]: EggTier.COMMON, + [Species.MEDITITE]: EggTier.COMMON, + [Species.ELECTRIKE]: EggTier.COMMON, + [Species.PLUSLE]: EggTier.COMMON, + [Species.MINUN]: EggTier.COMMON, + [Species.VOLBEAT]: EggTier.COMMON, + [Species.ILLUMISE]: EggTier.COMMON, + [Species.ROSELIA]: EggTier.COMMON, + [Species.GULPIN]: EggTier.COMMON, + [Species.CARVANHA]: EggTier.COMMON, + [Species.WAILMER]: EggTier.COMMON, + [Species.NUMEL]: EggTier.COMMON, + [Species.TORKOAL]: EggTier.COMMON, + [Species.SPOINK]: EggTier.COMMON, + [Species.SPINDA]: EggTier.COMMON, + [Species.TRAPINCH]: EggTier.COMMON, + [Species.CACNEA]: EggTier.COMMON, + [Species.SWABLU]: EggTier.COMMON, + [Species.ZANGOOSE]: EggTier.RARE, + [Species.SEVIPER]: EggTier.COMMON, + [Species.LUNATONE]: EggTier.COMMON, + [Species.SOLROCK]: EggTier.COMMON, + [Species.BARBOACH]: EggTier.COMMON, + [Species.CORPHISH]: EggTier.COMMON, + [Species.BALTOY]: EggTier.COMMON, + [Species.LILEEP]: EggTier.COMMON, + [Species.ANORITH]: EggTier.COMMON, + [Species.FEEBAS]: EggTier.RARE, + [Species.CASTFORM]: EggTier.COMMON, + [Species.KECLEON]: EggTier.COMMON, + [Species.SHUPPET]: EggTier.COMMON, + [Species.DUSKULL]: EggTier.COMMON, + [Species.TROPIUS]: EggTier.COMMON, + [Species.CHIMECHO]: EggTier.COMMON, + [Species.ABSOL]: EggTier.RARE, + [Species.WYNAUT]: EggTier.COMMON, + [Species.SNORUNT]: EggTier.COMMON, + [Species.SPHEAL]: EggTier.COMMON, + [Species.CLAMPERL]: EggTier.COMMON, + [Species.RELICANTH]: EggTier.COMMON, + [Species.LUVDISC]: EggTier.COMMON, + [Species.BAGON]: EggTier.RARE, + [Species.BELDUM]: EggTier.RARE, + [Species.REGIROCK]: EggTier.EPIC, + [Species.REGICE]: EggTier.EPIC, + [Species.REGISTEEL]: EggTier.EPIC, + [Species.LATIAS]: EggTier.EPIC, + [Species.LATIOS]: EggTier.EPIC, + [Species.KYOGRE]: EggTier.LEGENDARY, + [Species.GROUDON]: EggTier.LEGENDARY, + [Species.RAYQUAZA]: EggTier.LEGENDARY, + [Species.JIRACHI]: EggTier.EPIC, + [Species.DEOXYS]: EggTier.EPIC, + + [Species.TURTWIG]: EggTier.COMMON, + [Species.CHIMCHAR]: EggTier.COMMON, + [Species.PIPLUP]: EggTier.COMMON, + [Species.STARLY]: EggTier.COMMON, + [Species.BIDOOF]: EggTier.COMMON, + [Species.KRICKETOT]: EggTier.COMMON, + [Species.SHINX]: EggTier.COMMON, + [Species.BUDEW]: EggTier.COMMON, + [Species.CRANIDOS]: EggTier.COMMON, + [Species.SHIELDON]: EggTier.COMMON, + [Species.BURMY]: EggTier.COMMON, + [Species.COMBEE]: EggTier.COMMON, + [Species.PACHIRISU]: EggTier.COMMON, + [Species.BUIZEL]: EggTier.COMMON, + [Species.CHERUBI]: EggTier.COMMON, + [Species.SHELLOS]: EggTier.COMMON, + [Species.DRIFLOON]: EggTier.COMMON, + [Species.BUNEARY]: EggTier.COMMON, + [Species.GLAMEOW]: EggTier.COMMON, + [Species.CHINGLING]: EggTier.COMMON, + [Species.STUNKY]: EggTier.COMMON, + [Species.BRONZOR]: EggTier.COMMON, + [Species.BONSLY]: EggTier.COMMON, + [Species.MIME_JR]: EggTier.COMMON, + [Species.HAPPINY]: EggTier.COMMON, + [Species.CHATOT]: EggTier.COMMON, + [Species.SPIRITOMB]: EggTier.RARE, + [Species.GIBLE]: EggTier.RARE, + [Species.MUNCHLAX]: EggTier.RARE, + [Species.RIOLU]: EggTier.COMMON, + [Species.HIPPOPOTAS]: EggTier.COMMON, + [Species.SKORUPI]: EggTier.COMMON, + [Species.CROAGUNK]: EggTier.COMMON, + [Species.CARNIVINE]: EggTier.COMMON, + [Species.FINNEON]: EggTier.COMMON, + [Species.MANTYKE]: EggTier.COMMON, + [Species.SNOVER]: EggTier.COMMON, + [Species.ROTOM]: EggTier.RARE, + [Species.UXIE]: EggTier.EPIC, + [Species.MESPRIT]: EggTier.EPIC, + [Species.AZELF]: EggTier.EPIC, + [Species.DIALGA]: EggTier.LEGENDARY, + [Species.PALKIA]: EggTier.LEGENDARY, + [Species.HEATRAN]: EggTier.EPIC, + [Species.REGIGIGAS]: EggTier.EPIC, + [Species.GIRATINA]: EggTier.LEGENDARY, + [Species.CRESSELIA]: EggTier.EPIC, + [Species.PHIONE]: EggTier.RARE, + [Species.MANAPHY]: EggTier.EPIC, + [Species.DARKRAI]: EggTier.EPIC, + [Species.SHAYMIN]: EggTier.EPIC, + [Species.ARCEUS]: EggTier.LEGENDARY, + + [Species.VICTINI]: EggTier.EPIC, + [Species.SNIVY]: EggTier.COMMON, + [Species.TEPIG]: EggTier.COMMON, + [Species.OSHAWOTT]: EggTier.COMMON, + [Species.PATRAT]: EggTier.COMMON, + [Species.LILLIPUP]: EggTier.COMMON, + [Species.PURRLOIN]: EggTier.COMMON, + [Species.PANSAGE]: EggTier.COMMON, + [Species.PANSEAR]: EggTier.COMMON, + [Species.PANPOUR]: EggTier.COMMON, + [Species.MUNNA]: EggTier.COMMON, + [Species.PIDOVE]: EggTier.COMMON, + [Species.BLITZLE]: EggTier.COMMON, + [Species.ROGGENROLA]: EggTier.COMMON, + [Species.WOOBAT]: EggTier.COMMON, + [Species.DRILBUR]: EggTier.RARE, + [Species.AUDINO]: EggTier.COMMON, + [Species.TIMBURR]: EggTier.RARE, + [Species.TYMPOLE]: EggTier.COMMON, + [Species.THROH]: EggTier.RARE, + [Species.SAWK]: EggTier.RARE, + [Species.SEWADDLE]: EggTier.COMMON, + [Species.VENIPEDE]: EggTier.COMMON, + [Species.COTTONEE]: EggTier.COMMON, + [Species.PETILIL]: EggTier.COMMON, + [Species.BASCULIN]: EggTier.RARE, + [Species.SANDILE]: EggTier.RARE, + [Species.DARUMAKA]: EggTier.RARE, + [Species.MARACTUS]: EggTier.COMMON, + [Species.DWEBBLE]: EggTier.COMMON, + [Species.SCRAGGY]: EggTier.COMMON, + [Species.SIGILYPH]: EggTier.RARE, + [Species.YAMASK]: EggTier.COMMON, + [Species.TIRTOUGA]: EggTier.COMMON, + [Species.ARCHEN]: EggTier.COMMON, + [Species.TRUBBISH]: EggTier.COMMON, + [Species.ZORUA]: EggTier.COMMON, + [Species.MINCCINO]: EggTier.COMMON, + [Species.GOTHITA]: EggTier.COMMON, + [Species.SOLOSIS]: EggTier.COMMON, + [Species.DUCKLETT]: EggTier.COMMON, + [Species.VANILLITE]: EggTier.COMMON, + [Species.DEERLING]: EggTier.COMMON, + [Species.EMOLGA]: EggTier.COMMON, + [Species.KARRABLAST]: EggTier.COMMON, + [Species.FOONGUS]: EggTier.COMMON, + [Species.FRILLISH]: EggTier.COMMON, + [Species.ALOMOMOLA]: EggTier.RARE, + [Species.JOLTIK]: EggTier.COMMON, + [Species.FERROSEED]: EggTier.COMMON, + [Species.KLINK]: EggTier.COMMON, + [Species.TYNAMO]: EggTier.COMMON, + [Species.ELGYEM]: EggTier.COMMON, + [Species.LITWICK]: EggTier.COMMON, + [Species.AXEW]: EggTier.RARE, + [Species.CUBCHOO]: EggTier.COMMON, + [Species.CRYOGONAL]: EggTier.RARE, + [Species.SHELMET]: EggTier.COMMON, + [Species.STUNFISK]: EggTier.COMMON, + [Species.MIENFOO]: EggTier.COMMON, + [Species.DRUDDIGON]: EggTier.RARE, + [Species.GOLETT]: EggTier.COMMON, + [Species.PAWNIARD]: EggTier.RARE, + [Species.BOUFFALANT]: EggTier.RARE, + [Species.RUFFLET]: EggTier.COMMON, + [Species.VULLABY]: EggTier.COMMON, + [Species.HEATMOR]: EggTier.COMMON, + [Species.DURANT]: EggTier.RARE, + [Species.DEINO]: EggTier.RARE, + [Species.LARVESTA]: EggTier.RARE, + [Species.COBALION]: EggTier.EPIC, + [Species.TERRAKION]: EggTier.EPIC, + [Species.VIRIZION]: EggTier.EPIC, + [Species.TORNADUS]: EggTier.EPIC, + [Species.THUNDURUS]: EggTier.EPIC, + [Species.RESHIRAM]: EggTier.LEGENDARY, + [Species.ZEKROM]: EggTier.LEGENDARY, + [Species.LANDORUS]: EggTier.EPIC, + [Species.KYUREM]: EggTier.LEGENDARY, + [Species.KELDEO]: EggTier.EPIC, + [Species.MELOETTA]: EggTier.EPIC, + [Species.GENESECT]: EggTier.EPIC, + + [Species.CHESPIN]: EggTier.COMMON, + [Species.FENNEKIN]: EggTier.COMMON, + [Species.FROAKIE]: EggTier.RARE, + [Species.BUNNELBY]: EggTier.COMMON, + [Species.FLETCHLING]: EggTier.COMMON, + [Species.SCATTERBUG]: EggTier.COMMON, + [Species.LITLEO]: EggTier.COMMON, + [Species.FLABEBE]: EggTier.COMMON, + [Species.SKIDDO]: EggTier.COMMON, + [Species.PANCHAM]: EggTier.COMMON, + [Species.FURFROU]: EggTier.COMMON, + [Species.ESPURR]: EggTier.COMMON, + [Species.HONEDGE]: EggTier.RARE, + [Species.SPRITZEE]: EggTier.COMMON, + [Species.SWIRLIX]: EggTier.COMMON, + [Species.INKAY]: EggTier.COMMON, + [Species.BINACLE]: EggTier.COMMON, + [Species.SKRELP]: EggTier.COMMON, + [Species.CLAUNCHER]: EggTier.COMMON, + [Species.HELIOPTILE]: EggTier.COMMON, + [Species.TYRUNT]: EggTier.COMMON, + [Species.AMAURA]: EggTier.COMMON, + [Species.HAWLUCHA]: EggTier.RARE, + [Species.DEDENNE]: EggTier.COMMON, + [Species.CARBINK]: EggTier.COMMON, + [Species.GOOMY]: EggTier.RARE, + [Species.KLEFKI]: EggTier.COMMON, + [Species.PHANTUMP]: EggTier.COMMON, + [Species.PUMPKABOO]: EggTier.COMMON, + [Species.BERGMITE]: EggTier.COMMON, + [Species.NOIBAT]: EggTier.COMMON, + [Species.XERNEAS]: EggTier.LEGENDARY, + [Species.YVELTAL]: EggTier.LEGENDARY, + [Species.ZYGARDE]: EggTier.LEGENDARY, + [Species.DIANCIE]: EggTier.EPIC, + [Species.HOOPA]: EggTier.EPIC, + [Species.VOLCANION]: EggTier.EPIC, + [Species.ETERNAL_FLOETTE]: EggTier.RARE, + + [Species.ROWLET]: EggTier.COMMON, + [Species.LITTEN]: EggTier.COMMON, + [Species.POPPLIO]: EggTier.RARE, + [Species.PIKIPEK]: EggTier.COMMON, + [Species.YUNGOOS]: EggTier.COMMON, + [Species.GRUBBIN]: EggTier.COMMON, + [Species.CRABRAWLER]: EggTier.COMMON, + [Species.ORICORIO]: EggTier.COMMON, + [Species.CUTIEFLY]: EggTier.COMMON, + [Species.ROCKRUFF]: EggTier.COMMON, + [Species.WISHIWASHI]: EggTier.COMMON, + [Species.MAREANIE]: EggTier.COMMON, + [Species.MUDBRAY]: EggTier.COMMON, + [Species.DEWPIDER]: EggTier.COMMON, + [Species.FOMANTIS]: EggTier.COMMON, + [Species.MORELULL]: EggTier.COMMON, + [Species.SALANDIT]: EggTier.COMMON, + [Species.STUFFUL]: EggTier.COMMON, + [Species.BOUNSWEET]: EggTier.COMMON, + [Species.COMFEY]: EggTier.RARE, + [Species.ORANGURU]: EggTier.RARE, + [Species.PASSIMIAN]: EggTier.RARE, + [Species.WIMPOD]: EggTier.COMMON, + [Species.SANDYGAST]: EggTier.COMMON, + [Species.PYUKUMUKU]: EggTier.COMMON, + [Species.TYPE_NULL]: EggTier.RARE, + [Species.MINIOR]: EggTier.RARE, + [Species.KOMALA]: EggTier.COMMON, + [Species.TURTONATOR]: EggTier.RARE, + [Species.TOGEDEMARU]: EggTier.COMMON, + [Species.MIMIKYU]: EggTier.RARE, + [Species.BRUXISH]: EggTier.RARE, + [Species.DRAMPA]: EggTier.RARE, + [Species.DHELMISE]: EggTier.RARE, + [Species.JANGMO_O]: EggTier.RARE, + [Species.TAPU_KOKO]: EggTier.EPIC, + [Species.TAPU_LELE]: EggTier.EPIC, + [Species.TAPU_BULU]: EggTier.EPIC, + [Species.TAPU_FINI]: EggTier.EPIC, + [Species.COSMOG]: EggTier.EPIC, + [Species.NIHILEGO]: EggTier.EPIC, + [Species.BUZZWOLE]: EggTier.EPIC, + [Species.PHEROMOSA]: EggTier.EPIC, + [Species.XURKITREE]: EggTier.EPIC, + [Species.CELESTEELA]: EggTier.EPIC, + [Species.KARTANA]: EggTier.EPIC, + [Species.GUZZLORD]: EggTier.EPIC, + [Species.NECROZMA]: EggTier.LEGENDARY, + [Species.MAGEARNA]: EggTier.EPIC, + [Species.MARSHADOW]: EggTier.EPIC, + [Species.POIPOLE]: EggTier.EPIC, + [Species.STAKATAKA]: EggTier.EPIC, + [Species.BLACEPHALON]: EggTier.EPIC, + [Species.ZERAORA]: EggTier.EPIC, + [Species.MELTAN]: EggTier.EPIC, + [Species.ALOLA_RATTATA]: EggTier.COMMON, + [Species.ALOLA_SANDSHREW]: EggTier.COMMON, + [Species.ALOLA_VULPIX]: EggTier.COMMON, + [Species.ALOLA_DIGLETT]: EggTier.COMMON, + [Species.ALOLA_MEOWTH]: EggTier.COMMON, + [Species.ALOLA_GEODUDE]: EggTier.COMMON, + [Species.ALOLA_GRIMER]: EggTier.COMMON, + + [Species.GROOKEY]: EggTier.COMMON, + [Species.SCORBUNNY]: EggTier.RARE, + [Species.SOBBLE]: EggTier.COMMON, + [Species.SKWOVET]: EggTier.COMMON, + [Species.ROOKIDEE]: EggTier.COMMON, + [Species.BLIPBUG]: EggTier.COMMON, + [Species.NICKIT]: EggTier.COMMON, + [Species.GOSSIFLEUR]: EggTier.COMMON, + [Species.WOOLOO]: EggTier.COMMON, + [Species.CHEWTLE]: EggTier.COMMON, + [Species.YAMPER]: EggTier.COMMON, + [Species.ROLYCOLY]: EggTier.COMMON, + [Species.APPLIN]: EggTier.COMMON, + [Species.SILICOBRA]: EggTier.COMMON, + [Species.CRAMORANT]: EggTier.COMMON, + [Species.ARROKUDA]: EggTier.COMMON, + [Species.TOXEL]: EggTier.COMMON, + [Species.SIZZLIPEDE]: EggTier.COMMON, + [Species.CLOBBOPUS]: EggTier.COMMON, + [Species.SINISTEA]: EggTier.COMMON, + [Species.HATENNA]: EggTier.COMMON, + [Species.IMPIDIMP]: EggTier.COMMON, + [Species.MILCERY]: EggTier.COMMON, + [Species.FALINKS]: EggTier.RARE, + [Species.PINCURCHIN]: EggTier.COMMON, + [Species.SNOM]: EggTier.COMMON, + [Species.STONJOURNER]: EggTier.COMMON, + [Species.EISCUE]: EggTier.COMMON, + [Species.INDEEDEE]: EggTier.RARE, + [Species.MORPEKO]: EggTier.COMMON, + [Species.CUFANT]: EggTier.COMMON, + [Species.DRACOZOLT]: EggTier.RARE, + [Species.ARCTOZOLT]: EggTier.RARE, + [Species.DRACOVISH]: EggTier.RARE, + [Species.ARCTOVISH]: EggTier.RARE, + [Species.DURALUDON]: EggTier.RARE, + [Species.DREEPY]: EggTier.RARE, + [Species.ZACIAN]: EggTier.LEGENDARY, + [Species.ZAMAZENTA]: EggTier.LEGENDARY, + [Species.ETERNATUS]: EggTier.COMMON, + [Species.KUBFU]: EggTier.EPIC, + [Species.ZARUDE]: EggTier.EPIC, + [Species.REGIELEKI]: EggTier.EPIC, + [Species.REGIDRAGO]: EggTier.EPIC, + [Species.GLASTRIER]: EggTier.EPIC, + [Species.SPECTRIER]: EggTier.EPIC, + [Species.CALYREX]: EggTier.LEGENDARY, + [Species.GALAR_MEOWTH]: EggTier.COMMON, + [Species.GALAR_PONYTA]: EggTier.COMMON, + [Species.GALAR_SLOWPOKE]: EggTier.COMMON, + [Species.GALAR_FARFETCHD]: EggTier.COMMON, + [Species.GALAR_CORSOLA]: EggTier.COMMON, + [Species.GALAR_ZIGZAGOON]: EggTier.COMMON, + [Species.GALAR_DARUMAKA]: EggTier.RARE, + [Species.GALAR_YAMASK]: EggTier.COMMON, + [Species.GALAR_STUNFISK]: EggTier.COMMON, + [Species.GALAR_MR_MIME]: EggTier.COMMON, + [Species.GALAR_ARTICUNO]: EggTier.EPIC, + [Species.GALAR_ZAPDOS]: EggTier.EPIC, + [Species.GALAR_MOLTRES]: EggTier.EPIC, + [Species.HISUI_GROWLITHE]: EggTier.RARE, + [Species.HISUI_VOLTORB]: EggTier.COMMON, + [Species.HISUI_QWILFISH]: EggTier.RARE, + [Species.HISUI_SNEASEL]: EggTier.RARE, + [Species.HISUI_ZORUA]: EggTier.COMMON, + [Species.ENAMORUS]: EggTier.EPIC, + + [Species.SPRIGATITO]: EggTier.RARE, + [Species.FUECOCO]: EggTier.RARE, + [Species.QUAXLY]: EggTier.RARE, + [Species.LECHONK]: EggTier.COMMON, + [Species.TAROUNTULA]: EggTier.COMMON, + [Species.NYMBLE]: EggTier.COMMON, + [Species.PAWMI]: EggTier.COMMON, + [Species.TANDEMAUS]: EggTier.RARE, + [Species.FIDOUGH]: EggTier.COMMON, + [Species.SMOLIV]: EggTier.COMMON, + [Species.SQUAWKABILLY]: EggTier.COMMON, + [Species.NACLI]: EggTier.RARE, + [Species.CHARCADET]: EggTier.RARE, + [Species.TADBULB]: EggTier.COMMON, + [Species.WATTREL]: EggTier.COMMON, + [Species.MASCHIFF]: EggTier.COMMON, + [Species.SHROODLE]: EggTier.COMMON, + [Species.BRAMBLIN]: EggTier.COMMON, + [Species.TOEDSCOOL]: EggTier.COMMON, + [Species.KLAWF]: EggTier.COMMON, + [Species.CAPSAKID]: EggTier.COMMON, + [Species.RELLOR]: EggTier.COMMON, + [Species.FLITTLE]: EggTier.COMMON, + [Species.TINKATINK]: EggTier.RARE, + [Species.WIGLETT]: EggTier.COMMON, + [Species.BOMBIRDIER]: EggTier.COMMON, + [Species.FINIZEN]: EggTier.COMMON, + [Species.VAROOM]: EggTier.RARE, + [Species.CYCLIZAR]: EggTier.RARE, + [Species.ORTHWORM]: EggTier.RARE, + [Species.GLIMMET]: EggTier.RARE, + [Species.GREAVARD]: EggTier.COMMON, + [Species.FLAMIGO]: EggTier.RARE, + [Species.CETODDLE]: EggTier.COMMON, + [Species.VELUZA]: EggTier.RARE, + [Species.DONDOZO]: EggTier.RARE, + [Species.TATSUGIRI]: EggTier.RARE, + [Species.GREAT_TUSK]: EggTier.EPIC, + [Species.SCREAM_TAIL]: EggTier.EPIC, + [Species.BRUTE_BONNET]: EggTier.EPIC, + [Species.FLUTTER_MANE]: EggTier.EPIC, + [Species.SLITHER_WING]: EggTier.EPIC, + [Species.SANDY_SHOCKS]: EggTier.EPIC, + [Species.IRON_TREADS]: EggTier.EPIC, + [Species.IRON_BUNDLE]: EggTier.EPIC, + [Species.IRON_HANDS]: EggTier.EPIC, + [Species.IRON_JUGULIS]: EggTier.EPIC, + [Species.IRON_MOTH]: EggTier.EPIC, + [Species.IRON_THORNS]: EggTier.EPIC, + [Species.FRIGIBAX]: EggTier.RARE, + [Species.GIMMIGHOUL]: EggTier.RARE, + [Species.WO_CHIEN]: EggTier.EPIC, + [Species.CHIEN_PAO]: EggTier.EPIC, + [Species.TING_LU]: EggTier.EPIC, + [Species.CHI_YU]: EggTier.EPIC, + [Species.ROARING_MOON]: EggTier.EPIC, + [Species.IRON_VALIANT]: EggTier.EPIC, + [Species.KORAIDON]: EggTier.LEGENDARY, + [Species.MIRAIDON]: EggTier.LEGENDARY, + [Species.WALKING_WAKE]: EggTier.EPIC, + [Species.IRON_LEAVES]: EggTier.EPIC, + [Species.POLTCHAGEIST]: EggTier.RARE, + [Species.OKIDOGI]: EggTier.EPIC, + [Species.MUNKIDORI]: EggTier.EPIC, + [Species.FEZANDIPITI]: EggTier.EPIC, + [Species.OGERPON]: EggTier.EPIC, + [Species.GOUGING_FIRE]: EggTier.EPIC, + [Species.RAGING_BOLT]: EggTier.EPIC, + [Species.IRON_BOULDER]: EggTier.EPIC, + [Species.IRON_CROWN]: EggTier.EPIC, + [Species.TERAPAGOS]: EggTier.LEGENDARY, + [Species.PECHARUNT]: EggTier.EPIC, + [Species.PALDEA_TAUROS]: EggTier.RARE, + [Species.PALDEA_WOOPER]: EggTier.COMMON, + [Species.BLOODMOON_URSALUNA]: EggTier.EPIC, +}; diff --git a/src/data/egg.ts b/src/data/egg.ts index 5fffe4fcece..c475fc729e6 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -11,6 +11,7 @@ import { EggTier } from "#enums/egg-type"; import { Species } from "#enums/species"; import { EggSourceType } from "#enums/egg-source-types"; import { MANAPHY_EGG_MANAPHY_RATE, SAME_SPECIES_EGG_HA_RATE, GACHA_EGG_HA_RATE, GACHA_DEFAULT_RARE_EGGMOVE_RATE, SAME_SPECIES_EGG_RARE_EGGMOVE_RATE, GACHA_MOVE_UP_RARE_EGGMOVE_RATE, GACHA_DEFAULT_SHINY_RATE, GACHA_SHINY_UP_SHINY_RATE, SAME_SPECIES_EGG_SHINY_RATE, EGG_PITY_LEGENDARY_THRESHOLD, EGG_PITY_EPIC_THRESHOLD, EGG_PITY_RARE_THRESHOLD, SHINY_VARIANT_CHANCE, SHINY_EPIC_CHANCE, GACHA_DEFAULT_COMMON_EGG_THRESHOLD, GACHA_DEFAULT_RARE_EGG_THRESHOLD, GACHA_DEFAULT_EPIC_EGG_THRESHOLD, GACHA_LEGENDARY_UP_THRESHOLD_OFFSET, HATCH_WAVES_MANAPHY_EGG, HATCH_WAVES_COMMON_EGG, HATCH_WAVES_RARE_EGG, HATCH_WAVES_EPIC_EGG, HATCH_WAVES_LEGENDARY_EGG } from "#app/data/balance/rates"; +import { speciesEggTiers } from "#app/data/balance/species-egg-tiers"; export const EGG_SEED = 1073741824; @@ -160,7 +161,7 @@ export class Egg { // Override egg tier and hatchwaves if species was given if (eggOptions?.species) { - this._tier = this.getEggTierFromSpeciesStarterValue(); + this._tier = this.getEggTier(); this._hatchWaves = eggOptions.hatchWaves ?? this.getEggTierDefaultHatchWaves(); } // If species has no variant, set variantTier to common. This needs to @@ -261,11 +262,11 @@ export class Egg { return "Manaphy"; } switch (this.tier) { - case EggTier.GREAT: + case EggTier.RARE: return i18next.t("egg:greatTier"); - case EggTier.ULTRA: + case EggTier.EPIC: return i18next.t("egg:ultraTier"); - case EggTier.MASTER: + case EggTier.LEGENDARY: return i18next.t("egg:masterTier"); default: return i18next.t("egg:defaultTier"); @@ -336,9 +337,9 @@ export class Egg { switch (eggTier ?? this._tier) { case EggTier.COMMON: return HATCH_WAVES_COMMON_EGG; - case EggTier.GREAT: + case EggTier.RARE: return HATCH_WAVES_RARE_EGG; - case EggTier.ULTRA: + case EggTier.EPIC: return HATCH_WAVES_EPIC_EGG; } return HATCH_WAVES_LEGENDARY_EGG; @@ -347,7 +348,7 @@ export class Egg { private rollEggTier(): EggTier { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; const tierValue = Utils.randInt(256); - return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.GREAT : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.ULTRA : EggTier.MASTER; + return tierValue >= GACHA_DEFAULT_COMMON_EGG_THRESHOLD + tierValueOffset ? EggTier.COMMON : tierValue >= GACHA_DEFAULT_RARE_EGG_THRESHOLD + tierValueOffset ? EggTier.RARE : tierValue >= GACHA_DEFAULT_EPIC_EGG_THRESHOLD + tierValueOffset ? EggTier.EPIC : EggTier.LEGENDARY; } private rollSpecies(scene: BattleScene): Species | null { @@ -367,7 +368,7 @@ export class Egg { */ const rand = (Utils.randSeedInt(MANAPHY_EGG_MANAPHY_RATE) !== 1); return rand ? Species.PHIONE : Species.MANAPHY; - } else if (this.tier === EggTier.MASTER + } else if (this.tier === EggTier.LEGENDARY && this._sourceType === EggSourceType.GACHA_LEGENDARY) { if (!Utils.randSeedInt(2)) { return getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp); @@ -378,15 +379,15 @@ export class Egg { let maxStarterValue: integer; switch (this.tier) { - case EggTier.GREAT: + case EggTier.RARE: minStarterValue = 4; maxStarterValue = 5; break; - case EggTier.ULTRA: + case EggTier.EPIC: minStarterValue = 6; maxStarterValue = 7; break; - case EggTier.MASTER: + case EggTier.LEGENDARY: minStarterValue = 8; maxStarterValue = 9; break; @@ -398,8 +399,8 @@ export class Egg { const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; - let speciesPool = Object.keys(speciesStarterCosts) - .filter(s => speciesStarterCosts[s] >= minStarterValue && speciesStarterCosts[s] <= maxStarterValue) + let speciesPool = Object.keys(speciesEggTiers) + .filter(s => speciesEggTiers[s] === this.tier) .map(s => parseInt(s) as Species) .filter(s => !pokemonPrevolutions.hasOwnProperty(s) && getPokemonSpecies(s).isObtainable() && ignoredSpecies.indexOf(s) === -1); @@ -430,7 +431,9 @@ export class Egg { let totalWeight = 0; const speciesWeights : number[] = []; for (const speciesId of speciesPool) { - let weight = Math.floor((((maxStarterValue - speciesStarterCosts[speciesId]) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); + // Accounts for species that have starter costs outside of the normal range for their EggTier + const speciesCostClamped = Phaser.Math.Clamp(speciesStarterCosts[speciesId], minStarterValue, maxStarterValue); + let weight = Math.floor((((maxStarterValue - speciesCostClamped) / ((maxStarterValue - minStarterValue) + 1)) * 1.5 + 1) * 100); const species = getPokemonSpecies(speciesId); if (species.isRegional()) { weight = Math.floor(weight / 2); @@ -498,16 +501,16 @@ export class Egg { private checkForPityTierOverrides(scene: BattleScene): void { const tierValueOffset = this._sourceType === EggSourceType.GACHA_LEGENDARY ? GACHA_LEGENDARY_UP_THRESHOLD_OFFSET : 0; - scene.gameData.eggPity[EggTier.GREAT] += 1; - scene.gameData.eggPity[EggTier.ULTRA] += 1; - scene.gameData.eggPity[EggTier.MASTER] += 1 + tierValueOffset; + scene.gameData.eggPity[EggTier.RARE] += 1; + scene.gameData.eggPity[EggTier.EPIC] += 1; + scene.gameData.eggPity[EggTier.LEGENDARY] += 1 + tierValueOffset; // These numbers are roughly the 80% mark. That is, 80% of the time you'll get an egg before this gets triggered. - if (scene.gameData.eggPity[EggTier.MASTER] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.MASTER; - } else if (scene.gameData.eggPity[EggTier.ULTRA] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.ULTRA; - } else if (scene.gameData.eggPity[EggTier.GREAT] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { - this._tier = EggTier.GREAT; + if (scene.gameData.eggPity[EggTier.LEGENDARY] >= EGG_PITY_LEGENDARY_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.LEGENDARY; + } else if (scene.gameData.eggPity[EggTier.EPIC] >= EGG_PITY_EPIC_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.EPIC; + } else if (scene.gameData.eggPity[EggTier.RARE] >= EGG_PITY_RARE_THRESHOLD && this._tier === EggTier.COMMON) { + this._tier = EggTier.RARE; } scene.gameData.eggPity[this._tier] = 0; } @@ -516,38 +519,24 @@ export class Egg { scene.gameData.gameStats.eggsPulled++; if (this.isManaphyEgg()) { scene.gameData.gameStats.manaphyEggsPulled++; - this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.ULTRA); + this._hatchWaves = this.getEggTierDefaultHatchWaves(EggTier.EPIC); return; } switch (this.tier) { - case EggTier.GREAT: + case EggTier.RARE: scene.gameData.gameStats.rareEggsPulled++; break; - case EggTier.ULTRA: + case EggTier.EPIC: scene.gameData.gameStats.epicEggsPulled++; break; - case EggTier.MASTER: + case EggTier.LEGENDARY: scene.gameData.gameStats.legendaryEggsPulled++; break; } } - private getEggTierFromSpeciesStarterValue(): EggTier { - const speciesStartValue = speciesStarterCosts[this.species]; - if (speciesStartValue >= 1 && speciesStartValue <= 3) { - return EggTier.COMMON; - } - if (speciesStartValue >= 4 && speciesStartValue <= 5) { - return EggTier.GREAT; - } - if (speciesStartValue >= 6 && speciesStartValue <= 7) { - return EggTier.ULTRA; - } - if (speciesStartValue >= 8) { - return EggTier.MASTER; - } - - return EggTier.COMMON; + private getEggTier(): EggTier { + return speciesEggTiers[this.species]; } //// @@ -556,8 +545,8 @@ export class Egg { } export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timestamp: number): Species { - const legendarySpecies = Object.entries(speciesStarterCosts) - .filter(s => s[1] >= 8 && s[1] <= 9) + const legendarySpecies = Object.entries(speciesEggTiers) + .filter(s => s[1] === EggTier.LEGENDARY) .map(s => parseInt(s[0])) .filter(s => getPokemonSpecies(s).isObtainable()); @@ -579,17 +568,9 @@ export function getLegendaryGachaSpeciesForTimestamp(scene: BattleScene, timesta /** * Check for a given species EggTier Value - * @param species - Species for wich we will check the egg tier it belongs to + * @param pokemonSpecies - Species for wich we will check the egg tier it belongs to * @returns The egg tier of a given pokemon species */ export function getEggTierForSpecies(pokemonSpecies :PokemonSpecies): EggTier { - const speciesBaseValue = speciesStarterCosts[pokemonSpecies.getRootSpeciesId()]; - if (speciesBaseValue <= 3) { - return EggTier.COMMON; - } else if (speciesBaseValue <= 5) { - return EggTier.GREAT; - } else if (speciesBaseValue <= 7) { - return EggTier.ULTRA; - } - return EggTier.MASTER; + return speciesEggTiers[pokemonSpecies.getRootSpeciesId()]; } 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 f3b886ac0ac..4f3420f5194 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -150,7 +150,7 @@ export const ATrainersTestEncounter: MysteryEncounter = pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, - tier: EggTier.ULTRA + tier: EggTier.EPIC }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.epic`)); setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [ modifierTypes.SACRED_ASH ], guaranteedModifierTiers: [ ModifierTier.ROGUE, ModifierTier.ULTRA ], fillRemaining: true }, [ eggOptions ]); @@ -172,7 +172,7 @@ export const ATrainersTestEncounter: MysteryEncounter = pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: encounter.misc.trainerEggDescription, - tier: EggTier.GREAT + tier: EggTier.RARE }; encounter.setDialogueToken("eggType", i18next.t(`${namespace}:eggTypes.rare`)); setEncounterRewards(scene, { fillRemaining: false, rerollMultiplier: -1 }, [ eggOptions ]); 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 4515736b30a..0ac82243862 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 @@ -494,7 +494,7 @@ function getEggOptions(scene: BattleScene, commonEggs: number, rareEggs: number) pulled: false, sourceType: EggSourceType.EVENT, eggDescriptor: eggDescription, - tier: EggTier.GREAT + tier: EggTier.RARE }); } } diff --git a/src/enums/egg-type.ts b/src/enums/egg-type.ts index d8d0facb020..901e60b3c76 100644 --- a/src/enums/egg-type.ts +++ b/src/enums/egg-type.ts @@ -1,6 +1,6 @@ export enum EggTier { COMMON, - GREAT, - ULTRA, - MASTER + RARE, + EPIC, + LEGENDARY } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 4d85d5b8e1e..d8fcc281d1b 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -983,7 +983,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, s, statHolder); } - statHolder.value = Utils.clampInt(statHolder.value, 1, Number.MAX_SAFE_INTEGER); + statHolder.value = Phaser.Math.Clamp(statHolder.value, 1, Number.MAX_SAFE_INTEGER); this.setStat(s, statHolder.value); } diff --git a/src/test/eggs/egg.test.ts b/src/test/eggs/egg.test.ts index cf53cca5af8..6f57af63e6b 100644 --- a/src/test/eggs/egg.test.ts +++ b/src/test/eggs/egg.test.ts @@ -55,7 +55,7 @@ describe("Egg Generation Tests", () => { let gachaSpeciesCount = 0; for (let i = 0; i < EGG_HATCH_COUNT; i++) { - const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.MASTER }).generatePlayerPokemon(scene).species.speciesId; + const result = new Egg({ scene, timestamp, sourceType: EggSourceType.GACHA_LEGENDARY, tier: EggTier.LEGENDARY }).generatePlayerPokemon(scene).species.speciesId; if (result === expectedSpecies) { gachaSpeciesCount++; } @@ -82,7 +82,7 @@ describe("Egg Generation Tests", () => { }); it("should return an rare tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.GREAT; + const expectedTier = EggTier.RARE; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -90,7 +90,7 @@ describe("Egg Generation Tests", () => { }); it("should return an epic tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.ULTRA; + const expectedTier = EggTier.EPIC; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -98,7 +98,7 @@ describe("Egg Generation Tests", () => { }); it("should return an legendary tier egg", () => { const scene = game.scene; - const expectedTier = EggTier.MASTER; + const expectedTier = EggTier.LEGENDARY; const result = new Egg({ scene, tier: expectedTier }).tier; @@ -200,7 +200,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedEggTier = EggTier.COMMON; - const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).tier; + const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).tier; expect(result).toBe(expectedEggTier); }); @@ -208,7 +208,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const expectedHatchWaves = 10; - const result = new Egg({ scene, tier: EggTier.MASTER, species: Species.BULBASAUR }).hatchWaves; + const result = new Egg({ scene, tier: EggTier.LEGENDARY, species: Species.BULBASAUR }).hatchWaves; expect(result).toBe(expectedHatchWaves); }); @@ -229,7 +229,7 @@ describe("Egg Generation Tests", () => { const result = new EggData(legacyEgg).toEgg(); - expect(result.tier).toBe(EggTier.GREAT); + expect(result.tier).toBe(EggTier.RARE); expect(result.id).toBe(legacyEgg.id); expect(result.timestamp).toBe(legacyEgg.timestamp); expect(result.hatchWaves).toBe(legacyEgg.hatchWaves); @@ -241,9 +241,9 @@ describe("Egg Generation Tests", () => { new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.COMMON }); - expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); - expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); - expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 1); + expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1); + expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1); + expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 1); }); it("should increase legendary egg pity by two", () => { const scene = game.scene; @@ -251,9 +251,9 @@ describe("Egg Generation Tests", () => { new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true, tier: EggTier.COMMON }); - expect(scene.gameData.eggPity[EggTier.GREAT]).toBe(startPityValues[EggTier.GREAT] + 1); - expect(scene.gameData.eggPity[EggTier.ULTRA]).toBe(startPityValues[EggTier.ULTRA] + 1); - expect(scene.gameData.eggPity[EggTier.MASTER]).toBe(startPityValues[EggTier.MASTER] + 2); + expect(scene.gameData.eggPity[EggTier.RARE]).toBe(startPityValues[EggTier.RARE] + 1); + expect(scene.gameData.eggPity[EggTier.EPIC]).toBe(startPityValues[EggTier.EPIC] + 1); + expect(scene.gameData.eggPity[EggTier.LEGENDARY]).toBe(startPityValues[EggTier.LEGENDARY] + 2); }); it("should not increase manaphy egg count if bulbasaurs are pulled", () => { const scene = game.scene; @@ -277,7 +277,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingRareEggsPulled = scene.gameData.gameStats.rareEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.GREAT }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.RARE }); expect(scene.gameData.gameStats.rareEggsPulled).toBe(startingRareEggsPulled + 1); }); @@ -285,7 +285,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingEpicEggsPulled = scene.gameData.gameStats.epicEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.ULTRA }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.EPIC }); expect(scene.gameData.gameStats.epicEggsPulled).toBe(startingEpicEggsPulled + 1); }); @@ -293,7 +293,7 @@ describe("Egg Generation Tests", () => { const scene = game.scene; const startingLegendaryEggsPulled = scene.gameData.gameStats.legendaryEggsPulled; - new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.MASTER }); + new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true, tier: EggTier.LEGENDARY }); expect(scene.gameData.gameStats.legendaryEggsPulled).toBe(startingLegendaryEggsPulled + 1); }); @@ -301,8 +301,8 @@ describe("Egg Generation Tests", () => { vi.spyOn(Utils, "randInt").mockReturnValue(1); const scene = game.scene; - const expectedTier1 = EggTier.MASTER; - const expectedTier2 = EggTier.ULTRA; + const expectedTier1 = EggTier.LEGENDARY; + const expectedTier2 = EggTier.EPIC; const result1 = new Egg({ scene, sourceType: EggSourceType.GACHA_LEGENDARY, pulled: true }).tier; const result2 = new Egg({ scene, sourceType: EggSourceType.GACHA_MOVE, pulled: true }).tier; diff --git a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts index b1aa378d82a..7d783958422 100644 --- a/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/a-trainers-test-encounter.test.ts @@ -128,7 +128,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + 1).toBe(eggsAfter.length); const eggTier = eggsAfter[eggsAfter.length - 1].tier; - expect(eggTier === EggTier.ULTRA || eggTier === EggTier.MASTER).toBeTruthy(); + expect(eggTier === EggTier.EPIC || eggTier === EggTier.LEGENDARY).toBeTruthy(); }); }); @@ -176,7 +176,7 @@ describe("A Trainer's Test - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + 1).toBe(eggsAfter.length); const eggTier = eggsAfter[eggsAfter.length - 1].tier; - expect(eggTier).toBe(EggTier.GREAT); + expect(eggTier).toBe(EggTier.RARE); }); it("should leave encounter without battle", async () => { 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 7e445ac1fe2..bbb4f249feb 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 @@ -155,7 +155,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); @@ -213,7 +213,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); @@ -271,7 +271,7 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { expect(eggsAfter).toBeDefined(); expect(eggsBeforeLength + commonEggs + rareEggs).toBe(eggsAfter.length); expect(eggsAfter.filter(egg => egg.tier === EggTier.COMMON).length).toBe(commonEggs); - expect(eggsAfter.filter(egg => egg.tier === EggTier.GREAT).length).toBe(rareEggs); + expect(eggsAfter.filter(egg => egg.tier === EggTier.RARE).length).toBe(rareEggs); game.phaseInterceptor.superEndPhase(); await game.phaseInterceptor.to(PostMysteryEncounterPhase); diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 79b51ba6c44..1d97998f491 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -593,7 +593,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { }; const updatePokemonHp = () => { - let duration = !instant ? Utils.clampInt(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; + let duration = !instant ? Phaser.Math.Clamp(Math.abs((this.lastHp) - pokemon.hp) * 5, 250, 5000) : 0; const speed = (this.scene as BattleScene).hpBarSpeed; if (speed) { duration = speed >= 3 ? 0 : duration / Math.pow(2, speed); diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 366f1604740..3aa009b1b31 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -471,9 +471,9 @@ export default class EggGachaUiHandler extends MessageUiHandler { getGuaranteedEggTierFromPullCount(pullCount: number): EggTier { switch (pullCount) { case 10: - return EggTier.GREAT; + return EggTier.RARE; case 25: - return EggTier.ULTRA; + return EggTier.EPIC; default: return EggTier.COMMON; } @@ -516,7 +516,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { const eggText = addTextObject(this.scene, 0, 14, egg.getEggDescriptor(), TextStyle.PARTY, { align: "center" }); eggText.setOrigin(0.5, 0); - eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.ULTRA)); + eggText.setTint(getEggTierTextTint(!egg.isManaphyEgg() ? egg.tier : EggTier.EPIC)); ret.add(eggText); this.eggGachaSummaryContainer.addAt(ret, 0); diff --git a/src/ui/text.ts b/src/ui/text.ts index e6e1978118b..22dd3f4cd6a 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -356,11 +356,11 @@ export function getEggTierTextTint(tier: EggTier): integer { switch (tier) { case EggTier.COMMON: return getModifierTierTextTint(ModifierTier.COMMON); - case EggTier.GREAT: + case EggTier.RARE: return getModifierTierTextTint(ModifierTier.GREAT); - case EggTier.ULTRA: + case EggTier.EPIC: return getModifierTierTextTint(ModifierTier.ULTRA); - case EggTier.MASTER: + case EggTier.LEGENDARY: return getModifierTierTextTint(ModifierTier.MASTER); } } diff --git a/src/utils.ts b/src/utils.ts index 9cc95b00826..c2ee7100909 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,10 +38,6 @@ export function shiftCharCodes(str: string, shiftCount: integer) { return newStr; } -export function clampInt(value: integer, min: integer, max: integer): integer { - return Math.min(Math.max(value, min), max); -} - export function randGauss(stdev: number, mean: number = 0): number { if (!stdev) { return 0; From 5d0b36132061bae767dafdd3f27c6c1be12264df Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:19:05 -0700 Subject: [PATCH 26/75] [P2] Syrup Bomb effect is removed when user leaves the field (#4606) * Syrup Bomb's effect expires when the move user leaves the field * Add test * Remove check for the affected pokemon being switched out --- src/data/battler-tags.ts | 29 +++++++++++++---------------- src/test/moves/syrup_bomb.test.ts | 26 ++++++++++++++++++++------ 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 24c82e54427..3cc109df264 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2640,16 +2640,16 @@ export class ImprisonTag extends MoveRestrictionBattlerTag { /** * Battler Tag that applies the effects of Syrup Bomb to the target Pokemon. * For three turns, starting from the turn of hit, at the end of each turn, the target Pokemon's speed will decrease by 1. - * The tag can also expire by taking the target Pokemon off the field. + * The tag can also expire by taking the target Pokemon off the field, or the Pokemon that originally used the move. */ export class SyrupBombTag extends BattlerTag { - constructor() { - super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB); + constructor(sourceId: number) { + super(BattlerTagType.SYRUP_BOMB, BattlerTagLapseType.TURN_END, 3, Moves.SYRUP_BOMB, sourceId); } /** * Adds the Syrup Bomb battler tag to the target Pokemon. - * @param {Pokemon} pokemon the target Pokemon + * @param pokemon - The target {@linkcode Pokemon} */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); @@ -2658,15 +2658,16 @@ export class SyrupBombTag extends BattlerTag { /** * Applies the single-stage speed down to the target Pokemon and decrements the tag's turn count - * @param {Pokemon} pokemon the target Pokemon - * @param {BattlerTagLapseType} _lapseType - * @returns `true` if the turnCount is still greater than 0 | `false` if the turnCount is 0 or the target Pokemon has been removed from the field + * @param pokemon - The target {@linkcode Pokemon} + * @param _lapseType - N/A + * @returns `true` if the `turnCount` is still greater than `0`; `false` if the `turnCount` is `0` or the target or source Pokemon has been removed from the field */ override lapse(pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - if (!pokemon.isActive(true)) { + if (this.sourceId && !pokemon.scene.getPokemonById(this.sourceId)?.isActive(true)) { return false; } - pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); // Custom message in lieu of an animation in mainline + // Custom message in lieu of an animation in mainline + pokemon.scene.queueMessage(i18next.t("battlerTags:syrupBombLapse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); pokemon.scene.unshiftPhase(new StatStageChangePhase( pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPD ], -1, true, false, true @@ -2677,12 +2678,8 @@ export class SyrupBombTag extends BattlerTag { /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. - * - * @param {BattlerTagType} tagType the type of the {@linkcode BattlerTagType}. - * @param turnCount the turn count. - * @param {Moves} sourceMove the source {@linkcode Moves}. - * @param sourceId the source ID. - * @returns {BattlerTag} the corresponding {@linkcode BattlerTag} object. + * @param sourceId - The ID of the pokemon adding the tag + * @returns The corresponding {@linkcode BattlerTag} object. */ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { switch (tagType) { @@ -2851,7 +2848,7 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source case BattlerTagType.IMPRISON: return new ImprisonTag(sourceId); case BattlerTagType.SYRUP_BOMB: - return new SyrupBombTag(); + return new SyrupBombTag(sourceId); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/test/moves/syrup_bomb.test.ts b/src/test/moves/syrup_bomb.test.ts index 7f914e45cc6..ea2f8b6bab3 100644 --- a/src/test/moves/syrup_bomb.test.ts +++ b/src/test/moves/syrup_bomb.test.ts @@ -1,4 +1,3 @@ -import { allMoves } from "#app/data/move"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { Abilities } from "#enums/abilities"; @@ -7,7 +6,7 @@ import { Stat } from "#enums/stat"; import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { BattlerIndex } from "#app/battle"; -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; describe("Moves - SYRUP BOMB", () => { let phaserGame: Phaser.Game; @@ -26,20 +25,21 @@ describe("Moves - SYRUP BOMB", () => { beforeEach(() => { game = new GameManager(phaserGame); game.override - .starterSpecies(Species.MAGIKARP) + .battleType("single") .enemySpecies(Species.SNORLAX) + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH) .startingLevel(30) .enemyLevel(100) .moveset([ Moves.SYRUP_BOMB, Moves.SPLASH ]) .enemyMoveset(Moves.SPLASH); - vi.spyOn(allMoves[Moves.SYRUP_BOMB], "accuracy", "get").mockReturnValue(100); }); //Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/syrup_bomb_(move) it("decreases the target Pokemon's speed stat once per turn for 3 turns", async () => { - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); const targetPokemon = game.scene.getEnemyPokemon()!; expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); @@ -66,7 +66,7 @@ describe("Moves - SYRUP BOMB", () => { it("does not affect Pokemon with the ability Bulletproof", async () => { game.override.enemyAbility(Abilities.BULLETPROOF); - await game.startBattle([ Species.MAGIKARP ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); const targetPokemon = game.scene.getEnemyPokemon()!; @@ -79,4 +79,18 @@ describe("Moves - SYRUP BOMB", () => { expect(targetPokemon.getStatStage(Stat.SPD)).toBe(0); } ); + + it("stops lowering the target's speed if the user leaves the field", async () => { + await game.classicMode.startBattle([ Species.FEEBAS, Species.MILOTIC ]); + + game.move.select(Moves.SYRUP_BOMB); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.ENEMY ]); + await game.move.forceHit(); + await game.toNextTurn(); + + game.doSwitchPokemon(1); + await game.toNextTurn(); + + expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPD)).toBe(-1); + }); }); From 3f63c147a38ef9afb05c54bfe0983ed572d6f35b Mon Sep 17 00:00:00 2001 From: "Amani H." <109637146+xsn34kzx@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:44:51 -0400 Subject: [PATCH 27/75] [P3] Fix "Stat Won't Go Any Lower/Higher" Not Appearing (#4635) --- src/enums/stat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/enums/stat.ts b/src/enums/stat.ts index a12d53e8559..6b3f7dc6d79 100644 --- a/src/enums/stat.ts +++ b/src/enums/stat.ts @@ -50,7 +50,7 @@ export function getStatStageChangeDescriptionKey(stages: number, isIncrease: boo return isIncrease ? "battle:statRose" : "battle:statFell"; } else if (stages === 2) { return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell"; - } else if (stages <= 6) { + } else if (stages > 2 && stages <= 6) { return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell"; } return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower"; From 407cd65dcbe7ba343b2f01e17ed67750c888e218 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Fri, 11 Oct 2024 00:20:28 -0700 Subject: [PATCH 28/75] [Misc] Enemy item override will now apply to all enemies (#4620) * Enemy item override will now apply to all enemies * Update tsdocs --- src/battle-scene.ts | 9 +++++---- src/modifier/modifier.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a586b565e13..40e3971b7fc 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2676,7 +2676,7 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy of PersistentModifier type + * Removes all modifiers from enemy pokemon of {@linkcode PersistentModifier} type */ clearEnemyModifiers(): void { const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PersistentModifier); @@ -2687,10 +2687,11 @@ export default class BattleScene extends SceneBase { } /** - * Removes all modifiers from enemy of PokemonHeldItemModifier type + * Removes all modifiers from enemy pokemon of {@linkcode PokemonHeldItemModifier} type + * @param pokemon - If specified, only removes held items from that {@linkcode Pokemon} */ - clearEnemyHeldItemModifiers(): void { - const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier); + clearEnemyHeldItemModifiers(pokemon?: Pokemon): void { + const modifiersToRemove = this.enemyModifiers.filter(m => m instanceof PokemonHeldItemModifier && (!pokemon || m.getPokemon(this) === pokemon)); for (const m of modifiersToRemove) { this.enemyModifiers.splice(this.enemyModifiers.indexOf(m), 1); } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 6c9b5db1bca..dd8c82357a7 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3635,7 +3635,7 @@ export function overrideHeldItems(scene: BattleScene, pokemon: Pokemon, isPlayer } if (!isPlayer) { - scene.clearEnemyHeldItemModifiers(); + scene.clearEnemyHeldItemModifiers(pokemon); } heldItemsOverride.forEach(item => { From 80a784ac8d83c90af4ef16490346f24380da5f86 Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:24:53 +0200 Subject: [PATCH 29/75] [P3 Beta][UI] Fix loading behavior introduced with save preview (#4633) * [ui] partially revert loading behavior introduced with save preview * [beta][ui] fix scrolling issue in Load Game menu --- src/ui/save-slot-select-ui-handler.ts | 65 ++++++++++++++++----------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index bd1a7dd9ac4..2e664db8d43 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -12,7 +12,8 @@ import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; import { RunDisplayMode } from "#app/ui/run-info-ui-handler"; -const sessionSlotCount = 5; +const SESSION_SLOTS_COUNT = 5; +const SLOTS_ON_SCREEN = 3; export enum SaveSlotUiMode { LOAD, @@ -84,12 +85,10 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { this.saveSlotSelectCallback = args[1] as SaveSlotSelectCallback; this.saveSlotSelectContainer.setVisible(true); - this.populateSessionSlots() - .then(() => { - this.setScrollCursor(0); - this.setCursor(0); - }); + this.populateSessionSlots(); + this.setScrollCursor(0); + this.setCursor(0); return true; } @@ -161,9 +160,9 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } break; case Button.DOWN: - if (this.cursor < 2) { - success = this.setCursor(this.cursor + 1, this.cursor); - } else if (this.scrollCursor < sessionSlotCount - 3) { + if (this.cursor < (SLOTS_ON_SCREEN - 1)) { + success = this.setCursor(this.cursor + 1, cursorPosition); + } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); } break; @@ -184,13 +183,19 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { return success || error; } - async populateSessionSlots() { - for (let s = 0; s < sessionSlotCount; s++) { + populateSessionSlots() { + for (let s = 0; s < SESSION_SLOTS_COUNT; s++) { const sessionSlot = new SessionSlot(this.scene, s); - await sessionSlot.load(); this.scene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); + sessionSlot.load().then((success) => { + // If the cursor was moved to this slot while the session was loading + // call setCursor again to shift the slot position and show the arrow for save preview + if (success && (this.cursor + this.scrollCursor) === s) { + this.setCursor(s); + } + }); } } @@ -209,12 +214,12 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } /** - * setCursor takes user navigation as an input and positions the cursor accordingly - * @param cursor the index provided to the cursor - * @param prevCursor the previous index occupied by the cursor - optional + * Move the cursor to a new position and update the view accordingly + * @param cursor the new cursor position, between `0` and `SLOTS_ON_SCREEN - 1` + * @param prevSlotIndex index of the previous session occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT - 1` - optional * @returns `true` if the cursor position has changed | `false` if it has not */ - override setCursor(cursor: integer, prevCursor?: integer): boolean { + override setCursor(cursor: integer, prevSlotIndex?: integer): boolean { const changed = super.setCursor(cursor); if (!this.cursorObj) { @@ -241,21 +246,20 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } this.setArrowVisibility(hasData); } - if (!Utils.isNullOrUndefined(prevCursor)) { - this.revertSessionSlot(prevCursor); + if (!Utils.isNullOrUndefined(prevSlotIndex)) { + this.revertSessionSlot(prevSlotIndex); } return changed; } /** - * Helper function that resets the session slot position to its default central position - * @param prevCursor the previous location of the cursor + * Helper function that resets the given session slot to its default central position */ - revertSessionSlot(prevCursor: integer): void { - const sessionSlot = this.sessionSlots[prevCursor]; + revertSessionSlot(slotIndex: integer): void { + const sessionSlot = this.sessionSlots[slotIndex]; if (sessionSlot) { - sessionSlot.setPosition(0, prevCursor * 56); + sessionSlot.setPosition(0, slotIndex * 56); } } @@ -270,12 +274,18 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } } - setScrollCursor(scrollCursor: integer, priorCursor?: integer): boolean { + /** + * Move the scrolling cursor to a new position and update the view accordingly + * @param scrollCursor the new cursor position, between `0` and `SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN` + * @param prevSlotIndex index of the previous slot occupied by the cursor, between `0` and `SESSION_SLOTS_COUNT-1` - optional + * @returns `true` if the cursor position has changed | `false` if it has not + */ + setScrollCursor(scrollCursor: integer, prevSlotIndex?: integer): boolean { const changed = scrollCursor !== this.scrollCursor; if (changed) { this.scrollCursor = scrollCursor; - this.setCursor(this.cursor, priorCursor); + this.setCursor(this.cursor, prevSlotIndex); this.scene.tweens.add({ targets: this.sessionSlotsContainer, y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, @@ -290,6 +300,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { clear() { super.clear(); this.saveSlotSelectContainer.setVisible(false); + this.setScrollCursor(0); this.eraseCursor(); this.saveSlotSelectCallback = null; this.clearSessionSlots(); @@ -391,6 +402,10 @@ class SessionSlot extends Phaser.GameObjects.Container { load(): Promise { return new Promise(resolve => { this.scene.gameData.getSession(this.slotId).then(async sessionData => { + // Ignore the results if the view was exited + if (!this.active) { + return; + } if (!sessionData) { this.hasData = false; this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty")); From 4f456339f45038cd8df5b9c788251aa6c66bdde1 Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Fri, 11 Oct 2024 16:26:47 +0200 Subject: [PATCH 30/75] [UI] Remove score display in voucher menu (#4616) --- src/ui/achvs-ui-handler.ts | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index f90732d1fae..4e0e2feea81 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -40,7 +40,9 @@ export default class AchvsUiHandler extends MessageUiHandler { private iconsBg: Phaser.GameObjects.NineSlice; private icons: Phaser.GameObjects.Sprite[]; + private titleBg: Phaser.GameObjects.NineSlice; private titleText: Phaser.GameObjects.Text; + private scoreContainer: Phaser.GameObjects.Container; private scoreText: Phaser.GameObjects.Text; private unlockText: Phaser.GameObjects.Text; @@ -114,29 +116,31 @@ export default class AchvsUiHandler extends MessageUiHandler { const titleBg = addWindow(this.scene, 0, this.headerBg.height + this.iconsBg.height, 174, 24); titleBg.setOrigin(0, 0); + this.titleBg = titleBg; this.titleText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); const textSize = languageSettings[i18next.language]?.TextSize ?? this.titleText.style.fontSize; this.titleText.setFontSize(textSize); - this.titleText.setOrigin(0, 0); const titleBgCenterX = titleBg.x + titleBg.width / 2; const titleBgCenterY = titleBg.y + titleBg.height / 2; this.titleText.setOrigin(0.5, 0.5); this.titleText.setPosition(titleBgCenterX, titleBgCenterY); - const scoreBg = addWindow(this.scene, titleBg.x + titleBg.width, titleBg.y, 46, 24); + this.scoreContainer = this.scene.add.container(titleBg.x + titleBg.width, titleBg.y); + const scoreBg = addWindow(this.scene, 0, 0, 46, 24); scoreBg.setOrigin(0, 0); + this.scoreContainer.add(scoreBg); - this.scoreText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); - this.scoreText.setOrigin(0, 0); - this.scoreText.setPositionRelative(scoreBg, 8, 4); + this.scoreText = addTextObject(this.scene, scoreBg.width / 2, scoreBg.height / 2, "", TextStyle.WINDOW); + this.scoreText.setOrigin(0.5, 0.5); + this.scoreContainer.add(this.scoreText); - const unlockBg = addWindow(this.scene, scoreBg.x + scoreBg.width, scoreBg.y, 98, 24); + const unlockBg = addWindow(this.scene, this.scoreContainer.x + scoreBg.width, titleBg.y, 98, 24); unlockBg.setOrigin(0, 0); this.unlockText = addTextObject(this.scene, 0, 0, "", TextStyle.WINDOW); - this.unlockText.setOrigin(0, 0); - this.unlockText.setPositionRelative(unlockBg, 8, 4); + this.unlockText.setOrigin(0.5, 0.5); + this.unlockText.setPositionRelative(unlockBg, unlockBg.width / 2, unlockBg.height / 2); const descriptionBg = addWindow(this.scene, 0, titleBg.y + titleBg.height, (this.scene.game.canvas.width / 6) - 2, 42); descriptionBg.setOrigin(0, 0); @@ -157,8 +161,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.mainContainer.add(this.iconsContainer); this.mainContainer.add(titleBg); this.mainContainer.add(this.titleText); - this.mainContainer.add(scoreBg); - this.mainContainer.add(this.scoreText); + this.mainContainer.add(this.scoreContainer); this.mainContainer.add(unlockBg); this.mainContainer.add(this.unlockText); this.mainContainer.add(descriptionBg); @@ -167,8 +170,6 @@ export default class AchvsUiHandler extends MessageUiHandler { ui.add(this.mainContainer); this.currentPage = Page.ACHIEVEMENTS; - this.setCursor(0); - this.setScrollCursor(0); this.mainContainer.setVisible(false); } @@ -316,9 +317,19 @@ export default class AchvsUiHandler extends MessageUiHandler { if (update || pageChange) { switch (this.currentPage) { case Page.ACHIEVEMENTS: + if (pageChange) { + this.titleBg.width = 174; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(true); + } this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); break; case Page.VOUCHERS: + if (pageChange) { + this.titleBg.width = 220; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(false); + } this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); break; } @@ -442,6 +453,7 @@ export default class AchvsUiHandler extends MessageUiHandler { this.currentPage = Page.ACHIEVEMENTS; this.mainContainer.setVisible(false); this.setScrollCursor(0); + this.setCursor(0, true); this.eraseCursor(); } From 70b9a43c8b72a555ae4e5379f07d0c4a480ca4e2 Mon Sep 17 00:00:00 2001 From: Mason S <132116525+ElizaAlex@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:41:54 -0400 Subject: [PATCH 31/75] [P2] Fix first-turn status damage and arena hazards (#3528) * [Bug] Toxic Spikes implementation issues fixed Adjusted MoveEffectPhase.start() so that ENEMY_SIDE targeted moves no longer occur twice per use in double battles. Updated Toxic Orb test to no longer expect a tick of damage turn 1. Fixed Toxic/Poison dealing damage immediately when applied. Fixed Hazards not persisting through save Added unit tests Fixed flyout not displaying correct number of Spikes/Toxic Spikes after a refresh * Update Toxic Orb test * Updates Toxic Spikes tests * Apply suggestions from code review * Fix merge issues Replace `integer` with `number` in `arena-tag.ts` * Remove partial Magic Bounce implementation * Remove stray newline * Remove extra change in safeguard test --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/arena-tag.ts | 122 ++++++++++++-------- src/phases/check-status-effect-phase.ts | 23 ++++ src/phases/obtain-status-effect-phase.ts | 4 - src/phases/turn-start-phase.ts | 9 +- src/system/arena-data.ts | 8 +- src/system/game-data.ts | 17 ++- src/test/items/toxic_orb.test.ts | 54 ++++----- src/test/moves/toxic_spikes.test.ts | 136 +++++++++++++++++++++++ 8 files changed, 282 insertions(+), 91 deletions(-) create mode 100644 src/phases/check-status-effect-phase.ts create mode 100644 src/test/moves/toxic_spikes.test.ts diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 6407e139a71..45d64249296 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -28,20 +28,13 @@ export enum ArenaTagSide { } export abstract class ArenaTag { - public tagType: ArenaTagType; - public turnCount: integer; - public sourceMove?: Moves; - public sourceId?: integer; - public side: ArenaTagSide; - - - constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) { - this.tagType = tagType; - this.turnCount = turnCount; - this.sourceMove = sourceMove; - this.sourceId = sourceId; - this.side = side; - } + constructor( + public tagType: ArenaTagType, + public turnCount: number, + public sourceMove?: Moves, + public sourceId?: number, + public side: ArenaTagSide = ArenaTagSide.BOTH + ) {} apply(arena: Arena, args: any[]): boolean { return true; @@ -66,6 +59,18 @@ export abstract class ArenaTag { ? allMoves[this.sourceMove].name : null; } + + /** + * When given a arena tag or json representing one, load the data for it. + * This is meant to be inherited from by any arena tag with custom attributes + * @param {ArenaTag | any} source An arena tag + */ + loadTag(source : ArenaTag | any) : void { + this.turnCount = source.turnCount; + this.sourceMove = source.sourceMove; + this.sourceId = source.sourceId; + this.side = source.side; + } } /** @@ -73,7 +78,7 @@ export abstract class ArenaTag { * Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist. */ export class MistTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side); } @@ -117,7 +122,7 @@ export class WeakenMoveScreenTag extends ArenaTag { * @param side - The side (player or enemy) the tag affects. * @param weakenedCategories - The categories of moves that are weakened by this tag. */ - constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, weakenedCategories: MoveCategory[]) { + constructor(tagType: ArenaTagType, turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide, weakenedCategories: MoveCategory[]) { super(tagType, turnCount, sourceMove, sourceId, side); this.weakenedCategories = weakenedCategories; @@ -148,7 +153,7 @@ export class WeakenMoveScreenTag extends ArenaTag { * Used by {@linkcode Moves.REFLECT} */ class ReflectTag extends WeakenMoveScreenTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [ MoveCategory.PHYSICAL ]); } @@ -164,7 +169,7 @@ class ReflectTag extends WeakenMoveScreenTag { * Used by {@linkcode Moves.LIGHT_SCREEN} */ class LightScreenTag extends WeakenMoveScreenTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [ MoveCategory.SPECIAL ]); } @@ -180,7 +185,7 @@ class LightScreenTag extends WeakenMoveScreenTag { * Used by {@linkcode Moves.AURORA_VEIL} */ class AuroraVeilTag extends WeakenMoveScreenTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ]); } @@ -203,7 +208,7 @@ export class ConditionalProtectTag extends ArenaTag { /** Does this apply to all moves, including those that ignore other forms of protection? */ protected ignoresBypass: boolean; - constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) { + constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) { super(tagType, 1, sourceMove, sourceId, side); this.protectConditionFunc = condition; @@ -265,7 +270,7 @@ export class ConditionalProtectTag extends ArenaTag { */ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { const move = allMoves[moveId]; - const priority = new Utils.IntegerHolder(move.priority); + const priority = new Utils.NumberHolder(move.priority); const effectPhase = arena.scene.getCurrentPhase(); if (effectPhase instanceof MoveEffectPhase) { @@ -281,7 +286,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => { * Condition: The incoming move has increased priority. */ class QuickGuardTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc); } } @@ -312,7 +317,7 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean = * can be an ally or enemy. */ class WideGuardTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc); } } @@ -334,7 +339,7 @@ const MatBlockConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean => * Condition: The incoming move is a Physical or Special attack move. */ class MatBlockTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc); } @@ -372,7 +377,7 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (arena, moveId) => { * not target all Pokemon or sides of the field. */ class CraftyShieldTag extends ConditionalProtectTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true); } } @@ -384,12 +389,12 @@ class CraftyShieldTag extends ConditionalProtectTag { export class NoCritTag extends ArenaTag { /** * Constructor method for the NoCritTag class - * @param turnCount `integer` the number of turns this effect lasts + * @param turnCount `number` the number of turns this effect lasts * @param sourceMove {@linkcode Moves} the move that created this effect - * @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect + * @param sourceId `number` the ID of the {@linkcode Pokemon} that created this effect * @param side {@linkcode ArenaTagSide} the side to which this effect belongs */ - constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side); } @@ -419,7 +424,7 @@ class WishTag extends ArenaTag { private triggerMessage: string; private healHp: number; - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side); } @@ -460,7 +465,7 @@ export class WeakenMoveTypeTag extends ArenaTag { * @param sourceMove - The move that created the tag. * @param sourceId - The ID of the source of the tag. */ - constructor(tagType: ArenaTagType, turnCount: integer, type: Type, sourceMove: Moves, sourceId: integer) { + constructor(tagType: ArenaTagType, turnCount: number, type: Type, sourceMove: Moves, sourceId: number) { super(tagType, turnCount, sourceMove, sourceId); this.weakenedType = type; @@ -481,7 +486,7 @@ export class WeakenMoveTypeTag extends ArenaTag { * Weakens Electric type moves for a set amount of turns, usually 5. */ class MudSportTag extends WeakenMoveTypeTag { - constructor(turnCount: integer, sourceId: integer) { + constructor(turnCount: number, sourceId: number) { super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId); } @@ -499,7 +504,7 @@ class MudSportTag extends WeakenMoveTypeTag { * Weakens Fire type moves for a set amount of turns, usually 5. */ class WaterSportTag extends WeakenMoveTypeTag { - constructor(turnCount: integer, sourceId: integer) { + constructor(turnCount: number, sourceId: number) { super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId); } @@ -550,8 +555,8 @@ export class IonDelugeTag extends ArenaTag { * Abstract class to implement arena traps. */ export class ArenaTrapTag extends ArenaTag { - public layers: integer; - public maxLayers: integer; + public layers: number; + public maxLayers: number; /** * Creates a new instance of the ArenaTrapTag class. @@ -562,7 +567,7 @@ export class ArenaTrapTag extends ArenaTag { * @param side - The side (player or enemy) the tag affects. * @param maxLayers - The maximum amount of layers this tag can have. */ - constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) { + constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, maxLayers: number) { super(tagType, 0, sourceMove, sourceId, side); this.layers = 1; @@ -593,6 +598,12 @@ export class ArenaTrapTag extends ArenaTag { getMatchupScoreMultiplier(pokemon: Pokemon): number { return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2); } + + loadTag(source: any): void { + super.loadTag(source); + this.layers = source.layers; + this.maxLayers = source.maxLayers; + } } /** @@ -601,7 +612,7 @@ export class ArenaTrapTag extends ArenaTag { * in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap. */ class SpikesTag extends ArenaTrapTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3); } @@ -645,7 +656,7 @@ class SpikesTag extends ArenaTrapTag { class ToxicSpikesTag extends ArenaTrapTag { private neutralized: boolean; - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2); this.neutralized = false; } @@ -703,7 +714,7 @@ class ToxicSpikesTag extends ArenaTrapTag { class DelayedAttackTag extends ArenaTag { public targetIndex: BattlerIndex; - constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) { + constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex) { super(tagType, 3, sourceMove, sourceId); this.targetIndex = targetIndex; @@ -728,7 +739,7 @@ class DelayedAttackTag extends ArenaTag { * who is summoned into the trap, based on the Rock type's type effectiveness. */ class StealthRockTag extends ArenaTrapTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1); } @@ -804,7 +815,7 @@ class StealthRockTag extends ArenaTrapTag { * to any Pokémon who is summoned into this trap. */ class StickyWebTag extends ArenaTrapTag { - constructor(sourceId: integer, side: ArenaTagSide) { + constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1); } @@ -838,7 +849,7 @@ class StickyWebTag extends ArenaTrapTag { * also reversing the turn order for all Pokémon on the field as well. */ export class TrickRoomTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer) { + constructor(turnCount: number, sourceId: number) { super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId); } @@ -866,7 +877,7 @@ export class TrickRoomTag extends ArenaTag { * {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns. */ export class GravityTag extends ArenaTag { - constructor(turnCount: integer) { + constructor(turnCount: number) { super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY); } @@ -890,7 +901,7 @@ export class GravityTag extends ArenaTag { * Applies this arena tag for 4 turns (including the turn the move was used). */ class TailwindTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side); } @@ -928,7 +939,7 @@ class TailwindTag extends ArenaTag { * Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}. */ class HappyHourTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side); } @@ -942,7 +953,7 @@ class HappyHourTag extends ArenaTag { } class SafeguardTag extends ArenaTag { - constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) { + constructor(turnCount: number, sourceId: number, side: ArenaTagSide) { super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side); } @@ -955,6 +966,11 @@ class SafeguardTag extends ArenaTag { } } +class NoneTag extends ArenaTag { + constructor() { + super(ArenaTagType.NONE, 0); + } +} /** * This arena tag facilitates the application of the move Imprison * Imprison remains in effect as long as the source Pokemon is active and present on the field. @@ -1102,7 +1118,8 @@ class GrassWaterPledgeTag extends ArenaTag { } } -export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { +// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter +export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { case ArenaTagType.MIST: return new MistTag(turnCount, sourceId, side); @@ -1163,3 +1180,16 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov return null; } } + +/** + * When given a battler tag or json representing one, creates an actual ArenaTag object with the same data. + * @param {ArenaTag | any} source An arena tag + * @return {ArenaTag} The valid arena tag + */ +export function loadArenaTag(source: ArenaTag | any): ArenaTag { + const tag = getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.targetIndex, source.side) + ?? new NoneTag(); + tag.loadTag(source); + return tag; +} + diff --git a/src/phases/check-status-effect-phase.ts b/src/phases/check-status-effect-phase.ts new file mode 100644 index 00000000000..44918b54966 --- /dev/null +++ b/src/phases/check-status-effect-phase.ts @@ -0,0 +1,23 @@ +import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase"; +import { Phase } from "#app/phase"; +import { BattlerIndex } from "#app/battle"; +import BattleScene from "#app/battle-scene"; + +export class CheckStatusEffectPhase extends Phase { + private order : BattlerIndex[]; + constructor(scene : BattleScene, order : BattlerIndex[]) { + super(scene); + this.scene = scene; + this.order = order; + } + + start() { + const field = this.scene.getField(); + for (const o of this.order) { + if (field[o].status && field[o].status.isPostTurn()) { + this.scene.unshiftPhase(new PostTurnStatusEffectPhase(this.scene, o)); + } + } + this.end(); + } +} diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index bf38c432394..c396fa7ba59 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -6,7 +6,6 @@ import { StatusEffect } from "#app/enums/status-effect"; import Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; -import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; export class ObtainStatusEffectPhase extends PokemonPhase { private statusEffect?: StatusEffect | undefined; @@ -33,9 +32,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase { pokemon.updateInfo(true); new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => { this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined)); - if (pokemon.status?.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex)); - } this.end(); }); return; diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 53623f933f2..627cee4b06a 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -13,10 +13,10 @@ import { BerryPhase } from "./berry-phase"; import { FieldPhase } from "./field-phase"; import { MoveHeaderPhase } from "./move-header-phase"; import { MovePhase } from "./move-phase"; -import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase"; import { SwitchSummonPhase } from "./switch-summon-phase"; import { TurnEndPhase } from "./turn-end-phase"; import { WeatherEffectPhase } from "./weather-effect-phase"; +import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase"; import { BattlerIndex } from "#app/battle"; import { TrickRoomTag } from "#app/data/arena-tag"; import { SwitchType } from "#enums/switch-type"; @@ -206,11 +206,8 @@ export class TurnStartPhase extends FieldPhase { this.scene.pushPhase(new WeatherEffectPhase(this.scene)); - for (const o of moveOrder) { - if (field[o].status && field[o].status.isPostTurn()) { - this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o)); - } - } + /** Add a new phase to check who should be taking status damage */ + this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder)); this.scene.pushPhase(new BerryPhase(this.scene)); this.scene.pushPhase(new TurnEndPhase(this.scene)); diff --git a/src/system/arena-data.ts b/src/system/arena-data.ts index 5b907805372..ba37de0ed0e 100644 --- a/src/system/arena-data.ts +++ b/src/system/arena-data.ts @@ -1,5 +1,5 @@ import { Arena } from "../field/arena"; -import { ArenaTag } from "../data/arena-tag"; +import { ArenaTag, loadArenaTag } from "../data/arena-tag"; import { Biome } from "#enums/biome"; import { Weather } from "../data/weather"; import { Terrain } from "#app/data/terrain"; @@ -15,6 +15,10 @@ export default class ArenaData { this.biome = sourceArena ? sourceArena.biomeType : source.biome; this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null; this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null; - this.tags = sourceArena ? sourceArena.tags : []; + this.tags = []; + + if (source.tags) { + this.tags = source.tags.map(t => loadArenaTag(t)); + } } } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 0d2f35ae728..b162962fac6 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -31,7 +31,7 @@ import { TrainerVariant } from "#app/field/trainer"; import { Variant } from "#app/data/variant"; import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad"; import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard"; -import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; +import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena"; import * as Modifier from "#app/modifier/modifier"; import { StatusEffect } from "#app/data/status-effect"; import ChallengeData from "#app/system/challenge-data"; @@ -50,6 +50,7 @@ import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatch import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api"; +import { ArenaTrapTag } from "#app/data/arena-tag"; export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, @@ -1085,8 +1086,18 @@ export class GameData { scene.arena.terrain = sessionData.arena.terrain; scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct? - // TODO - //scene.arena.tags = sessionData.arena.tags; + + scene.arena.tags = sessionData.arena.tags; + if (scene.arena.tags) { + for (const tag of scene.arena.tags) { + if (tag instanceof ArenaTrapTag) { + const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag; + scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers)); + } else { + scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount)); + } + } + } for (const modifierData of sessionData.modifiers) { const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]); diff --git a/src/test/items/toxic_orb.test.ts b/src/test/items/toxic_orb.test.ts index a83fd3655e5..63c7b6245f5 100644 --- a/src/test/items/toxic_orb.test.ts +++ b/src/test/items/toxic_orb.test.ts @@ -1,7 +1,4 @@ import { StatusEffect } from "#app/data/status-effect"; -import { EnemyCommandPhase } from "#app/phases/enemy-command-phase"; -import { MessagePhase } from "#app/phases/message-phase"; -import { TurnEndPhase } from "#app/phases/turn-end-phase"; import i18next from "#app/plugins/i18n"; import { Abilities } from "#enums/abilities"; import { Moves } from "#enums/moves"; @@ -10,6 +7,7 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +const TIMEOUT = 20 * 1000; describe("Items - Toxic orb", () => { let phaserGame: Phaser.Game; @@ -27,40 +25,36 @@ describe("Items - Toxic orb", () => { beforeEach(() => { game = new GameManager(phaserGame); - const moveToUse = Moves.GROWTH; - const oppMoveToUse = Moves.TACKLE; - game.override.battleType("single"); - game.override.enemySpecies(Species.RATTATA); - game.override.ability(Abilities.INSOMNIA); - game.override.enemyAbility(Abilities.INSOMNIA); - game.override.startingLevel(2000); - game.override.moveset([ moveToUse ]); - game.override.enemyMoveset([ oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse ]); - game.override.startingHeldItems([{ - name: "TOXIC_ORB", - }]); + game.override + .battleType("single") + .enemySpecies(Species.RATTATA) + .ability(Abilities.BALL_FETCH) + .enemyAbility(Abilities.BALL_FETCH) + .moveset([ Moves.SPLASH ]) + .enemyMoveset(Moves.SPLASH) + .startingHeldItems([{ + name: "TOXIC_ORB", + }]); vi.spyOn(i18next, "t"); }); - it("TOXIC ORB", async () => { - const moveToUse = Moves.GROWTH; - await game.startBattle([ - Species.MIGHTYENA, - Species.MIGHTYENA, - ]); - expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB"); + it("badly poisons the holder", async () => { + await game.classicMode.startBattle([ Species.MIGHTYENA ]); - game.move.select(moveToUse); + const player = game.scene.getPlayerField()[0]; - // will run the 13 phase from enemyCommandPhase to TurnEndPhase - await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase); + game.move.select(Moves.SPLASH); + + await game.phaseInterceptor.to("TurnEndPhase"); // Toxic orb should trigger here - await game.phaseInterceptor.run(MessagePhase); + await game.phaseInterceptor.run("MessagePhase"); expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything()); - await game.phaseInterceptor.run(MessagePhase); - expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.activation", expect.anything()); - expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC); - }, 20000); + await game.toNextTurn(); + + expect(player.status?.effect).toBe(StatusEffect.TOXIC); + // Damage should not have ticked yet. + expect(player.status?.turnCount).toBe(0); + }, TIMEOUT); }); diff --git a/src/test/moves/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts new file mode 100644 index 00000000000..bac1ccdccd8 --- /dev/null +++ b/src/test/moves/toxic_spikes.test.ts @@ -0,0 +1,136 @@ +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { StatusEffect } from "#app/data/status-effect"; +import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data"; +import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +const TIMEOUT = 20 * 1000; + +describe("Moves - Toxic Spikes", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .startingWave(5) + .enemySpecies(Species.RATTATA) + .enemyAbility(Abilities.BALL_FETCH) + .ability(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]); + }); + + it("should not affect the opponent if they do not switch", async() => { + await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); + + const enemy = game.scene.getEnemyField()[0]; + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + game.doSwitchPokemon(1); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(enemy.hp).toBe(enemy.getMaxHp()); + expect(enemy.status?.effect).toBeUndefined(); + }, TIMEOUT); + + it("should poison the opponent if they switch into 1 layer", async() => { + await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.ROAR); + await game.phaseInterceptor.to("TurnEndPhase"); + + const enemy = game.scene.getEnemyField()[0]; + + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + expect(enemy.status?.effect).toBe(StatusEffect.POISON); + }, TIMEOUT); + + it("should badly poison the opponent if they switch into 2 layers", async() => { + await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.ROAR); + await game.phaseInterceptor.to("TurnEndPhase"); + + const enemy = game.scene.getEnemyField()[0]; + expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); + expect(enemy.status?.effect).toBe(StatusEffect.TOXIC); + }, TIMEOUT); + + it("should be removed if a grounded poison pokemon switches in", async() => { + game.override.enemySpecies(Species.GRIMER); + await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.ROAR); + await game.phaseInterceptor.to("TurnEndPhase"); + + const enemy = game.scene.getEnemyField()[0]; + expect(enemy.hp).toBe(enemy.getMaxHp()); + expect(enemy.status?.effect).toBeUndefined(); + + expect(game.scene.arena.tags.length).toBe(0); + }, TIMEOUT); + + it("shouldn't create multiple layers per use in doubles", async() => { + await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + + const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag); + expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES); + expect(arenaTags.layers).toBe(1); + }, TIMEOUT); + + it("should persist through reload", async() => { + game.override.startingWave(1); + const scene = game.scene; + const gameData = new GameData(scene); + + await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + + game.move.select(Moves.TOXIC_SPIKES); + await game.phaseInterceptor.to("TurnEndPhase"); + game.move.select(Moves.SPLASH); + await game.doKillOpponents(); + await game.phaseInterceptor.to("BattleEndPhase"); + await game.toNextWave(); + + const sessionData : SessionSaveData = gameData["getSessionSaveData"](game.scene); + localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true)); + const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true)); + gameData.loadSession(game.scene, 0, recoveredData); + + expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); + localStorage.removeItem("sessionTestData"); + }, TIMEOUT); +}); From 89d7e7ea655b8a0f103cee1a6ab696a26da4896a Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Fri, 11 Oct 2024 21:46:00 +0200 Subject: [PATCH 32/75] [P3][UI] Fix tooltip bugs in Starter Select screen (#4641) * [UI] Fix candy friendship tooltip bug in Starter Select * [UI] remove tooltip when exiting starter select screen --- src/ui/starter-select-ui-handler.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 5bbe765947e..2be89052bc1 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2963,8 +2963,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.natureCursor = -1; if (this.activeTooltip === "CANDY") { - const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId); - this.scene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); + if (this.lastSpecies) { + const { currentFriendship, friendshipCap } = this.getFriendship(this.lastSpecies.speciesId); + this.scene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`); + } else { + this.scene.ui.hideTooltip(); + } } if (species?.forms?.find(f => f.formKey === "female")) { @@ -3655,6 +3659,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { StarterPrefs.save(this.starterPreferences); this.cursor = -1; this.hideInstructions(); + this.activeTooltip = undefined; + this.scene.ui.hideTooltip(); + this.starterSelectContainer.setVisible(false); this.blockInput = false; From f0cc1fc88bba1015d47ec5a34d83b224952b09c6 Mon Sep 17 00:00:00 2001 From: pom-eranian Date: Fri, 11 Oct 2024 16:08:39 -0400 Subject: [PATCH 33/75] [Sprite] Hisui Goodra, Sliggoo, Shiftry (#4642) * [Sprite][Anim] 275 Shiftree- cropped ear fix - @hamez * 705 6706 Sliggoo Hisuian Goodra [Color Fixes] - @ rival_kieran - Fix to Kalosian Sligoo (705). Front exp should now use shaders properly. - Fix to all of Hisuian Goodra's base sprites + Variants to allow them to use shaders properly (as they didn't before). .json file included. - Fix to Hisuian Goodra's back static Rare Variant which used colors from the Epic variant on accident on cheeks + spots and drips. - Hisuian Goodra's eyes on Variant (Rare, Epic) exps were mapped to the wrong color compared to the statics, this has been fixed. - All images should be indexed. * Set variants to palette, removed old files --- public/images/pokemon/275.png | Bin 10555 -> 11106 bytes public/images/pokemon/6706.png | Bin 1040 -> 1103 bytes public/images/pokemon/back/6706.png | Bin 879 -> 942 bytes public/images/pokemon/exp/6706.png | Bin 24796 -> 27836 bytes public/images/pokemon/exp/705.png | Bin 1851 -> 1913 bytes public/images/pokemon/exp/back/6706.png | Bin 8135 -> 8808 bytes public/images/pokemon/female/275.png | Bin 10686 -> 11186 bytes public/images/pokemon/shiny/275.png | Bin 10555 -> 11107 bytes public/images/pokemon/shiny/female/275.png | Bin 10686 -> 11186 bytes public/images/pokemon/variant/6706.json | 40 + public/images/pokemon/variant/6706_2.json | 41 - public/images/pokemon/variant/6706_2.png | Bin 5170 -> 0 bytes public/images/pokemon/variant/6706_3.json | 41 - public/images/pokemon/variant/6706_3.png | Bin 5177 -> 0 bytes .../images/pokemon/variant/_masterlist.json | 20 +- public/images/pokemon/variant/back/6706.json | 38 + .../images/pokemon/variant/back/6706_2.json | 41 - public/images/pokemon/variant/back/6706_2.png | Bin 4762 -> 0 bytes .../images/pokemon/variant/back/6706_3.json | 41 - public/images/pokemon/variant/back/6706_3.png | Bin 4765 -> 0 bytes public/images/pokemon/variant/exp/6706.json | 40 + public/images/pokemon/variant/exp/6706_2.json | 2015 ----------------- public/images/pokemon/variant/exp/6706_2.png | Bin 55978 -> 0 bytes public/images/pokemon/variant/exp/6706_3.json | 2015 ----------------- public/images/pokemon/variant/exp/6706_3.png | Bin 56190 -> 0 bytes public/images/pokemon/variant/exp/705.json | 33 + public/images/pokemon/variant/exp/705_2.json | 272 --- public/images/pokemon/variant/exp/705_2.png | Bin 4111 -> 0 bytes public/images/pokemon/variant/exp/705_3.json | 272 --- public/images/pokemon/variant/exp/705_3.png | Bin 4119 -> 0 bytes .../images/pokemon/variant/exp/back/6706.json | 38 + .../pokemon/variant/exp/back/6706_2.json | 776 ------- .../pokemon/variant/exp/back/6706_2.png | Bin 22444 -> 0 bytes .../pokemon/variant/exp/back/6706_3.json | 776 ------- .../pokemon/variant/exp/back/6706_3.png | Bin 22483 -> 0 bytes 35 files changed, 199 insertions(+), 6300 deletions(-) create mode 100644 public/images/pokemon/variant/6706.json delete mode 100644 public/images/pokemon/variant/6706_2.json delete mode 100644 public/images/pokemon/variant/6706_2.png delete mode 100644 public/images/pokemon/variant/6706_3.json delete mode 100644 public/images/pokemon/variant/6706_3.png create mode 100644 public/images/pokemon/variant/back/6706.json delete mode 100644 public/images/pokemon/variant/back/6706_2.json delete mode 100644 public/images/pokemon/variant/back/6706_2.png delete mode 100644 public/images/pokemon/variant/back/6706_3.json delete mode 100644 public/images/pokemon/variant/back/6706_3.png create mode 100644 public/images/pokemon/variant/exp/6706.json delete mode 100644 public/images/pokemon/variant/exp/6706_2.json delete mode 100644 public/images/pokemon/variant/exp/6706_2.png delete mode 100644 public/images/pokemon/variant/exp/6706_3.json delete mode 100644 public/images/pokemon/variant/exp/6706_3.png create mode 100644 public/images/pokemon/variant/exp/705.json delete mode 100644 public/images/pokemon/variant/exp/705_2.json delete mode 100644 public/images/pokemon/variant/exp/705_2.png delete mode 100644 public/images/pokemon/variant/exp/705_3.json delete mode 100644 public/images/pokemon/variant/exp/705_3.png create mode 100644 public/images/pokemon/variant/exp/back/6706.json delete mode 100644 public/images/pokemon/variant/exp/back/6706_2.json delete mode 100644 public/images/pokemon/variant/exp/back/6706_2.png delete mode 100644 public/images/pokemon/variant/exp/back/6706_3.json delete mode 100644 public/images/pokemon/variant/exp/back/6706_3.png diff --git a/public/images/pokemon/275.png b/public/images/pokemon/275.png index 07a6fe725adfc61aad7a862a12678b088b533a37..61f98ed16555660d553a699e2dda6d8c0b2feb6f 100644 GIT binary patch literal 11106 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 diff --git a/public/images/pokemon/6706.png b/public/images/pokemon/6706.png index 44f627acd1011076a81973d1fa47e325aa12239b..e967b5508713a8aa1802e00ca8963727797f1275 100644 GIT binary patch delta 1082 zcmV-A1jYN12+s(RB!2;OQb$4nuFf3k0000yP)t-s0000F5e-X4Pk?iOn2VUdtik5f z>G%2eVp4~ZaJjdU@>61Sj+Uy#%;DhV^fo|DQD1OYW_XK}rp3kplbCiSpRd+&l*v8AKWycm{nM zyV(7=!MrMQAY(`3IQekSw2Nqw>8#I4B|}Iz$~dKjPjB(V=jQDHm?>$f!~79p>Y$oP zFi*&tJ@S~546o5FXJU*gWtBXb87aeC6tW95sefyv zemu<_d`@o!)-!T}@`bL*f-POhXmLetoVu<)e8;B1i0zqJit33()sCaP!P$7GIj z5XE-8lUZKqf|t*O%aENK+$vI%y|nh|k=P>XGVI7nnu6@J4qeyFkjP;TTok7!Tv?v& zkbih9^%2Y51ig$OUiCKHL`38{JtsTAx~7+8ozx_t1sHpC9MnM&=r zc*ro+Le%D`;N;jKaihAsb=YlUA@0WJygnyea}pL!T95GNYR6UC%}+#)JdoJr^-{!@ zcnf$U5gjFOxs6S}5<%iRbV3w?O#3l0GJoa#X^e7E2p^^#d=Ocz7VS(N6q` zOQchADe>kvWHd$o4d0L*C5wXifrxj6oDg4=a61TBA}-{qKZ>XhzeSN*{P6Qei8FHg ziEq2I{1V9hTT&Q2gMLh^AB*XinZNq0zxs9k2jQQ9d?va*lmGw#07*qoM6N<$f>Zqx AD*ylh delta 1018 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 Hu0mjfSn$d2 diff --git a/public/images/pokemon/exp/705.png b/public/images/pokemon/exp/705.png index 3413bcd9fa35db5d1cd102176e6664c2fbfbd6e6..670e8be5d51fd8645ca363e13dcc9eca01115e36 100644 GIT binary patch delta 1886 zcmV-k2ch`84*3p{B!2;OQb$4nuFf3k0000gP)t-s0000G5D-m8O@MQNzpTIV+VW;u zfRBB%xS{1unLTFAQcj#cXU|i%@~Zf82(thH01k9gPE!E?|NsC0|NsC0|NsC0S4!Iy z000K#NklvG&G5QMdWkTy=<|8*now;87KCg)VDn13I(V}EoHh}BA~&T;%} zrB~P`c(s#3;KU{LxdeaM+v5gHIM%4YApE)Aa%(Wm37`s&Rj}KFFl(n!JI5Xks$G39 z+z!;m>^SBEE!+ITt;|U`r)6QrF6_|Jj#!DzmR~DZV%Y`Pkp#&E*oD?>Xy{S5Ey zsFf&evI`sZ%zv#_$1P64eN&{ck$Q6{DDTwSkt-2dZ9J<@Y)ZL${?%+KZXQM0Ag!6v z?j#g4d-_#=P^I!VQc8`{5-aRa*9sjw$%w`wI+!#AabYnSU66#BJ&4I3J%`KMC1PCt(C0 zN5Avt)RI7E{c=cdtMeGf|5$KK@cSKJxI$}jBG5c2v)}@`CHNgWgyw-MQ^QSgdn0Zc z{t;UgI_s2I7j4CDgqP;Ax@zb9#N9F+j?r`?eQV3hmfy`Fw*h|=qwJ*AAlc0tS{?dnU0b+~}^(%LnJ&`b>a3V4BGh$u!pru0L3GlF zCNh?e8#U}$(M|i8-P0OECv9-r#OPvn0(Y(9eDDJ3R(t&tJiVL5)}*_nj01G9{mssv z1@~uf(Ch^03Wla#%33%2ekz-3{<6r?9nRAPEKj={e&5#KvC{DH83RrGFD=f{jr1)~ zdw(2WSDqT%S#h()j_d5H*PzW!dlYySuExx_#+xi0#0d5K<=mIX|nCDPlRUXunUcLfCt z^){#1q{p)NINU3c3%^9KNfX2KK8-(=m(?h+M6XFRtyM#w3JMo>1N=8>qPOz47=H)6 zLjC77DG=)arzgceIsXm_FtDG$^Mx)#5fET{gijT~Nel!Wn9;XGW~qYjw?<4C@NEz> zRZx@dXA;Z;_aJ9m7&^^RBbrVX6truMS>P6=r`s^JZ;f~jGFQ-pTx^2^DchEzZTrsB z0;=|av?Lqb$Y2S`P_}+rD)Oz7-O3(z0%}qzmF-XEX99v*oIG7Qpb-E z9L7M|@(h+UM}(_e(~}%U)fxv?mh(VH6%!GjShsl+L~pW*5X&m=BD9u3Gk=YfG#G%) zEl)wJ0maY;fT2q93?{}zbUM?CkLX1Xl1k@uIVO_HAj-ewWe$=`0TwwXQpkC11K=>J zA@9PsmdW`n01lJVa85*nu?>KOr0|>wN&*}vRVXPZQjycx2EbubIl?05L@F|TXC8(F z943XwL>h9E+5k9E3Xh2t(tlsMvZ)Jz!=!Y9riL{cR+o!i030Z#D`;Zak+awZz=2Y_ z$-7K5LZ-+~Ls2pZO4WH6AtMw>F}4A4s1%+PiKLkC0XR@fVTMPW+xU>fq)@{ufATqp zNtM8h^m)v3YrJBGOkAsYjAZG^^AsnMIPQM0Mdr7dJ|`k4SmGJ!C4c-!gNkE68Y>9T zw0Rfdey@;76xHyl767RzIq!m!K;Q<->uuM8l^5RYN+xnXh)+zcTK z32`6j_wg7?C($4|25yFYhlGT1%@|=J4Bc~yMA{kY!Ssx!7`I6U8EMiAu{Sy%(>!jn z6S4;>-#TT(AYHnqIDay&#b;mlH6aHuaCL?TKzyaVOKR?9vN+ae(w222NC)0E=r~m$ zc()vOkW$0mNQ>*DI9X_DtT_yo9{DOD5;U*wSP@dQhn7~#yHw;oBY^}BCWS{>P!ixE zDLldglK_ZlEksyg6agUykFcO9!huXvlMD-rA{?Z(kYPblgp=3?5L3x}@t#LmP!i!F zEj+=3k_d-s;RzO$L^x0jPq3gQ!u_$_%&uL#cJ11=YuBz_yLRo`wQJX|UAuPe+V$_$ YZ@>AKSD6@q_5c6?07*qoM6N<$f^O`ZumAu6 delta 1812 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 literal 8135 zcmW+*cQ{;M6DC;EMQ8O8okUrLRiaDOsL|UZ)+&ocZ$T^)k!V?Ei5}4{ONicACwlKh zjb8HY_xs~M=Q;1ZXXc!_b7$r}H&$O)gMyTW6b}!NLQ7NC5DyO@^8e>P0ShB0CA&<`mkpFA;XacOm*j(?T5b_^_UjBTG^o~fj) z2jLdr`xxrJ#8cXs!l~rY)KXP8dOPzcGY-AV|1oM=DZM_k0XxD_*Z^lvkXtZ^N3zIOfKa&G(|`9NR3{@M;v6OZ!^oQM(mnQw33-S z5S`lou3MbFtlKAy%g3+1d$+6}H_yaJ)5U7b;&3fXj|KffW?;Jf$C%WAfO35xoza8O z!i71Jc|OXE9-&hHtlds|y|Q)WXH@Ki*U|w|aV78qCOYjWjqf*|r?XXKKU>{lnd%z69|Zrx&`ue5 zKhyW@{WfakrPuzx0B3Y~ee2Yd16tjlCHnE(e30LIFgvn^sgad9N`Tq;>o{}1o$BbY zG7UVPU4VT+B*8u5n%t>$UMB{6mS?YkFK45nuP(#S+p9LNfmoOHNu+D~)l+^x%OY2Y zu(Eb7yc9#mo0R?&l+|0JwsI`=*^YjeiC;99{U3L&P!{iZ;0Ssa8Q!^pb#{{lMSk`0 zcr$`shXU+-WvLqNWhvNrrtK&XPtj+89j>K;<|`{B!Y@zg=ADjEZ&TJRH;g_rDLM*4Fc0_`WbQyA zzklOgu_z-?qNnd2Z|k#flBLEUJkz9j%|)G6oc#S@p_K_9M%dpKP3JR;elyeyc6Zd? zA{;I>$VOIQP3Va|F*md8(>m>j-U4IY-&rqHg@4}V3$akpC)8eg#@AAwUXFwQ9sQ<& zOcB>q+nR(xMJ2;s9wICpv)ih?zvO-F%ivWE-881$mFwUkZ%Fb~<6XZ-ncVT6uni>`+i2NiRTU$8JhHh0uld#9z4^-^ za!m9gt!88uOw~d$h1cAep>0@D3&xT`ZxvGwI(^7Km?`>8-uhQJUm~#+=965;$U|Om zxwbetHp%Ggh)6pDIxVQe^(uYxoY^_@m-QB5kP;u>hNGLt>-C_P9m0~es2sy2bUI7i za@D_HSbi|{%qI|A8*D)LB zd$05fN&j;*e^Oo*!?S;!50~GJJd&N12~h`jhkoXQo~?DeyCGx+v?F})8B^W}t|xuL zmUCjnpLgl~;Lz&|4Y8e#e@_a0$2Fa!{VJ)(T-;>i*bNh6T|VRznub(&y*IFx%-brc zqo>ukJ_oUpTw}t{_}2fsyT186z2_t=Yh@Cf1@?HFXuP9M3KjKW*|hDt_N}JbqPE=ZbkY2J@*I27&Kl245l@hU`)EAG!*78Pa-9`ihHA&+Oh~;JjgI@_; zys5^1U%pUnn z{{cPxx>s9GI}0f*SbvveKC0;sx!QY(Ec^qnkWK()pT4|K)EwI|=FY0oWJ&LjkU9;C zSD@A#L=BDCO;FW6v>DUe0om~8GtNRh3jVS@`fKNVMi_P_pl9Z*J2v6`P1Jl{Vl}v7 z$kMubF>R@Q$X)05Lp-~wgxKdRw&%pXT{71}r2?}r-BLs+Y7CM1I-wK<3ve(GFUSUP zlFH_=C@p-U_5PcG@nRC&;0R%L*tsQ@)=1eWDoEQ~=6>1DduQfFrT#w_%l`0J%9g`3 zc)-6^3>h{Z9{;jjz5j7}G3z#>c0ugO==Ql>ZXCRrBWt|hQ6E^lCCt@2rsmo+)k zCamDx<<{*#xt5XEUrVaqq@njCnlZmjkYLCO+Al*oH)idUq07K2_n>t|aB;1_q|9;J zd1o+oh&XU0I~t_*_c=k)T`h1#XDCIoQji4soj14%JKhAzrh@o?y&yzgrEaQ>=y;^A zz1}yb$r{)0Q*=LJkYdxdJ?ZZk_NPT=4by$#J54HBfVfm-XG3f3*!QUF)Y25PLA3Ll zvi(Gs^1@pwVCN5{Nbyxb`L)oqpmU`o$PPDLMo;ABNnsoN=SmB!ppFZm= z#NI;z;2>M!YP>G|o{ns<+I&BVep(|*A|rVE)O%mPyu@%sG{E_iRCZdej{AqJaMedg z*LwfN)48a1U$k#c{?@mfRkLsCjHz!u>*iG7ZMBHCuGSBk&df}{A8NT+gBYhvkINJX zBR=r5@$jyKZmuc>ZcM*Aq9s**A^EK^ zGb$qQeB<1bEvYaiYA|WGR(`^F45ny)qWH#r(Li7YaMi&R(xd0QaZ7-{{vHFY&(|e# z{!1VG>5CUZuy@3G>~qGraZ^>X;5odnv<@$dM6-)%8AKS0%gvm;d0}`%z1}T`R+m1R z`mEY&J3=&~yQ_dawzk~Zw|?o505R0YILwrIF053gwCV6Qz#vnT6@kP0+E^9Wjl+RU zZ+g7Fg{k;f^1b*?ip%-EWkWsn;(`2)tgU=4$K`sT0|66U2`U4=VI||HU#7^xtpe>7 zUn~{)JUe5d-i%0Dr>qNdw`#%e71vu*#5iB&T|It{%Gnzc5?w;kY}1VyU%65H)u(1t zbiti|scQB3ClF8%daRsS=HtUpG_dr8C{~(4duqb4X6}DhfKxI$5KL*9(_xe~Fx0Z& zS%s~Q$k2P7g#BT8Q4ghr{~Z^HwCx3jJ;hg=j7;ThF+ciw9}=W7ohx#}i{Rd9=j_J1 z#akeSS^8TRmzRI(wNe|G08_Fgo)V(FDb;+#;v)_kpJ_*`1W)P70;54N+pN~y-a(QK z$QzP&q(X&^B9#RAMu@5!ZO=7uRUdu`n569067`?iev`1dr1zM-OF+HP;=BI!skC_xSb8jDu)Z-H#SKcKpvb-~O7#QIm_qxRhxH64Ol8;&A z2=3KSVPW%}Zqk6=M-_KN>$$5vU@;1FCU5zEgMw!JR!Hz%nDhGDUe_0XZ+>+#Q?Zcr z6y5gltYH)3`Z)q={h63!KSJk2X_9Ia7PR;&y%R`!6+lRlgkJRp42Y|_dN$Dkq3R0N z6DKn7{ocKoVjCUSsSFuYf~Kqh)@0M2AA`8bwWTt=F{za$W|i5Tx2x+rQnoh9FTJgk7l&+Q8dHfq*HOqh* zTv(L2ywuFSS`Zet zdLkZLj8#gGDZ^i8F0+Ma+9zPI>hV1R(|C{Kzd7D>N!DOmk{m!HO-)NbR&&Gf?DQ_u z!1x2V+*ez$y0`cd57N zxU4sP%yj(krF>2ki_dcT9qd+R-tNy!0E7qa2#NSwiQwFHLYX)=E(kA)Zkhmnx+Y z{Qj%C6J0dPyXs;7WEQnC&-#UM2sgL)O|6~I=8rTaz`+IEfl zyd+9*OUQ($0bg2a)6p@+bWo_Omo8Y%9|iOz;I9T$VsdsZX~8Nsl@L{TRtK^u7}H) zmC_jCRD^ij5`)K%`@)$n?*(Db)*_hF?~xZlvp3-3j+WJPc5opeyM_LGDua^smTobQ zqW}=OLT9qSC+m>xC1u)q2hym=KTrvI?QW$H{g)M}&PaAXA%RWaqq^OCfa{hItM?zRq3L^Cb-=o7O7OX+n)`y1Kj2@aPdaJ@3*XYx zmuoip8gEj}(L?yjVDa~P;dRUA!=`k3gtu&R+0veI{bmg4k8*DP22wjh$L%lJp0uq~ z%vCxKOIcUhhNp~~t~IbS#iS?u6IktwdkOG*#33UdDj3miU)A~(+_n!qohlp08UE6L z^)r0I4)z+8lWi|B(DyUkT2#VfpLD2VQ}<~-4B?YD;ZG0@wcaUp#YmA_^k?EKD+82r zjrouFf)Z6~JCc-O#5<5-E{o{*bA#7#ArI8cG;{v6(sgmk{){LX!toZTA9pd^hyF~F ztFAzq52`Fq(363_p8&3=EGN9Z7z)0CbIQ!B%#pY=VRKU=r)YN&M5#EdY?h+mqFYkh zt5N0rHJD84;r0#FB3D^?vMU4y$n+QxFf}*l?aK6*epfo-)q)ans@tm%_amEgeMm`v zh6lQ>ag(9EPd!T+{OH(A?;@HWn1IrY18IYA|76x1%3r5EM3@^XV*n`^Q zgvRcWBLKVIiGPwj9ZuLq2jh9ff4PSlX>UW77rDT@fRFEgTEz!xI7Q$z)6LAKrg=tU z&Hvg4MK|YWVOcufy+-nu)!Pqk3wcBIvB3ImVzu1IOuD$aIE!Jb^r12GM{EGB$_xkW z3@Eh$w;Ux3H-9dlW;Am<0C3(1Lc7`%|7JAm89?C@9;mUvgermlZSkL3W2Vq^|LxBn zvzB|9Qzrr5n7N{!^l(v+B}!y@Sy~)ll;o~p71U&%!l%l74o>K+Ve?6fz-mEBEZdc7 zpmr1CHEC3Oipi4p)XP}SA`r1-1^g)LsWwlR9`1$w+X8q z9|LOK(KJj06;Iy0#}!IM(Bvw`)|K!Mh7F~hk2A3)5hs19hHTA(PpdcJNov0T*pNHE zDei${XLkJZnlKPnNbv_R;aR#dLgzB+7;tk7Lf#{L6q5XOXyH-+7a{b<^B&+uG**p) zoN)NC5AlPR@fgr-2QS`CD%j1RQt*1JaC=;0Fg%; z=#0XF!j5-xge1KKX-?Zre{z0wd#1;LnJ1XY?Ue)OpFy>oJtO6b=>teb%h57)f#N=l z!p~F;U-sdcg)7I3YC%KgiqOK*_X z;CM9>-kAU?J4zYr=uPc6%H;aTXCNz?clx<*=a}ulPYT!FC+;C$i6om^ciZ73PtH2#AKfptsDXl{oCv-Mmkp2={^+iq zhEtv_>K%(2>h06VyKAM25Vewj7eZo70$!x1J70>O3zgrG6xz=H#aUD@W>Eof`M5n5 zRdf2q9WXRWti@sWc@q(R}1dxIr_7IXj_p0XX z8f=+WvC!*uK0Y+1ex4&zQ~^^ALayT$F@7R>a81x==t2f+1$;Quya1O3%)3Wd(#=Fj z-5}ipZ`$2q_-h5b3wZV~?)0EVKSc4E=CO(I@_H-)wmrN28N(#da0C?fb}RUFBt*ad!RyeuDu z&;;hbuypkYssfCY>=3GLU&~n;*A+tb7NKuq^lw;kh2br@@p)v^Kj3| z@gkC>bXNvJG%@o&>1wQx)k0ce(-1Y~g({L+9Rodki3IS2%$0JHo_k*`bt&J}ZO1|% zRq}Cf#iBW#r^^ugpe9hZ-d-$SK(2_3P>K%ACIk2r_SqiEQA+~acB1M*#7qEN@S_}@ z@6eFx`^xU%bnJjvOtC7?abH9&wDc{?KMs{?f=3v>Tp7}gPddy9w`L72PGqz?qnb1-YM>Y)vFBv3cVpeVD)|Uh^Xs4M6As$mf4ysb5k&Op z;(|4lJ^7h=V$`qL#&4WOKTJ2_AHcC66`TzP)*>jD$Wd~HyIm7wwj^{Fe3$qq>&j6P zym$97P^C9LlLdANSzeaQF zQJB}hvZy(I74P@t)RE>yCN1J8Ba`O@YX-r$@0$||gT;Id(fyu_Wz~eYa^u~o8Ay26 z!GrCz0GiBmIs*aK&B;_E3@GvtSU+!c8gRt2=at@*yk_ZWtZc&;Nf7e67FlAgk(B{H z0UXi%MB8FZevFe>;HVe#CYz*yRR4%24Ag|^p30VQjlZlZdt|@7@?jM;VOM?Y6wMrk zn+-$gm}d3L`PQ8BRb-@)wM1i1ZXVL^;1Qw7k-X5Q2!$X;;vEc4dA0cML)yRnpKPdl z!eQh)&~&I+>uE@UL!;2fQtoff;!jPLdwg3!t(uv{oVYAXf%9`4t9tP_^c?JB7r^qZ zZ6IyL6Z}A{6+@+QqEKA!G3n6QAB;(EUj7}*PNZs8VL$&WJ8j~;9#0@v$4(zdiuFCY zXpmCqDGnSN&g@A|Kz%Y)iUtz)`ZktX=DxqLfL$jbSec^Zz(q2XOK@mn{mlVac)_KC z9kN5WhWQQY`*{IQah4YesO1NCG+Tp}u=h*tme$CL##P;OUHnQpa&^_n;s?TiyVblb zq}?)Ah&+C2s1r zzl{xXR0ZZ8gzgRQBf>42pORKFigFcCX-q@Yvohcnj20QpmWr)&8&&qg&I4r^UmnM_ zJQW}7C@Yn-36iO6u$|$^fK&gG8r1dK@J@Z~@B&CRWcSOpqQbE*uOp4ssWnfLhm3J{ zFS>jm=+{V0z!tPr9;41tuqVk>H)^{P`jkZ7uFOKFZhrEj4BIj!Ur7t5M2~1iPNU=o zeQD*dzWFT!Y?t@_WG&?$C@SvR0z2O#FkHmHb*T5LdfVI;@A(apO86*YKO}xFlSyN4 zp#4@`Lll>^9%X@1TJ(`66bx8yqZE%7dO%f>$~nc+7B%~TWw9nb-ugpgl^T0Inv-s0 zGAi&3R^e*sGkeX=uLtr>?#d{|uZ;VCss4n;o}-af0&7Z4zOC$J=R`=GQh|2~>|b&s z%W#l#%f?33bu^Y|3=r{y2i_C@cqd~0E>Ucb(Xt>NgzrbhYg9yb?{?&;Csjo{L9J|o zCsA7w8N=1L*UMQckej>5fOjK|l2>Hsa-%;N1;WA}Uqz?~E{7;*tO~R!;dbHm^@o7) zoLJ?wEJ8m=#LN{QF7-D!JJ^+!d_~QswTe6QUH2?&sh959h9&hg7bEIBA2VK?St%Oo zlxQJVWv@t?^A|{)tmj4Vl{j~KY>~S}26&%s*OQ_G-(zW>MJT1%iu8`DK1sju>2ANCOeS>2tm=UZroX&JW9 zBAv_d;TEYpv9EGhpA`d)Dfgf@o6h-TP!zeR`<5)OwL)z6UG}$OPLvaR!95#{SIi8y zz6Nl8M#oZC)kUvC4A))5H8Y47t;A35L%9=?ksQfm10#@^-%+E^CX|dKF!YMF#rC-) z4m9RuUv0Eh@J30}bJFib94YsgX1d6@pB)rtWIEXJ{{|42(Q(nAMwp#3DO_86@v6&r zLmZxzVU-1Ytu3_^5!%Y~fxXMW4*M`c>nzJt!R`PI2sZx*AextJLOqlfY(CjXVNS#D z7jB9Cqjxa>7x77m^@UO}MBa)?MsGNOf_GxN7NgmxxB{-5Himb!9~If;`uC`;)nYMHj&8c*%C|He dN+TDFBJsV4B4ue&xF76zT57tgWzTFQ{s$;Zrdt32 diff --git a/public/images/pokemon/female/275.png b/public/images/pokemon/female/275.png index 7f251793a15764d2a7bb5849e65045ad7884eae3..c3c358716b2d919fc2a882d2f69e3ad524d57761 100644 GIT binary patch literal 11186 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≪pFCQ%=xE@hNoXB9038F;6HZ(ytRK>+ik2e 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 VwS#>?K2`v{tSucZ-XOf={|_}1*(Lx0 diff --git a/public/images/pokemon/variant/exp/705.json b/public/images/pokemon/variant/exp/705.json new file mode 100644 index 00000000000..a29b8f124dc --- /dev/null +++ b/public/images/pokemon/variant/exp/705.json @@ -0,0 +1,33 @@ +{ + "1": { + "101010":"101010", + "4d454d":"8a2166", + "807380":"b93f84", + "bfacbf":"e56ca6", + "f2daf2":"fbb3d2", + "665980":"4e4094", + "8f7db3":"8b69c3", + "b8a1e5":"c7a1e5", + "4d993d":"aa6a00", + "66cc52":"ffd047", + "4e9c3e":"0c5474", + "67cf53":"3aa8c4", + "b6f2aa":"63cee1" + }, + "2": { + "101010":"101010", + "4d454d":"194f51", + "807380":"2b736f", + "bfacbf":"5db6a9", + "f2daf2":"9cead8", + "665980":"274159", + "8f7db3":"2f667c", + "b8a1e5":"4a9699", + "4d993d":"007d61", + "66cc52":"49ffbf", + "4e9c3e":"842401", + "67cf53":"a34205", + "b6f2aa":"d27e26" + } +} + diff --git a/public/images/pokemon/variant/exp/705_2.json b/public/images/pokemon/variant/exp/705_2.json deleted file mode 100644 index bf9fd104c5d..00000000000 --- a/public/images/pokemon/variant/exp/705_2.json +++ /dev/null @@ -1,272 +0,0 @@ -{ - "textures": [ - { - "image": "705_2.png", - "format": "RGBA8888", - "size": { - "w": 154, - "h": 154 - }, - "scale": 1, - "frames": [ - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 91, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 35, - "h": 58 - }, - "frame": { - "x": 119, - "y": 58, - "w": 35, - "h": 58 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:4bf155254b23c88780e7eee282256589:82bb727988054c3064e203b6908ff464:6b57e983626c7fc9144ab67f30c66814$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/705_2.png b/public/images/pokemon/variant/exp/705_2.png deleted file mode 100644 index 8256ebc7fdbebeaff9d693aed989c4fbce61b77e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4111 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 diff --git a/public/images/pokemon/variant/exp/705_3.json b/public/images/pokemon/variant/exp/705_3.json deleted file mode 100644 index 199d7bc9c3e..00000000000 --- a/public/images/pokemon/variant/exp/705_3.json +++ /dev/null @@ -1,272 +0,0 @@ -{ - "textures": [ - { - "image": "705_3.png", - "format": "RGBA8888", - "size": { - "w": 154, - "h": 154 - }, - "scale": 1, - "frames": [ - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 46, - "h": 58 - }, - "frame": { - "x": 0, - "y": 0, - "w": 46, - "h": 58 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 46, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 45, - "h": 58 - }, - "frame": { - "x": 91, - "y": 0, - "w": 45, - "h": 58 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 1, - "y": 0, - "w": 42, - "h": 58 - }, - "frame": { - "x": 0, - "y": 58, - "w": 42, - "h": 58 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 41, - "h": 58 - }, - "frame": { - "x": 42, - "y": 58, - "w": 41, - "h": 58 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 36, - "h": 58 - }, - "frame": { - "x": 83, - "y": 58, - "w": 36, - "h": 58 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 49, - "h": 58 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 35, - "h": 58 - }, - "frame": { - "x": 119, - "y": 58, - "w": 35, - "h": 58 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:4bf155254b23c88780e7eee282256589:82bb727988054c3064e203b6908ff464:6b57e983626c7fc9144ab67f30c66814$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/705_3.png b/public/images/pokemon/variant/exp/705_3.png deleted file mode 100644 index 66b43956bdf70a75057437c951b98f1c55eef48c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4119 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 Lf*Zay@Obb);ds$n diff --git a/public/images/pokemon/variant/exp/back/6706.json b/public/images/pokemon/variant/exp/back/6706.json new file mode 100644 index 00000000000..2de5352e936 --- /dev/null +++ b/public/images/pokemon/variant/exp/back/6706.json @@ -0,0 +1,38 @@ +{ + "1": { + "566678": "197497", + "8e96aa": "3b235c", + "929aad": "3aa8c4", + "625287": "4e4094", + "536273": "301848", + "988b98": "b24c86", + "36404c": "0c5474", + "c4cce1": "513981", + "e6d3e9": "f1a4c5", + "bfacc1": "d074a0", + "546475": "0e6296", + "c5cee3": "63cee1", + "80737f": "8a2166", + "4b454f": "6f1357", + "9170b9": "8b69c3", + "b791f2": "c7a1e5" + }, + "2": { + "566678": "a34205", + "8e96aa": "073338", + "929aad": "d27e26", + "625287": "0e3f47", + "536273": "042329", + "988b98": "2b736f", + "36404c": "842401", + "c4cce1": "0d484a", + "e6d3e9": "9cead8", + "bfacc1": "5db6a9", + "546475": "8e480b", + "c5cee3": "f7af58", + "80737f": "194f51", + "4b454f": "274159", + "9170b9": "2f667c", + "b791f2": "4a9699" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/6706_2.json b/public/images/pokemon/variant/exp/back/6706_2.json deleted file mode 100644 index 5c916aeb664..00000000000 --- a/public/images/pokemon/variant/exp/back/6706_2.json +++ /dev/null @@ -1,776 +0,0 @@ -{ - "textures": [ - { - "image": "6706_2.png", - "format": "RGBA8888", - "size": { - "w": 358, - "h": 358 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 0, - "y": 69, - "w": 83, - "h": 72 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:5d65e2c5a6a97b7c7014a175ce3592af:3255e87f637a475d82734fc7d93baf71:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/6706_2.png b/public/images/pokemon/variant/exp/back/6706_2.png deleted file mode 100644 index cb79347842094431759bdf08b8d5c76eac99023a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22444 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 XiwmH!3^jqR&X^1hO!TYt>?8gU6->{8 diff --git a/public/images/pokemon/variant/exp/back/6706_3.json b/public/images/pokemon/variant/exp/back/6706_3.json deleted file mode 100644 index 3bb1dc426b2..00000000000 --- a/public/images/pokemon/variant/exp/back/6706_3.json +++ /dev/null @@ -1,776 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 358, - "h": 358 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 4, - "w": 84, - "h": 69 - }, - "frame": { - "x": 0, - "y": 0, - "w": 84, - "h": 69 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 84, - "y": 0, - "w": 83, - "h": 72 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 1, - "w": 83, - "h": 72 - }, - "frame": { - "x": 0, - "y": 69, - "w": 83, - "h": 72 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 167, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 83, - "h": 70 - }, - "frame": { - "x": 250, - "y": 0, - "w": 83, - "h": 70 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 167, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 83, - "y": 72, - "w": 82, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 3, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 0, - "y": 141, - "w": 82, - "h": 73 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 4, - "y": 0, - "w": 82, - "h": 73 - }, - "frame": { - "x": 249, - "y": 70, - "w": 82, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 0, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 0, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 5, - "y": 0, - "w": 81, - "h": 73 - }, - "frame": { - "x": 81, - "y": 214, - "w": 81, - "h": 73 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 81, - "h": 71 - }, - "frame": { - "x": 81, - "y": 287, - "w": 81, - "h": 71 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 165, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 2, - "y": 1, - "w": 82, - "h": 72 - }, - "frame": { - "x": 247, - "y": 143, - "w": 82, - "h": 72 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 162, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 3, - "w": 81, - "h": 70 - }, - "frame": { - "x": 162, - "y": 288, - "w": 81, - "h": 70 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 86, - "h": 73 - }, - "spriteSourceSize": { - "x": 6, - "y": 0, - "w": 80, - "h": 73 - }, - "frame": { - "x": 242, - "y": 215, - "w": 80, - "h": 73 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:5d65e2c5a6a97b7c7014a175ce3592af:3255e87f637a475d82734fc7d93baf71:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/back/6706_3.png b/public/images/pokemon/variant/exp/back/6706_3.png deleted file mode 100644 index 6390c20799f368a4253062379b4624b3f1f0ea69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22483 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 Date: Fri, 11 Oct 2024 13:09:15 -0700 Subject: [PATCH 34/75] [P3] Fix "falsex" type hint for Sheer Cold against ice types (#4638) Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/move.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index ff0c24f5032..5aa6b2623c0 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -4556,18 +4556,19 @@ export class WaterSuperEffectTypeMultiplierAttr extends VariableMoveTypeMultipli export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr { /** * Checks to see if the Target is Ice-Type or not. If so, the move will have no effect. - * @param {Pokemon} user N/A - * @param {Pokemon} target Pokemon that is being checked whether Ice-Type or not. - * @param {Move} move N/A - * @param {any[]} args Sets to false if the target is Ice-Type, so it should do no damage/no effect. - * @returns {boolean} Returns true if move is successful, false if Ice-Type. + * @param user n/a + * @param target The {@linkcode Pokemon} targeted by the move + * @param move n/a + * @param args `[0]` a {@linkcode Utils.NumberHolder | NumberHolder} containing a type effectiveness multiplier + * @returns `true` if this Ice-type immunity applies; `false` otherwise */ apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { + const multiplier = args[0] as Utils.NumberHolder; if (target.isOfType(Type.ICE)) { - (args[0] as Utils.BooleanHolder).value = false; - return false; + multiplier.value = 0; + return true; } - return true; + return false; } } From cfb92b4e0874d1f6344578d5abb689e78b9c8fbf Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:44:16 -0700 Subject: [PATCH 35/75] [Move] Telekinesis + [Bug] Ingrain (#4506) * some early set up * localization * Added Wiglett family to restrictions * Added Smack Down + 1000 Arrows Interactions * Added checks for certain tags * Gravity removes telekinesis from all pokemon on the field * need to check something else real quick * mmmmmm * think this is fine? * ingrain fixes * more ingrain * Telekinesis Test + Move Fix * Test Name change * another day another try... * Test Cleanup * fsfdsfds * Revert "fsfdsfds" This reverts commit cb7abcfd9f69342ae7b1864e2e4e124029f9f67e. * whoops * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Missed one * Update src/data/move.ts Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * Add separate battler tags in move attr * Update src/data/battler-tags.ts Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * removed onRemove * Documentation * Update src/data/battler-tags.ts --------- Co-authored-by: frutescens Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> --- src/data/arena-tag.ts | 3 +- src/data/battler-tags.ts | 39 ++++++-- src/data/move.ts | 20 ++-- src/enums/battler-tag-type.ts | 3 +- src/field/pokemon.ts | 2 +- src/phases/move-effect-phase.ts | 6 +- src/test/moves/telekinesis.test.ts | 124 +++++++++++++++++++++++++ src/test/moves/thousand_arrows.test.ts | 4 +- 8 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 src/test/moves/telekinesis.test.ts diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 45d64249296..11d28ab7e25 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -885,7 +885,8 @@ export class GravityTag extends ArenaTag { arena.scene.queueMessage(i18next.t("arenaTag:gravityOnAdd")); arena.scene.getField(true).forEach((pokemon) => { if (pokemon !== null) { - pokemon.removeTag(BattlerTagType.MAGNET_RISEN); + pokemon.removeTag(BattlerTagType.FLOATING); + pokemon.removeTag(BattlerTagType.TELEKINESIS); } }); } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 3cc109df264..18f03ada941 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1713,7 +1713,12 @@ export class TypeImmuneTag extends BattlerTag { } } -export class MagnetRisenTag extends TypeImmuneTag { +/** + * Battler Tag that lifts the affected Pokemon into the air and provides immunity to Ground type moves. + * @see {@link https://bulbapedia.bulbagarden.net/wiki/Magnet_Rise_(move) | Moves.MAGNET_RISE} + * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS} + */ +export class FloatingTag extends TypeImmuneTag { constructor(tagType: BattlerTagType, sourceMove: Moves) { super(tagType, sourceMove, Type.GROUND, 5); } @@ -1721,13 +1726,17 @@ export class MagnetRisenTag extends TypeImmuneTag { onAdd(pokemon: Pokemon): void { super.onAdd(pokemon); - pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + if (this.sourceMove === Moves.MAGNET_RISE) { + pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + } onRemove(pokemon: Pokemon): void { super.onRemove(pokemon); - - pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + if (this.sourceMove === Moves.MAGNET_RISE) { + pokemon.scene.queueMessage(i18next.t("battlerTags:magnetRisenOnRemove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } } } @@ -2676,6 +2685,22 @@ export class SyrupBombTag extends BattlerTag { } } +/** + * Telekinesis raises the target into the air for three turns and causes all moves used against the target (aside from OHKO moves) to hit the target unless the target is in a semi-invulnerable state from Fly/Dig. + * The first effect is provided by {@linkcode FloatingTag}, the accuracy-bypass effect is provided by TelekinesisTag + * The effects of Telekinesis can be baton passed to a teammate. Unlike the mainline games, Telekinesis can be baton-passed to Mega Gengar. + * @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS} + */ +export class TelekinesisTag extends BattlerTag { + constructor(sourceMove: Moves) { + super(BattlerTagType.TELEKINESIS, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE ], 3, sourceMove, undefined, true); + } + + override onAdd(pokemon: Pokemon) { + pokemon.scene.queueMessage(i18next.t("battlerTags:telekinesisOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } +} + /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * @param sourceId - The ID of the pokemon adding the tag @@ -2802,8 +2827,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new CursedTag(sourceId); case BattlerTagType.CHARGED: return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); - case BattlerTagType.MAGNET_RISEN: - return new MagnetRisenTag(tagType, sourceMove); + case BattlerTagType.FLOATING: + return new FloatingTag(tagType, sourceMove); case BattlerTagType.MINIMIZED: return new MinimizeTag(); case BattlerTagType.DESTINY_BOND: @@ -2849,6 +2874,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new ImprisonTag(sourceId); case BattlerTagType.SYRUP_BOMB: return new SyrupBombTag(sourceId); + case BattlerTagType.TELEKINESIS: + return new TelekinesisTag(sourceMove); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/move.ts b/src/data/move.ts index 5aa6b2623c0..bd3706545f9 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7843,7 +7843,9 @@ export function initMoves() { .attr(RandomMovesetMoveAttr, true) .ignoresVirtual(), new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3) - .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true), + .attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true) + .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, true, true) + .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLOATING ], true), new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], -1, true), new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3) @@ -8177,8 +8179,8 @@ export function initMoves() { new SelfStatusMove(Moves.AQUA_RING, Type.WATER, -1, 20, -1, 0, 4) .attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true), new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4) - .attr(AddBattlerTagAttr, BattlerTagType.MAGNET_RISEN, true, true) - .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.MAGNET_RISEN, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))), + .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, true, true) + .condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [ BattlerTagType.FLOATING, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN ].every((tag) => !user.getTag(tag))), new AttackMove(Moves.FLARE_BLITZ, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4) .attr(RecoilAttr, false, 0.33) .attr(HealStatusEffectAttr, true, StatusEffect.FREEZE) @@ -8403,7 +8405,11 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true), new StatusMove(Moves.TELEKINESIS, Type.PSYCHIC, -1, 15, -1, 0, 5) .condition(failOnGravityCondition) - .unimplemented(), + .condition((_user, target, _move) => ![ Species.DIGLETT, Species.DUGTRIO, Species.ALOLA_DIGLETT, Species.ALOLA_DUGTRIO, Species.SANDYGAST, Species.PALOSSAND, Species.WIGLETT, Species.WUGTRIO ].includes(target.species.speciesId)) + .condition((_user, target, _move) => !(target.species.speciesId === Species.GENGAR && target.getFormKey() === "mega")) + .condition((_user, target, _move) => Utils.isNullOrUndefined(target.getTag(BattlerTagType.INGRAIN)) && Utils.isNullOrUndefined(target.getTag(BattlerTagType.IGNORE_FLYING))) + .attr(AddBattlerTagAttr, BattlerTagType.TELEKINESIS, false, true, 3) + .attr(AddBattlerTagAttr, BattlerTagType.FLOATING, false, true, 3), new StatusMove(Moves.MAGIC_ROOM, Type.PSYCHIC, -1, 10, -1, 0, 5) .ignoresProtect() .target(MoveTarget.BOTH_SIDES) @@ -8411,7 +8417,7 @@ export function initMoves() { new AttackMove(Moves.SMACK_DOWN, Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN ]) + .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) .attr(HitsTagAttr, BattlerTagType.FLYING) .makesContact(false), new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5) @@ -8844,9 +8850,9 @@ export function initMoves() { .attr(NeutralDamageAgainstFlyingTypeMultiplierAttr) .attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true) .attr(HitsTagAttr, BattlerTagType.FLYING) - .attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN) + .attr(HitsTagAttr, BattlerTagType.FLOATING) .attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED) - .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN ]) + .attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ]) .makesContact(false) .target(MoveTarget.ALL_NEAR_ENEMIES), new AttackMove(Moves.THOUSAND_WAVES, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6) diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 43c849a78e0..2efae9ad359 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -54,7 +54,7 @@ export enum BattlerTagType { CURSED = "CURSED", CHARGED = "CHARGED", ROOSTED = "ROOSTED", - MAGNET_RISEN = "MAGNET_RISEN", + FLOATING = "FLOATING", MINIMIZED = "MINIMIZED", DESTINY_BOND = "DESTINY_BOND", CENTER_OF_ATTENTION = "CENTER_OF_ATTENTION", @@ -86,4 +86,5 @@ export enum BattlerTagType { IMPRISON = "IMPRISON", SYRUP_BOMB = "SYRUP_BOMB", ELECTRIFIED = "ELECTRIFIED", + TELEKINESIS = "TELEKINESIS" } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index d8fcc281d1b..c495e0833cd 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1488,7 +1488,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } isGrounded(): boolean { - return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.MAGNET_RISEN) && !this.getTag(SemiInvulnerableTag)); + return !!this.getTag(GroundedTag) || (!this.isOfType(Type.FLYING, true, true) && !this.hasAbility(Abilities.LEVITATE) && !this.getTag(BattlerTagType.FLOATING) && !this.getTag(SemiInvulnerableTag)); } /** diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index b2d429a4313..581cd5ff017 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -4,7 +4,7 @@ import { applyPreAttackAbAttrs, AddSecondStrikeAbAttr, IgnoreMoveEffectsAbAttr, import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { MoveAnim } from "#app/data/battle-anims"; import { BattlerTagLapseType, DamageProtectedTag, ProtectedTag, SemiInvulnerableTag, SubstituteTag } from "#app/data/battler-tags"; -import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; +import { MoveTarget, applyMoveAttrs, OverrideMoveEffectAttr, MultiHitAttr, AttackMove, FixedDamageAttr, VariableTargetAttr, MissEffectAttr, MoveFlags, applyFilteredMoveAttrs, MoveAttr, MoveEffectAttr, OneHitKOAttr, MoveEffectTrigger, ChargeAttr, MoveCategory, NoEffectAttr, HitsTagAttr, ToxicAccuracyAttr } from "#app/data/move"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { BattlerTagType } from "#app/enums/battler-tag-type"; import { Moves } from "#app/enums/moves"; @@ -404,6 +404,10 @@ export class MoveEffectPhase extends PokemonPhase { return true; } + if (target.getTag(BattlerTagType.TELEKINESIS) && !target.getTag(SemiInvulnerableTag) && !this.move.getMove().hasAttr(OneHitKOAttr)) { + return true; + } + const semiInvulnerableTag = target.getTag(SemiInvulnerableTag); if (semiInvulnerableTag && !this.move.getMove().getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType) diff --git a/src/test/moves/telekinesis.test.ts b/src/test/moves/telekinesis.test.ts new file mode 100644 index 00000000000..76c0d001f00 --- /dev/null +++ b/src/test/moves/telekinesis.test.ts @@ -0,0 +1,124 @@ +import { BattlerTagType } from "#enums/battler-tag-type"; +import { allMoves } from "#app/data/move"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { MoveResult } from "#app/field/pokemon"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest"; + +describe("Moves - Telekinesis", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.TELEKINESIS, Moves.TACKLE, Moves.MUD_SHOT, Moves.SMACK_DOWN ]) + .battleType("single") + .enemySpecies(Species.SNORLAX) + .enemyLevel(60) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.SPLASH ]); + }); + + it("Telekinesis makes the affected vulnerable to most attacking moves regardless of accuracy", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + vi.spyOn(allMoves[Moves.TACKLE], "accuracy", "get").mockReturnValue(0); + game.move.select(Moves.TACKLE); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.isFullHp()).toBe(false); + }); + + it("Telekinesis makes the affected airborne and immune to most Ground-moves", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + vi.spyOn(allMoves[Moves.MUD_SHOT], "accuracy", "get").mockReturnValue(100); + game.move.select(Moves.MUD_SHOT); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.isFullHp()).toBe(true); + }); + + it("Telekinesis can still affect Pokemon that have been transformed into invalid Pokemon", async () => { + game.override.enemyMoveset(Moves.TRANSFORM); + await game.classicMode.startBattle([ Species.DIGLETT ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + expect(enemyOpponent.summonData.speciesForm?.speciesId).toBe(Species.DIGLETT); + }); + + it("Moves like Smack Down and 1000 Arrows remove all effects of Telekinesis from the target Pokemon", async () => { + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + game.move.select(Moves.SMACK_DOWN); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined(); + }); + + it("Ingrain will remove the floating effect of Telekinesis, but not the 100% hit", async () => { + game.override.enemyMoveset([ Moves.SPLASH, Moves.INGRAIN ]); + await game.classicMode.startBattle([ Species.MAGIKARP ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + const enemyOpponent = game.scene.getEnemyPokemon()!; + + game.move.select(Moves.TELEKINESIS); + await game.forceEnemyMove(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeDefined(); + + await game.toNextTurn(); + vi.spyOn(allMoves[Moves.MUD_SHOT], "accuracy", "get").mockReturnValue(0); + game.move.select(Moves.MUD_SHOT); + await game.forceEnemyMove(Moves.INGRAIN); + await game.phaseInterceptor.to("TurnEndPhase"); + expect(enemyOpponent.getTag(BattlerTagType.TELEKINESIS)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.INGRAIN)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); + expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined(); + expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS); + }); +}); diff --git a/src/test/moves/thousand_arrows.test.ts b/src/test/moves/thousand_arrows.test.ts index 112be476955..976b4352ee4 100644 --- a/src/test/moves/thousand_arrows.test.ts +++ b/src/test/moves/thousand_arrows.test.ts @@ -85,13 +85,13 @@ describe("Moves - Thousand Arrows", () => { const enemyPokemon = game.scene.getEnemyPokemon()!; - enemyPokemon.addTag(BattlerTagType.MAGNET_RISEN, undefined, Moves.MAGNET_RISE); + enemyPokemon.addTag(BattlerTagType.FLOATING, undefined, Moves.MAGNET_RISE); game.move.select(Moves.THOUSAND_ARROWS); await game.phaseInterceptor.to(BerryPhase, false); - expect(enemyPokemon.getTag(BattlerTagType.MAGNET_RISEN)).toBeUndefined(); + expect(enemyPokemon.getTag(BattlerTagType.FLOATING)).toBeUndefined(); expect(enemyPokemon.getTag(BattlerTagType.IGNORE_FLYING)).toBeDefined(); expect(enemyPokemon.hp).toBeLessThan(enemyPokemon.getMaxHp()); } From b7eb95b7614d0df1674f4bd9e000c0e1cd80b04e Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:22:26 -0400 Subject: [PATCH 36/75] [Test] Fix several flaky tests (#4639) --- src/test/moves/safeguard.test.ts | 2 +- .../an-offer-you-cant-refuse-encounter.test.ts | 11 +++++++---- .../encounters/berries-abound-encounter.test.ts | 12 ++++++++---- .../the-pokemon-salesman-encounter.test.ts | 4 ++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/test/moves/safeguard.test.ts b/src/test/moves/safeguard.test.ts index c180ff338a5..6505162fd04 100644 --- a/src/test/moves/safeguard.test.ts +++ b/src/test/moves/safeguard.test.ts @@ -33,7 +33,7 @@ describe("Moves - Safeguard", () => { .enemyLevel(5) .starterSpecies(Species.DRATINI) .moveset([ Moves.NUZZLE, Moves.SPORE, Moves.YAWN, Moves.SPLASH ]) - .ability(Abilities.BALL_FETCH); + .ability(Abilities.UNNERVE); // Stop wild Pokemon from potentially eating Lum Berry }); it("protects from damaging moves with additional effects", async () => { diff --git a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts index 9883b4332b9..0585b4ce72b 100644 --- a/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/an-offer-you-cant-refuse-encounter.test.ts @@ -18,6 +18,7 @@ import { Moves } from "#enums/moves"; import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; import i18next from "i18next"; +import { Abilities } from "#enums/abilities"; const namespace = "mysteryEncounters/anOfferYouCantRefuse"; /** Gyarados for Indimidate */ @@ -37,10 +38,11 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { beforeEach(async () => { game = new GameManager(phaserGame); scene = game.scene; - game.override.mysteryEncounterChance(100); - game.override.startingWave(defaultWave); - game.override.startingBiome(defaultBiome); - game.override.disableTrainerWaves(); + game.override.mysteryEncounterChance(100) + .startingWave(defaultWave) + .startingBiome(defaultBiome) + .disableTrainerWaves() + .ability(Abilities.INTIMIDATE); // Extortion ability const biomeMap = new Map([ [ Biome.VOLCANO, [ MysteryEncounterType.MYSTERIOUS_CHALLENGERS ]], @@ -195,6 +197,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { }); it("should award EXP to a pokemon with a move in EXTORTION_MOVES", async () => { + game.override.ability(Abilities.SYNCHRONIZE); // Not an extortion ability, so we can test extortion move await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, [ Species.ABRA ]); const party = scene.getParty(); const abra = party.find((pkm) => pkm.species.speciesId === Species.ABRA)!; diff --git a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts index 8e286468bea..bfa3d428bc0 100644 --- a/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/berries-abound-encounter.test.ts @@ -176,10 +176,12 @@ describe("Berries Abound - Mystery Encounter", () => { const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); + scene.getParty().forEach(pkm => { + vi.spyOn(pkm, "getStat").mockReturnValue(1); // for ease return for every stat + }); + const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId; - // Setting enemy's level arbitrarily high to outspeed - config.pokemonConfigs![0].dataSource!.level = 1000; await runMysteryEncounterToEnd(game, 2, undefined, true); @@ -198,10 +200,12 @@ describe("Berries Abound - Mystery Encounter", () => { const encounterTextSpy = vi.spyOn(EncounterDialogueUtils, "showEncounterText"); await game.runToMysteryEncounter(MysteryEncounterType.BERRIES_ABOUND, defaultParty); + scene.getParty().forEach(pkm => { + vi.spyOn(pkm, "getStat").mockReturnValue(1); // for ease return for every stat + }); + const config = game.scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]; const speciesToSpawn = config.pokemonConfigs?.[0].species.speciesId; - // Setting enemy's level arbitrarily high to outspeed - config.pokemonConfigs![0].dataSource!.level = 1000; await runMysteryEncounterToEnd(game, 2, undefined, true); diff --git a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts index f8d1ffd3ded..040381c4ac3 100644 --- a/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-pokemon-salesman-encounter.test.ts @@ -147,8 +147,8 @@ describe("The Pokemon Salesman - Mystery Encounter", () => { expect(scene.getParty().length).toBe(initialPartySize + 1); - const newlyPurchasedPokemon = scene.getParty().find(p => p.name === pokemonName); - expect(newlyPurchasedPokemon).toBeDefined(); + const newlyPurchasedPokemon = scene.getParty()[scene.getParty().length - 1]; + expect(newlyPurchasedPokemon.name).toBe(pokemonName); expect(newlyPurchasedPokemon!.moveset.length > 0).toBeTruthy(); }); From 2ac688de4bf0d4715a1aa394b3c12162073ae0fe Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Sat, 12 Oct 2024 16:06:26 -0400 Subject: [PATCH 37/75] [Misc] More complete phase logging (#4651) --- src/battle-scene.ts | 6 +++++- src/phase.ts | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 40e3971b7fc..850d0baab5d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -2316,7 +2316,10 @@ export default class BattleScene extends SceneBase { } } - this.currentPhase?.start(); + if (this.currentPhase) { + console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;"); + this.currentPhase.start(); + } } overridePhase(phase: Phase): boolean { @@ -2326,6 +2329,7 @@ export default class BattleScene extends SceneBase { this.standbyPhase = this.currentPhase; this.currentPhase = phase; + console.log(`%cStart Phase ${phase.constructor.name}`, "color:green;"); phase.start(); return true; diff --git a/src/phase.ts b/src/phase.ts index 02939757112..5cf91f2c478 100644 --- a/src/phase.ts +++ b/src/phase.ts @@ -8,7 +8,6 @@ export class Phase { } start() { - console.log(`%cStart Phase ${this.constructor.name}`, "color:green;"); if (this.scene.abilityBar.shown) { this.scene.abilityBar.resetAutoHideTimer(); } From ebb76129990751aa3e92784c179a838edaaefd96 Mon Sep 17 00:00:00 2001 From: PrabbyDD <147005742+PrabbyDD@users.noreply.github.com> Date: Sat, 12 Oct 2024 15:29:36 -0700 Subject: [PATCH 38/75] [Bug] Stat Stages are now changed individually instead of all at once (#4457) Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> --- src/phases/stat-stage-change-phase.ts | 10 ++++ src/test/abilities/competitive.test.ts | 72 ++++++++++++++++++++++++++ src/test/abilities/defiant.test.ts | 70 +++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 src/test/abilities/competitive.test.ts create mode 100644 src/test/abilities/defiant.test.ts diff --git a/src/phases/stat-stage-change-phase.ts b/src/phases/stat-stage-change-phase.ts index 4418c38c849..bfe19ea9ca5 100644 --- a/src/phases/stat-stage-change-phase.ts +++ b/src/phases/stat-stage-change-phase.ts @@ -36,6 +36,16 @@ export class StatStageChangePhase extends PokemonPhase { } start() { + + // Check if multiple stats are being changed at the same time, then run SSCPhase for each of them + if (this.stats.length > 1) { + for (let i = 0; i < this.stats.length; i++) { + const stat = [ this.stats[i] ]; + this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.battlerIndex, this.selfTarget, stat, this.stages, this.showMessage, this.ignoreAbilities, this.canBeCopied, this.onChange)); + } + return this.end(); + } + const pokemon = this.getPokemon(); if (!pokemon.isActive(true)) { diff --git a/src/test/abilities/competitive.test.ts b/src/test/abilities/competitive.test.ts new file mode 100644 index 00000000000..ecb276a1b8d --- /dev/null +++ b/src/test/abilities/competitive.test.ts @@ -0,0 +1,72 @@ +import { Stat } from "#enums/stat"; +import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Competitive", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override.battleType("single") + .enemySpecies(Species.BEEDRILL) + .enemyMoveset(Moves.TICKLE) + .startingLevel(1) + .moveset([ Moves.SPLASH, Moves.CLOSE_COMBAT ]) + .ability(Abilities.COMPETITIVE); + }); + + it("lower atk and def by 1 via tickle, then increase spatk by 4 via competitive", async () => { + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4); + }); + + it("lowering your own stats should not trigger competitive", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.CLOSE_COMBAT); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0); + }); + + it("white herb should remove only the negative effects", async () => { + game.override.startingHeldItems([{ name: "WHITE_HERB" }]); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(4); + }); +}); diff --git a/src/test/abilities/defiant.test.ts b/src/test/abilities/defiant.test.ts new file mode 100644 index 00000000000..aa8d250dad7 --- /dev/null +++ b/src/test/abilities/defiant.test.ts @@ -0,0 +1,70 @@ +import { Stat } from "#enums/stat"; +import { TurnInitPhase } from "#app/phases/turn-init-phase"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Defiant", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + + game.override.battleType("single") + .enemySpecies(Species.BEEDRILL) + .enemyMoveset(Moves.TICKLE) + .startingLevel(1) + .moveset([ Moves.SPLASH, Moves.CLOSE_COMBAT ]) + .ability(Abilities.DEFIANT); + }); + + it("lower atk and def by 1 via tickle, then increase atk by 4 via defiant", async () => { + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + }); + + it("lowering your own stats should not trigger defiant", async () => { + game.override.enemyMoveset(Moves.SPLASH); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.CLOSE_COMBAT); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(-1); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0); + }); + + it("white herb should remove only the negative effects", async () => { + game.override.startingHeldItems([{ name: "WHITE_HERB" }]); + await game.classicMode.startBattle([ Species.FLYGON ]); + + const playerPokemon = game.scene.getPlayerPokemon()!; + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to(TurnInitPhase); + + expect(playerPokemon.getStatStage(Stat.DEF)).toBe(0); + expect(playerPokemon.getStatStage(Stat.ATK)).toBe(3); + }); +}); From caf29e2ce3fe5bd194a0a42eac9b91f32b65dc89 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:42:20 -0400 Subject: [PATCH 39/75] [Documentation] Document all (P) moves (#4650) * Document all (P) moves * Fix some typos * Fix more typos * Address innerthunder comments * Add circle throw and dragon tail (P) --- src/data/move.ts | 81 +++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index bd3706545f9..8e9977337cc 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -365,6 +365,14 @@ export default class Move implements Localizable { return this; } + /** + * Internal dev flag for documenting edge cases. When using this, please document the known edge case. + * @returns the called object {@linkcode Move} + */ + edgeCase(): this { + return this; + } + /** * Marks the move as "partial": appends texts to the move name * @returns the called object {@linkcode Move} @@ -7084,7 +7092,8 @@ export function initMoves() { .attr(ForceSwitchOutAttr) .ignoresSubstitute() .hidesTarget() - .windMove(), + .windMove() + .partial(), // Should force random switches new AttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1) .attr(ChargeAttr, ChargeAnim.FLY_CHARGING, i18next.t("moveTriggers:flewUpHigh", { pokemonName: "{USER}" }), BattlerTagType.FLYING) .condition(failOnGravityCondition) @@ -7161,7 +7170,8 @@ export function initMoves() { new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1) .attr(ForceSwitchOutAttr) .soundBased() - .hidesTarget(), + .hidesTarget() + .partial(), // Should force random switching new StatusMove(Moves.SING, Type.NORMAL, 55, 15, -1, 0, 1) .attr(StatusEffectAttr, StatusEffect.SLEEP) .soundBased(), @@ -7302,7 +7312,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), new AttackMove(Moves.QUICK_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1), new AttackMove(Moves.RAGE, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1) - .partial(), + .partial(), // No effect implemented new SelfStatusMove(Moves.TELEPORT, Type.PSYCHIC, -1, 20, -1, -6, 1) .attr(ForceSwitchOutAttr, true) .hidesUser(), @@ -7617,7 +7627,7 @@ export function initMoves() { new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2) .attr(StatStageChangeAttr, [ Stat.ATK ], -2), new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2) - .partial() + .partial() // Does not lock the user, also does not increase damage properly .attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL), new AttackMove(Moves.FALSE_SWIPE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 2) .attr(SurviveDamageAttr), @@ -7688,7 +7698,7 @@ export function initMoves() { .ignoresSubstitute() .condition((user, target, move) => new EncoreTag(user.id).canAdd(target)), new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2) - .partial(), + .partial(), // No effect implemented new AttackMove(Moves.RAPID_SPIN, Type.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, 100, 0, 2) .attr(StatStageChangeAttr, [ Stat.SPD ], 1, true) .attr(RemoveBattlerTagAttr, [ @@ -7753,7 +7763,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -1) .ballBombMove(), new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2) - .partial() + .partial() // Complete buggy mess .attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", { pokemonName: "{USER}" })), new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2) .attr(StatStageChangeAttr, [ Stat.DEF ], -1), @@ -7771,7 +7781,7 @@ export function initMoves() { .ignoresVirtual() .soundBased() .target(MoveTarget.RANDOM_NEAR_ENEMY) - .partial(), + .partial(), // Does not lock the user, does not stop Pokemon from sleeping new SelfStatusMove(Moves.STOCKPILE, Type.NORMAL, -1, 20, -1, 0, 3) .condition(user => (user.getTag(StockpilingTag)?.stockpiledCount ?? 0) < 3) .attr(AddBattlerTagAttr, BattlerTagType.STOCKPILING, true), @@ -7794,7 +7804,7 @@ export function initMoves() { .target(MoveTarget.BOTH_SIDES), new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3) .ignoresSubstitute() - .partial() // Incomplete implementation because of Uproar's partial implementation + .edgeCase() // Incomplete implementation because of Uproar's partial implementation .attr(AddBattlerTagAttr, BattlerTagType.TORMENT, false, true, 1), new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPATK ], 1) @@ -7883,7 +7893,7 @@ export function initMoves() { .unimplemented(), new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3) .makesContact(false) - .partial(), + .partial(), // No effect implemented new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3) .attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", { pokemonName: "{USER}" }), BattlerTagType.UNDERWATER, true) .attr(GulpMissileTagAttr) @@ -7914,7 +7924,7 @@ export function initMoves() { .attr(AddArenaTagAttr, ArenaTagType.MUD_SPORT, 5) .target(MoveTarget.BOTH_SIDES), new AttackMove(Moves.ICE_BALL, Type.ICE, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 3) - .partial() + .partial() // Does not lock the user properly, does not increase damage correctly .attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL) .ballBombMove(), new AttackMove(Moves.NEEDLE_ARM, Type.GRASS, MoveCategory.PHYSICAL, 60, 100, 15, 30, 0, 3) @@ -8057,7 +8067,7 @@ export function initMoves() { .attr(ConfuseAttr) .pulseMove(), new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3) - .partial() + .partial() // Complete buggy mess .attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", { pokemonName: "{USER}" })), new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3) .attr(StatStageChangeAttr, [ Stat.SPATK ], -2, true), @@ -8467,7 +8477,7 @@ export function initMoves() { .attr(AfterYouAttr), new AttackMove(Moves.ROUND, Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5) .soundBased() - .partial(), + .partial(), // No effect implemented new AttackMove(Moves.ECHOED_VOICE, Type.NORMAL, MoveCategory.SPECIAL, 40, 100, 15, -1, 0, 5) .attr(ConsecutiveUseMultiBasePowerAttr, 5, false) .soundBased(), @@ -8509,7 +8519,8 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK ], 1, true) .attr(StatStageChangeAttr, [ Stat.SPD ], 2, true), new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5) - .attr(ForceSwitchOutAttr), + .attr(ForceSwitchOutAttr) + .partial(), // Should force random switches new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5) .target(MoveTarget.ALL_NEAR_ENEMIES) .attr(RemoveHeldItemAttr, true), @@ -8577,7 +8588,8 @@ export function initMoves() { .attr(CritOnlyAttr), new AttackMove(Moves.DRAGON_TAIL, Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5) .attr(ForceSwitchOutAttr) - .hidesTarget(), + .hidesTarget() + .partial(), // Should force random switches new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], 1, true), new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5) @@ -8705,7 +8717,7 @@ export function initMoves() { .ignoresVirtual(), new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6) .attr(AddTypeAttr, Type.GHOST) - .partial(), + .edgeCase(), // Weird interaction with Forest's Curse, reflect type, burn up new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1) .soundBased(), @@ -8718,7 +8730,7 @@ export function initMoves() { .triageMove(), new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6) .attr(AddTypeAttr, Type.GRASS) - .partial(), + .edgeCase(), // Weird interaction with Trick or Treat, reflect type, burn up new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6) .windMove() .makesContact(false) @@ -8726,7 +8738,7 @@ export function initMoves() { new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6) .attr(StatusEffectAttr, StatusEffect.FREEZE) .attr(WaterSuperEffectTypeMultiplierAttr) - .partial(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell. + .edgeCase(), // This currently just multiplies the move's power instead of changing its effectiveness. It also doesn't account for abilities that modify type effectiveness such as tera shell. new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES), @@ -9104,15 +9116,15 @@ export function initMoves() { /* Unused */ new AttackMove(Moves.SINISTER_ARROW_RAID, Type.GHOST, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) .makesContact(false) - .partial() + .edgeCase() // I assume it's because the user needs spirit shackle and decidueye .ignoresVirtual(), new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7) .attr(AlwaysHitMinimizeAttr) .attr(HitsTagAttr, BattlerTagType.MINIMIZED, true) - .partial() + .edgeCase() // I assume it's because it needs darkest lariat and incineroar .ignoresVirtual(), new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs sparkling aria and primarina .ignoresVirtual(), new AttackMove(Moves.GUARDIAN_OF_ALOLA, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7) .unimplemented() @@ -9121,10 +9133,10 @@ export function initMoves() { .unimplemented() .ignoresVirtual(), new AttackMove(Moves.STOKED_SPARKSURFER, Type.ELECTRIC, MoveCategory.SPECIAL, 175, -1, 1, 100, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs thunderbolt and Alola Raichu .ignoresVirtual(), new AttackMove(Moves.PULVERIZING_PANCAKE, Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs giga impact and snorlax .ignoresVirtual(), new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 2, true) @@ -9155,13 +9167,13 @@ export function initMoves() { .attr(RechargeAttr), new AttackMove(Moves.SPECTRAL_THIEF, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7) .ignoresSubstitute() - .partial(), + .partial(), // Does not steal stats new AttackMove(Moves.SUNSTEEL_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7) .ignoresAbilities() - .partial(), + .edgeCase(), // Should not ignore abilities when called virtually (metronome) new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) .ignoresAbilities() - .partial(), + .edgeCase(), // Should not ignore abilities when called virtually (metronome) new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1), new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7) @@ -9172,7 +9184,7 @@ export function initMoves() { .attr(FormChangeItemTypeAttr), /* Unused */ new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it's because it needs thunderbolt and pikachu in a cap .ignoresVirtual(), /* End Unused */ new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7) @@ -9185,7 +9197,7 @@ export function initMoves() { new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) .ignoresAbilities() - .partial(), + .edgeCase(), // Should not ignore abilities when called virtually (metronome) /* Unused */ new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7) .attr(PhotonGeyserCategoryAttr) @@ -9198,7 +9210,7 @@ export function initMoves() { .ignoresAbilities() .ignoresVirtual(), new AttackMove(Moves.LETS_SNUGGLE_FOREVER, Type.FAIRY, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) - .partial() + .edgeCase() // I assume it needs play rough and mimikyu .ignoresVirtual(), new AttackMove(Moves.SPLINTERED_STORMSHARDS, Type.ROCK, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7) .attr(ClearTerrainAttr) @@ -9208,7 +9220,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true, undefined, undefined, undefined, undefined, true) .soundBased() .target(MoveTarget.ALL_NEAR_ENEMIES) - .partial() + .edgeCase() // I assume it needs clanging scales and Kommo-O .ignoresVirtual(), /* End Unused */ new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 50, 100, 15, -1, 2, 7) //LGPE Implementation @@ -9271,14 +9283,14 @@ export function initMoves() { new AttackMove(Moves.JAW_LOCK, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8) .attr(JawLockAttr) .bitingMove(), - new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki + new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) .attr(EatBerryAttr) .attr(StatStageChangeAttr, [ Stat.DEF ], 2, true) .condition((user) => { const userBerries = user.scene.findModifiers(m => m instanceof BerryModifier, user.isPlayer()); return userBerries.length > 0; }) - .partial(), + .edgeCase(), // Stuff Cheeks should not be selectable when the user does not have a berry, see wiki new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8) .attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ], 1, true) .attr(AddBattlerTagAttr, BattlerTagType.NO_RETREAT, true, false) @@ -9292,7 +9304,7 @@ export function initMoves() { new AttackMove(Moves.DRAGON_DARTS, Type.DRAGON, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 8) .attr(MultiHitAttr, MultiHitType._2) .makesContact(false) - .partial(), + .partial(), // smart targetting is unimplemented new StatusMove(Moves.TEATIME, Type.NORMAL, -1, 10, -1, 0, 8) .attr(EatBerryAttr) .target(MoveTarget.ALL), @@ -9731,7 +9743,7 @@ export function initMoves() { .attr(StatStageChangeAttr, [ Stat.SPDEF ], -2), new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9) .makesContact(false) - .partial(), + .partial(), // No effect implemented (requires Commander) new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9) .punchingMove(), new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, -1, 0, 9) @@ -9943,8 +9955,7 @@ export function initMoves() { new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9) .attr(FlinchAttr) .condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()]?.move?.move!].priority > 0 ) // TODO: is this bang correct? - //TODO: Should also apply when target move priority increased by ability ex. gale wings - .partial(), + .partial(), // Should also apply when target move priority increased by ability ex. gale wings new AttackMove(Moves.MALIGNANT_CHAIN, Type.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9) .attr(StatusEffectAttr, StatusEffect.TOXIC) ); From 8e7aea0f8990450d5d06e273f83188fffa87857e Mon Sep 17 00:00:00 2001 From: damocleas Date: Sat, 12 Oct 2024 23:51:14 -0400 Subject: [PATCH 40/75] Fixed Charizard and Kingler BST, fixed ability indexing for gmax forms (#4652) --- src/data/pokemon-species.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 1ceb5971f6a..8bb23cfc208 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -975,7 +975,7 @@ export function initSpecies() { new PokemonSpecies(Species.VENUSAUR, 1, false, false, false, "Seed Pokémon", Type.GRASS, Type.POISON, 2, 100, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, GrowthRate.MEDIUM_SLOW, 87.5, true, true, new PokemonForm("Normal", "", Type.GRASS, Type.POISON, 2, 100, Abilities.OVERGROW, Abilities.NONE, Abilities.CHLOROPHYLL, 525, 80, 82, 83, 100, 100, 80, 45, 50, 263, true, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.GRASS, Type.POISON, 2.4, 155.5, Abilities.THICK_FAT, Abilities.THICK_FAT, Abilities.THICK_FAT, 625, 80, 100, 123, 122, 120, 80, 45, 50, 263, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, Type.POISON, 24, 999.9, Abilities.EFFECT_SPORE, Abilities.EFFECT_SPORE, Abilities.EFFECT_SPORE, 625, 120, 82, 98, 130, 115, 80, 45, 50, 263, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, Type.POISON, 24, 999.9, Abilities.EFFECT_SPORE, Abilities.NONE, Abilities.EFFECT_SPORE, 625, 120, 82, 98, 130, 115, 80, 45, 50, 263, true), ), new PokemonSpecies(Species.CHARMANDER, 1, false, false, false, "Lizard Pokémon", Type.FIRE, null, 0.6, 8.5, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 309, 39, 52, 43, 60, 50, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.CHARMELEON, 1, false, false, false, "Flame Pokémon", Type.FIRE, null, 1.1, 19, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 405, 58, 64, 58, 80, 65, 80, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), @@ -983,20 +983,20 @@ export function initSpecies() { new PokemonForm("Normal", "", Type.FIRE, Type.FLYING, 1.7, 90.5, Abilities.BLAZE, Abilities.NONE, Abilities.SOLAR_POWER, 534, 78, 84, 78, 109, 85, 100, 45, 50, 267, false, null, true), new PokemonForm("Mega X", SpeciesFormKey.MEGA_X, Type.FIRE, Type.DRAGON, 1.7, 110.5, Abilities.TOUGH_CLAWS, Abilities.NONE, Abilities.TOUGH_CLAWS, 634, 78, 130, 111, 130, 85, 100, 45, 50, 267), new PokemonForm("Mega Y", SpeciesFormKey.MEGA_Y, Type.FIRE, Type.FLYING, 1.7, 100.5, Abilities.DROUGHT, Abilities.NONE, Abilities.DROUGHT, 634, 78, 104, 78, 159, 115, 100, 45, 50, 267), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, Type.FLYING, 28, 999.9, Abilities.BERSERK, Abilities.BERSERK, Abilities.BERSERK, 634, 118, 84, 93, 139, 110, 100, 45, 50, 267), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, Type.FLYING, 28, 999.9, Abilities.BERSERK, Abilities.NONE, Abilities.BERSERK, 634, 118, 84, 93, 139, 100, 100, 45, 50, 267), ), new PokemonSpecies(Species.SQUIRTLE, 1, false, false, false, "Tiny Turtle Pokémon", Type.WATER, null, 0.5, 9, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 314, 44, 48, 65, 50, 64, 43, 45, 50, 63, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.WARTORTLE, 1, false, false, false, "Turtle Pokémon", Type.WATER, null, 1, 22.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 405, 59, 63, 80, 65, 80, 58, 45, 50, 142, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.BLASTOISE, 1, false, false, false, "Shellfish Pokémon", Type.WATER, null, 1.6, 85.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.WATER, null, 1.6, 85.5, Abilities.TORRENT, Abilities.NONE, Abilities.RAIN_DISH, 530, 79, 83, 100, 85, 105, 78, 45, 50, 265, false, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.WATER, null, 1.6, 101.1, Abilities.MEGA_LAUNCHER, Abilities.NONE, Abilities.MEGA_LAUNCHER, 630, 79, 103, 120, 135, 115, 78, 45, 50, 265), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, Type.STEEL, 25, 999.9, Abilities.SHELL_ARMOR, Abilities.SHELL_ARMOR, Abilities.SHELL_ARMOR, 630, 119, 83, 135, 115, 110, 68, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, Type.STEEL, 25, 999.9, Abilities.SHELL_ARMOR, Abilities.NONE, Abilities.SHELL_ARMOR, 630, 119, 83, 135, 115, 110, 68, 45, 50, 265), ), new PokemonSpecies(Species.CATERPIE, 1, false, false, false, "Worm Pokémon", Type.BUG, null, 0.3, 2.9, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 45, 30, 35, 20, 20, 45, 255, 50, 39, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.METAPOD, 1, false, false, false, "Cocoon Pokémon", Type.BUG, null, 0.7, 9.9, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 50, 20, 55, 25, 25, 30, 120, 50, 72, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.BUTTERFREE, 1, false, false, false, "Butterfly Pokémon", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, GrowthRate.MEDIUM_FAST, 50, true, true, new PokemonForm("Normal", "", Type.BUG, Type.FLYING, 1.1, 32, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.TINTED_LENS, 395, 60, 45, 50, 90, 80, 70, 45, 50, 198, true, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 999.9, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, Abilities.COMPOUND_EYES, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.BUG, Type.FLYING, 17, 999.9, Abilities.COMPOUND_EYES, Abilities.NONE, Abilities.COMPOUND_EYES, 495, 85, 35, 80, 120, 90, 85, 45, 50, 198, true), ), new PokemonSpecies(Species.WEEDLE, 1, false, false, false, "Hairy Bug Pokémon", Type.BUG, Type.POISON, 0.3, 3.2, Abilities.SHIELD_DUST, Abilities.NONE, Abilities.RUN_AWAY, 195, 40, 35, 30, 20, 20, 50, 255, 70, 39, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.KAKUNA, 1, false, false, false, "Cocoon Pokémon", Type.BUG, Type.POISON, 0.6, 10, Abilities.SHED_SKIN, Abilities.NONE, Abilities.SHED_SKIN, 205, 45, 25, 50, 25, 25, 35, 120, 70, 72, GrowthRate.MEDIUM_FAST, 50, false), @@ -1025,7 +1025,7 @@ export function initSpecies() { new PokemonForm("Cute Cosplay", "cute-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom new PokemonForm("Smart Cosplay", "smart-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom new PokemonForm("Tough Cosplay", "tough-cosplay", Type.ELECTRIC, null, 0.4, 6, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 430, 45, 80, 50, 75, 60, 120, 190, 50, 112, true, null, true), //Custom - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.ELECTRIC, null, 21, 999.9, Abilities.LIGHTNING_ROD, Abilities.LIGHTNING_ROD, Abilities.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112), //+100 BST from Partner Form + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.ELECTRIC, null, 21, 999.9, Abilities.LIGHTNING_ROD, Abilities.NONE, Abilities.LIGHTNING_ROD, 530, 125, 95, 60, 90, 70, 90, 190, 50, 112), //+100 BST from Partner Form ), new PokemonSpecies(Species.RAICHU, 1, false, false, false, "Mouse Pokémon", Type.ELECTRIC, null, 0.8, 30, Abilities.STATIC, Abilities.NONE, Abilities.LIGHTNING_ROD, 485, 60, 90, 55, 90, 80, 110, 75, 50, 243, GrowthRate.MEDIUM_FAST, 50, true), new PokemonSpecies(Species.SANDSHREW, 1, false, false, false, "Mouse Pokémon", Type.GROUND, null, 0.6, 12, Abilities.SAND_VEIL, Abilities.NONE, Abilities.SAND_RUSH, 300, 50, 75, 85, 20, 30, 40, 255, 50, 60, GrowthRate.MEDIUM_FAST, 50, false), @@ -1110,7 +1110,7 @@ export function initSpecies() { new PokemonSpecies(Species.GENGAR, 1, false, false, false, "Shadow Pokémon", Type.GHOST, Type.POISON, 1.5, 40.5, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, GrowthRate.MEDIUM_SLOW, 50, false, true, new PokemonForm("Normal", "", Type.GHOST, Type.POISON, 1.5, 40.5, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 500, 60, 65, 60, 130, 75, 110, 45, 50, 250, false, null, true), new PokemonForm("Mega", SpeciesFormKey.MEGA, Type.GHOST, Type.POISON, 1.4, 40.5, Abilities.SHADOW_TAG, Abilities.NONE, Abilities.NONE, 600, 60, 65, 80, 170, 95, 130, 45, 50, 250), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GHOST, Type.POISON, 20, 999.9, Abilities.CURSED_BODY, Abilities.CURSED_BODY, Abilities.CURSED_BODY, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GHOST, Type.POISON, 20, 999.9, Abilities.CURSED_BODY, Abilities.NONE, Abilities.NONE, 600, 140, 65, 70, 140, 85, 100, 45, 50, 250), ), new PokemonSpecies(Species.ONIX, 1, false, false, false, "Rock Snake Pokémon", Type.ROCK, Type.GROUND, 8.8, 210, Abilities.ROCK_HEAD, Abilities.STURDY, Abilities.WEAK_ARMOR, 385, 35, 45, 160, 30, 45, 70, 45, 50, 77, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.DROWZEE, 1, false, false, false, "Hypnosis Pokémon", Type.PSYCHIC, null, 1, 32.4, Abilities.INSOMNIA, Abilities.FOREWARN, Abilities.INNER_FOCUS, 328, 60, 48, 45, 43, 90, 42, 190, 70, 66, GrowthRate.MEDIUM_FAST, 50, false), @@ -1118,7 +1118,7 @@ export function initSpecies() { new PokemonSpecies(Species.KRABBY, 1, false, false, false, "River Crab Pokémon", Type.WATER, null, 0.4, 6.5, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 325, 30, 105, 90, 25, 25, 50, 225, 50, 65, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.KINGLER, 1, false, false, false, "Pincer Pokémon", Type.WATER, null, 1.3, 60, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, GrowthRate.MEDIUM_FAST, 50, false, true, new PokemonForm("Normal", "", Type.WATER, null, 1.3, 60, Abilities.HYPER_CUTTER, Abilities.SHELL_ARMOR, Abilities.SHEER_FORCE, 475, 55, 130, 115, 50, 50, 75, 60, 50, 166, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 19, 999.9, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, 575, 90, 155, 140, 50, 80, 70, 60, 50, 166), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 19, 999.9, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, Abilities.TOUGH_CLAWS, 575, 90, 155, 140, 50, 70, 70, 60, 50, 166), ), new PokemonSpecies(Species.VOLTORB, 1, false, false, false, "Ball Pokémon", Type.ELECTRIC, null, 0.5, 10.4, Abilities.SOUNDPROOF, Abilities.STATIC, Abilities.AFTERMATH, 330, 40, 30, 50, 55, 55, 100, 190, 70, 66, GrowthRate.MEDIUM_FAST, null, false), new PokemonSpecies(Species.ELECTRODE, 1, false, false, false, "Ball Pokémon", Type.ELECTRIC, null, 1.2, 66.6, Abilities.SOUNDPROOF, Abilities.STATIC, Abilities.AFTERMATH, 490, 60, 50, 70, 80, 80, 150, 60, 70, 172, GrowthRate.MEDIUM_FAST, null, false), @@ -2298,25 +2298,25 @@ export function initSpecies() { new PokemonSpecies(Species.MELTAN, 7, false, false, true, "Hex Nut Pokémon", Type.STEEL, null, 0.2, 8, Abilities.MAGNET_PULL, Abilities.NONE, Abilities.NONE, 300, 46, 65, 65, 55, 35, 34, 3, 0, 150, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.MELMETAL, 7, false, false, true, "Hex Nut Pokémon", Type.STEEL, null, 2.5, 800, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, GrowthRate.SLOW, null, false, true, new PokemonForm("Normal", "", Type.STEEL, null, 2.5, 800, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 600, 135, 143, 143, 80, 65, 34, 3, 0, 300, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, null, 25, 999.9, Abilities.IRON_FIST, Abilities.IRON_FIST, Abilities.IRON_FIST, 700, 175, 165, 155, 85, 75, 45, 3, 0, 300), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, null, 25, 999.9, Abilities.IRON_FIST, Abilities.NONE, Abilities.NONE, 700, 175, 165, 155, 85, 75, 45, 3, 0, 300), ), new PokemonSpecies(Species.GROOKEY, 8, false, false, false, "Chimp Pokémon", Type.GRASS, null, 0.3, 5, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 310, 50, 65, 50, 40, 40, 65, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.THWACKEY, 8, false, false, false, "Beat Pokémon", Type.GRASS, null, 0.7, 14, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 420, 70, 85, 70, 55, 60, 80, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.RILLABOOM, 8, false, false, false, "Drummer Pokémon", Type.GRASS, null, 2.1, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.GRASS, null, 2.1, 90, Abilities.OVERGROW, Abilities.NONE, Abilities.GRASSY_SURGE, 530, 100, 125, 90, 60, 70, 85, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, null, 28, 999.9, Abilities.GRASSY_SURGE, Abilities.GRASSY_SURGE, Abilities.GRASSY_SURGE, 630, 125, 150, 105, 85, 85, 80, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.GRASS, null, 28, 999.9, Abilities.GRASSY_SURGE, Abilities.NONE, Abilities.GRASSY_SURGE, 630, 125, 150, 105, 85, 85, 80, 45, 50, 265), ), new PokemonSpecies(Species.SCORBUNNY, 8, false, false, false, "Rabbit Pokémon", Type.FIRE, null, 0.3, 4.5, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 310, 50, 71, 40, 40, 40, 69, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.RABOOT, 8, false, false, false, "Rabbit Pokémon", Type.FIRE, null, 0.6, 9, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 420, 65, 86, 60, 55, 60, 94, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.CINDERACE, 8, false, false, false, "Striker Pokémon", Type.FIRE, null, 1.4, 33, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.FIRE, null, 1.4, 33, Abilities.BLAZE, Abilities.NONE, Abilities.LIBERO, 530, 80, 116, 75, 65, 75, 119, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, null, 27, 999.9, Abilities.LIBERO, Abilities.LIBERO, Abilities.LIBERO, 630, 100, 146, 80, 90, 80, 134, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FIRE, null, 27, 999.9, Abilities.LIBERO, Abilities.NONE, Abilities.LIBERO, 630, 100, 146, 80, 90, 80, 134, 45, 50, 265), ), new PokemonSpecies(Species.SOBBLE, 8, false, false, false, "Water Lizard Pokémon", Type.WATER, null, 0.3, 4, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 310, 50, 40, 40, 70, 40, 70, 45, 50, 62, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.DRIZZILE, 8, false, false, false, "Water Lizard Pokémon", Type.WATER, null, 0.7, 11.5, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 420, 65, 60, 55, 95, 55, 90, 45, 50, 147, GrowthRate.MEDIUM_SLOW, 87.5, false), new PokemonSpecies(Species.INTELEON, 8, false, false, false, "Secret Agent Pokémon", Type.WATER, null, 1.9, 45.2, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, GrowthRate.MEDIUM_SLOW, 87.5, false, true, new PokemonForm("Normal", "", Type.WATER, null, 1.9, 45.2, Abilities.TORRENT, Abilities.NONE, Abilities.SNIPER, 530, 70, 85, 65, 125, 65, 120, 45, 50, 265, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 40, 999.9, Abilities.SNIPER, Abilities.SNIPER, Abilities.SNIPER, 630, 95, 97, 77, 147, 77, 137, 45, 50, 265), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.WATER, null, 40, 999.9, Abilities.SNIPER, Abilities.NONE, Abilities.SNIPER, 630, 95, 97, 77, 147, 77, 137, 45, 50, 265), ), new PokemonSpecies(Species.SKWOVET, 8, false, false, false, "Cheeky Pokémon", Type.NORMAL, null, 0.3, 2.5, Abilities.CHEEK_POUCH, Abilities.NONE, Abilities.GLUTTONY, 275, 70, 55, 55, 35, 35, 25, 255, 50, 55, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.GREEDENT, 8, false, false, false, "Greedy Pokémon", Type.NORMAL, null, 0.6, 6, Abilities.CHEEK_POUCH, Abilities.NONE, Abilities.GLUTTONY, 460, 120, 95, 95, 55, 75, 20, 90, 50, 161, GrowthRate.MEDIUM_FAST, 50, false), @@ -2422,7 +2422,7 @@ export function initSpecies() { new PokemonForm("Ruby Swirl", "ruby-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), new PokemonForm("Caramel Swirl", "caramel-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), new PokemonForm("Rainbow Swirl", "rainbow-swirl", Type.FAIRY, null, 0.3, 0.5, Abilities.SWEET_VEIL, Abilities.NONE, Abilities.AROMA_VEIL, 495, 65, 60, 75, 110, 121, 64, 100, 50, 173, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FAIRY, null, 30, 999.9, Abilities.MISTY_SURGE, Abilities.MISTY_SURGE, Abilities.MISTY_SURGE, 595, 135, 60, 75, 130, 131, 64, 100, 50, 173), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.FAIRY, null, 30, 999.9, Abilities.MISTY_SURGE, Abilities.NONE, Abilities.MISTY_SURGE, 595, 135, 60, 75, 130, 131, 64, 100, 50, 173), ), new PokemonSpecies(Species.FALINKS, 8, false, false, false, "Formation Pokémon", Type.FIGHTING, null, 3, 62, Abilities.BATTLE_ARMOR, Abilities.NONE, Abilities.DEFIANT, 470, 65, 100, 100, 70, 60, 75, 45, 50, 165, GrowthRate.MEDIUM_FAST, null, false), new PokemonSpecies(Species.PINCURCHIN, 8, false, false, false, "Sea Urchin Pokémon", Type.ELECTRIC, null, 0.3, 1, Abilities.LIGHTNING_ROD, Abilities.NONE, Abilities.ELECTRIC_SURGE, 435, 48, 101, 95, 91, 85, 15, 75, 50, 152, GrowthRate.MEDIUM_FAST, 50, false), @@ -2444,7 +2444,7 @@ export function initSpecies() { new PokemonSpecies(Species.CUFANT, 8, false, false, false, "Copperderm Pokémon", Type.STEEL, null, 1.2, 100, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 330, 72, 80, 49, 40, 49, 40, 190, 50, 66, GrowthRate.MEDIUM_FAST, 50, false), new PokemonSpecies(Species.COPPERAJAH, 8, false, false, false, "Copperderm Pokémon", Type.STEEL, null, 3, 650, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, GrowthRate.MEDIUM_FAST, 50, false, true, new PokemonForm("Normal", "", Type.STEEL, null, 3, 650, Abilities.SHEER_FORCE, Abilities.NONE, Abilities.HEAVY_METAL, 500, 122, 130, 69, 80, 69, 30, 90, 50, 175, false, null, true), - new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, Type.GROUND, 23, 999.9, Abilities.MOLD_BREAKER, Abilities.MOLD_BREAKER, Abilities.MOLD_BREAKER, 600, 167, 155, 89, 80, 89, 20, 90, 50, 175), + new PokemonForm("G-Max", SpeciesFormKey.GIGANTAMAX, Type.STEEL, Type.GROUND, 23, 999.9, Abilities.MOLD_BREAKER, Abilities.NONE, Abilities.MOLD_BREAKER, 600, 167, 155, 89, 80, 89, 20, 90, 50, 175), ), new PokemonSpecies(Species.DRACOZOLT, 8, false, false, false, "Fossil Pokémon", Type.ELECTRIC, Type.DRAGON, 1.8, 190, Abilities.VOLT_ABSORB, Abilities.HUSTLE, Abilities.SAND_RUSH, 505, 90, 100, 90, 80, 70, 75, 45, 50, 177, GrowthRate.SLOW, null, false), new PokemonSpecies(Species.ARCTOZOLT, 8, false, false, false, "Fossil Pokémon", Type.ELECTRIC, Type.ICE, 2.3, 150, Abilities.VOLT_ABSORB, Abilities.STATIC, Abilities.SLUSH_RUSH, 505, 90, 100, 90, 90, 80, 55, 45, 50, 177, GrowthRate.SLOW, null, false), From 391f38c3c8bd6106f7c38f4a451883add3bac310 Mon Sep 17 00:00:00 2001 From: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Date: Sun, 13 Oct 2024 00:45:38 -0400 Subject: [PATCH 41/75] [Documentation] Document all (P) abilities (#4649) * Document partial abilities * Fix typo * Address comments * Fix typo Terapagos -> Ogerpon --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/ability.ts | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 6a391818866..07fd48e2f91 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -118,6 +118,14 @@ export class Ability implements Localizable { this.nameAppend += " (N)"; return this; } + + /** + * Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case. + * @returns the ability + */ + edgeCase(): this { + return this; + } } type AbAttrApplyFunc = (attr: TAttr, passive: boolean) => boolean | Promise; @@ -4906,7 +4914,7 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.SHIELD_DUST, 3) .attr(IgnoreMoveEffectsAbAttr) - .partial(), + .edgeCase(), // Does not work with secret power (unimplemented) new Ability(Abilities.OWN_TEMPO, 3) .attr(BattlerTagImmunityAbAttr, BattlerTagType.CONFUSED) .attr(IntimidateImmunityAbAttr) @@ -4951,7 +4959,7 @@ export function initAbilities() { .ignorable(), new Ability(Abilities.SERENE_GRACE, 3) .attr(MoveEffectChanceMultiplierAbAttr, 2) - .partial(), + .edgeCase(), // does not work with secret power (unimplemented) new Ability(Abilities.SWIFT_SWIM, 3) .attr(StatMultiplierAbAttr, Stat.SPD, 2) .condition(getWeatherCondition(WeatherType.RAIN, WeatherType.HEAVY_RAIN)), @@ -5235,7 +5243,8 @@ export function initAbilities() { new Ability(Abilities.SHEER_FORCE, 5) .attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096) .attr(MoveEffectChanceMultiplierAbAttr, 0) - .partial(), + .edgeCase() // Should disable shell bell and Meloetta's relic song transformation + .edgeCase(), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented new Ability(Abilities.CONTRARY, 5) .attr(StatStageChangeMultiplierAbAttr, -1) .ignorable(), @@ -5278,7 +5287,7 @@ export function initAbilities() { /** Rate is doubled when under sun {@link https://dex.pokemonshowdown.com/abilities/harvest} */ (pokemon) => 0.5 * (getWeatherCondition(WeatherType.SUNNY, WeatherType.HARSH_SUN)(pokemon) ? 2 : 1) ) - .partial(), + .edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented) new Ability(Abilities.TELEPATHY, 5) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove) .ignorable(), @@ -5357,7 +5366,7 @@ export function initAbilities() { .bypassFaint(), new Ability(Abilities.VICTORY_STAR, 5) .attr(StatMultiplierAbAttr, Stat.ACC, 1.1) - .partial(), + .partial(), // Does not boost ally's accuracy new Ability(Abilities.TURBOBLAZE, 5) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTurboblaze", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) .attr(MoveAbilityBypassAbAttr), @@ -5468,7 +5477,7 @@ export function initAbilities() { .attr(UnsuppressableAbilityAbAttr) .attr(NoFusionAbilityAbAttr) .bypassFaint() - .partial(), + .partial(), // Meteor form should protect against status effects and yawn new Ability(Abilities.STAKEOUT, 7) .attr(MovePowerBoostAbAttr, (user, target, move) => user?.scene.currentBattle.turnCommands[target?.getBattlerIndex() ?? BattlerIndex.ATTACKER]?.command === Command.POKEMON, 2), new Ability(Abilities.WATER_BUBBLE, 7) @@ -5536,9 +5545,9 @@ export function initAbilities() { .attr(NoFusionAbilityAbAttr) .bypassFaint() .partial(), - new Ability(Abilities.CORROSION, 7) // TODO: Test Corrosion against Magic Bounce once it is implemented + new Ability(Abilities.CORROSION, 7) .attr(IgnoreTypeStatusEffectImmunityAbAttr, [ StatusEffect.POISON, StatusEffect.TOXIC ], [ Type.STEEL, Type.POISON ]) - .partial(), + .edgeCase(), // Should interact correctly with magic coat/bounce (not yet implemented), fling with toxic orb (not implemented yet), and synchronize (not fully implemented yet) new Ability(Abilities.COMATOSE, 7) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) @@ -5693,7 +5702,7 @@ export function initAbilities() { new Ability(Abilities.WANDERING_SPIRIT, 8) .attr(PostDefendAbilitySwapAbAttr) .bypassFaint() - .partial(), + .edgeCase(), // interacts incorrectly with rock head. It's meant to switch abilities before recoil would apply so that a pokemon with rock head would lose rock head first and still take the recoil new Ability(Abilities.GORILLA_TACTICS, 8) .attr(GorillaTacticsAbAttr), new Ability(Abilities.NEUTRALIZING_GAS, 8) @@ -5702,7 +5711,7 @@ export function initAbilities() { .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) - .partial(), + .partial(), // A bunch of weird interactions with other abilities being suppressed then unsuppressed new Ability(Abilities.PASTEL_VEIL, 8) .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) @@ -5807,7 +5816,7 @@ export function initAbilities() { new Ability(Abilities.GOOD_AS_GOLD, 9) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon !== attacker && move.category === MoveCategory.STATUS) .ignorable() - .partial(), + .partial(), // Lots of weird interactions with moves and abilities such as negating status moves that target the field new Ability(Abilities.VESSEL_OF_RUIN, 9) .attr(FieldMultiplyStatAbAttr, Stat.SPATK, 0.75) .attr(PostSummonMessageAbAttr, (user) => i18next.t("abilityTriggers:postSummonVesselOfRuin", { pokemonNameWithAffix: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.SPATK)) })) @@ -5840,7 +5849,7 @@ export function initAbilities() { .attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.SLICING_MOVE), 1.5), new Ability(Abilities.SUPREME_OVERLORD, 9) .attr(VariableMovePowerBoostAbAttr, (user, target, move) => 1 + 0.1 * Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 5)) - .partial(), + .partial(), // Counter resets every wave new Ability(Abilities.COSTAR, 9) .attr(PostSummonCopyAllyStatsAbAttr), new Ability(Abilities.TOXIC_DEBRIS, 9) @@ -5873,25 +5882,25 @@ export function initAbilities() { .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9) .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPDEF ], 1, true) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9) .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.ATK ], 1, true) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9) .attr(PostBattleInitStatStageChangeAbAttr, [ Stat.DEF ], 1, true) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) .attr(NoTransformAbilityAbAttr) - .partial(), + .partial(), // Ogerpon tera interactions new Ability(Abilities.TERA_SHIFT, 9) .attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1) .attr(UncopiableAbilityAbAttr) From 470f9e4e19276ec341736df795949743acbcee71 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:46:41 -0700 Subject: [PATCH 42/75] [P3] Fix Substitute visual error on wave transition (#4648) --- src/field/pokemon.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index c495e0833cd..136c1eb1685 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -749,9 +749,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const relX = newOffset[0] - initialOffset[0]; const relY = newOffset[1] - initialOffset[1]; + const subTag = this.getTag(SubstituteTag); + if (duration) { + // TODO: can this use stricter typing? + const targets: any[] = [ this ]; + if (subTag?.sprite) { + targets.push(subTag.sprite); + } this.scene.tweens.add({ - targets: this, + targets: targets, x: (_target, _key, value: number) => value + relX, y: (_target, _key, value: number) => value + relY, duration: duration, @@ -761,6 +768,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } else { this.x += relX; this.y += relY; + if (subTag?.sprite) { + subTag.sprite.x += relX; + subTag.sprite.y += relY; + } } }); } From 8a355d500a27ca8c74482665a8680528007ab727 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Sun, 13 Oct 2024 00:30:04 -0700 Subject: [PATCH 43/75] [Bug] Move Restriction Battler Tag bugs (#4536) * Added fixes * Revert "Added fixes" This reverts commit 3feccd792ddef0b32ddc40782b60f23f952cad23. * Added loadTag functions * Fixes * typeodcs * Torment * yawn * hsldklahdlhalhdlahldhlah * Imprison Fixes * Fixed imprison not interrupting PRE_MOVE * just kidding * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Fixing what broke * added scp[es * missed a scope * Update src/data/battler-tags.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * good tp go * merge * battler tags * Apply suggestions from code review * Changed function names * publics --------- Co-authored-by: frutescens Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/arena-tag.ts | 68 ++++++++++++++++++++------------- src/data/battler-tags.ts | 75 ++++++++++++++++++++++++------------- src/data/move.ts | 3 +- src/field/pokemon.ts | 8 ++-- src/phases/command-phase.ts | 4 +- 5 files changed, 99 insertions(+), 59 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 11d28ab7e25..71cf11fa06f 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -4,7 +4,7 @@ import { Type } from "#app/data/type"; import * as Utils from "#app/utils"; import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move"; import { getPokemonNameWithAffix } from "#app/messages"; -import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "#app/field/pokemon"; +import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#app/data/status-effect"; import { BattlerIndex } from "#app/battle"; import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; @@ -71,6 +71,32 @@ export abstract class ArenaTag { this.sourceId = source.sourceId; this.side = source.side; } + + /** + * Helper function that retrieves the source Pokemon + * @param scene medium to retrieve the source Pokemon + * @returns The source {@linkcode Pokemon} or `null` if none is found + */ + public getSourcePokemon(scene: BattleScene): Pokemon | null { + return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + } + + /** + * Helper function that retrieves the Pokemon affected + * @param scene - medium to retrieve the involved Pokemon + * @returns list of PlayerPokemon or EnemyPokemon on the field + */ + public getAffectedPokemon(scene: BattleScene): Pokemon[] { + switch (this.side) { + case ArenaTagSide.PLAYER: + return scene.getPlayerField() ?? []; + case ArenaTagSide.ENEMY: + return scene.getEnemyField() ?? []; + case ArenaTagSide.BOTH: + default: + return scene.getField(true) ?? []; + } + } } /** @@ -978,36 +1004,24 @@ class NoneTag extends ArenaTag { * Imprison will apply to any opposing Pokemon that switch onto the field as well. */ class ImprisonTag extends ArenaTrapTag { - private source: Pokemon; - constructor(sourceId: number, side: ArenaTagSide) { super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1); } - /** - * Helper function that retrieves the Pokemon affected - * @param {BattleScene} scene medium to retrieve the involved Pokemon - * @returns list of PlayerPokemon or EnemyPokemon on the field - */ - private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] { - if (!this.source.isPlayer()) { - return scene.getPlayerField() ?? []; - } - return scene.getEnemyField() ?? []; - } - /** * This function applies the effects of Imprison to the opposing Pokemon already present on the field. * @param arena */ override onAdd({ scene }: Arena) { - this.source = scene.getPokemonById(this.sourceId!)!; - if (this.source) { - const party = this.retrieveField(scene); - party?.forEach((p: PlayerPokemon | EnemyPokemon ) => { - p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + const source = this.getSourcePokemon(scene); + if (source) { + const party = this.getAffectedPokemon(scene); + party?.forEach((p: Pokemon ) => { + if (p.isAllowedInBattle()) { + p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); + } }); - scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(this.source) })); + scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(source) })); } } @@ -1016,8 +1030,9 @@ class ImprisonTag extends ArenaTrapTag { * @param _arena * @returns `true` if the source of the tag is still active on the field | `false` if not */ - override lapse(_arena: Arena): boolean { - return this.source.isActive(true); + override lapse({ scene }: Arena): boolean { + const source = this.getSourcePokemon(scene); + return source ? source.isActive(true) : false; } /** @@ -1026,7 +1041,8 @@ class ImprisonTag extends ArenaTrapTag { * @returns `true` */ override activateTrap(pokemon: Pokemon): boolean { - if (this.source.isActive(true)) { + const source = this.getSourcePokemon(pokemon.scene); + if (source && source.isActive(true) && pokemon.isAllowedInBattle()) { pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId); } return true; @@ -1037,8 +1053,8 @@ class ImprisonTag extends ArenaTrapTag { * @param arena */ override onRemove({ scene }: Arena): void { - const party = this.retrieveField(scene); - party?.forEach((p: PlayerPokemon | EnemyPokemon) => { + const party = this.getAffectedPokemon(scene); + party?.forEach((p: Pokemon) => { p.removeTag(BattlerTagType.IMPRISON); }); } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 18f03ada941..a5016746013 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -23,6 +23,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { ShowAbilityPhase } from "#app/phases/show-ability-phase"; import { StatStageChangePhase, StatStageChangeCallback } from "#app/phases/stat-stage-change-phase"; import { PokemonAnimType } from "#app/enums/pokemon-anim-type"; +import BattleScene from "#app/battle-scene"; export enum BattlerTagLapseType { FAINT, @@ -90,6 +91,15 @@ export class BattlerTag { this.sourceMove = source.sourceMove; this.sourceId = source.sourceId; } + + /** + * Helper function that retrieves the source Pokemon object + * @param scene medium to retrieve the source Pokemon + * @returns The source {@linkcode Pokemon} or `null` if none is found + */ + public getSourcePokemon(scene: BattleScene): Pokemon | null { + return this.sourceId ? scene.getPokemonById(this.sourceId) : null; + } } export interface WeatherBattlerTag { @@ -120,7 +130,7 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { const phase = pokemon.scene.getCurrentPhase() as MovePhase; const move = phase.move; - if (this.isMoveRestricted(move.moveId)) { + if (this.isMoveRestricted(move.moveId, pokemon)) { if (this.interruptedText(pokemon, move.moveId)) { pokemon.scene.queueMessage(this.interruptedText(pokemon, move.moveId)); } @@ -136,10 +146,11 @@ export abstract class MoveRestrictionBattlerTag extends BattlerTag { /** * Gets whether this tag is restricting a move. * - * @param {Moves} move {@linkcode Moves} ID to check restriction for. - * @returns {boolean} `true` if the move is restricted by this tag, otherwise `false`. + * @param move - {@linkcode Moves} ID to check restriction for. + * @param user - The {@linkcode Pokemon} involved + * @returns `true` if the move is restricted by this tag, otherwise `false`. */ - abstract isMoveRestricted(move: Moves): boolean; + public abstract isMoveRestricted(move: Moves, user?: Pokemon): boolean; /** * Checks if this tag is restricting a move based on a user's decisions during the target selection phase @@ -327,6 +338,16 @@ export class GorillaTacticsTag extends MoveRestrictionBattlerTag { pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.ATK, false) * 1.5, false); } + /** + * Loads the Gorilla Tactics Battler Tag along with its unique class variable moveId + * @override + * @param source Gorilla Tactics' {@linkcode BattlerTag} information + */ + public override loadTag(source: BattlerTag | any): void { + super.loadTag(source); + this.moveId = source.moveId; + } + /** * * @override @@ -2510,8 +2531,6 @@ export class MysteryEncounterPostSummonTag extends BattlerTag { * Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied */ export class TormentTag extends MoveRestrictionBattlerTag { - private target: Pokemon; - constructor(sourceId: number) { super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId); } @@ -2523,7 +2542,6 @@ export class TormentTag extends MoveRestrictionBattlerTag { */ override onAdd(pokemon: Pokemon) { super.onAdd(pokemon); - this.target = pokemon; pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500); } @@ -2542,15 +2560,18 @@ export class TormentTag extends MoveRestrictionBattlerTag { * @param {Moves} move the move under investigation * @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other */ - override isMoveRestricted(move: Moves): boolean { - const lastMove = this.target.getLastXMoves(1)[0]; + public override isMoveRestricted(move: Moves, user: Pokemon): boolean { + if (!user) { + return false; + } + const lastMove = user.getLastXMoves(1)[0]; if ( !lastMove ) { return false; } // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts const moveObj = allMoves[lastMove.move]; - const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || this.target.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); + const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr); const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS); if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) { return true; @@ -2602,37 +2623,39 @@ export class TauntTag extends MoveRestrictionBattlerTag { * The tag is only removed when the source-user is removed from the field. */ export class ImprisonTag extends MoveRestrictionBattlerTag { - private source: Pokemon | null; - constructor(sourceId: number) { super(BattlerTagType.IMPRISON, [ BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE ], 1, Moves.IMPRISON, sourceId); } - override onAdd(pokemon: Pokemon) { - if (this.sourceId) { - this.source = pokemon.scene.getPokemonById(this.sourceId); - } - } - /** * Checks if the source of Imprison is still active - * @param _pokemon - * @param _lapseType + * @override + * @param pokemon The pokemon this tag is attached to * @returns `true` if the source is still active */ - override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean { - return this.source?.isActive(true) ?? false; + public override lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { + const source = this.getSourcePokemon(pokemon.scene); + if (source) { + if (lapseType === BattlerTagLapseType.PRE_MOVE) { + return super.lapse(pokemon, lapseType) && source.isActive(true); + } else { + return source.isActive(true); + } + } + return false; } /** * Checks if the source of the tag has the parameter move in its moveset and that the source is still active + * @override * @param {Moves} move the move under investigation * @returns `false` if either condition is not met */ - override isMoveRestricted(move: Moves): boolean { - if (this.source) { - const sourceMoveset = this.source.getMoveset().map(m => m!.moveId); - return sourceMoveset?.includes(move) && this.source.isActive(true); + public override isMoveRestricted(move: Moves, user: Pokemon): boolean { + const source = this.getSourcePokemon(user.scene); + if (source) { + const sourceMoveset = source.getMoveset().map(m => m!.moveId); + return sourceMoveset?.includes(move) && source.isActive(true); } return false; } diff --git a/src/data/move.ts b/src/data/move.ts index 8e9977337cc..d4f3b2ce3ee 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7883,7 +7883,8 @@ export function initMoves() { .attr(SwitchAbilitiesAttr), new StatusMove(Moves.IMPRISON, Type.PSYCHIC, 100, 10, -1, 0, 3) .ignoresSubstitute() - .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false), + .attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false) + .target(MoveTarget.ENEMY_SIDE), new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3) .attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN) .condition((user, target, move) => !!user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)), diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 136c1eb1685..9ae83753e62 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3062,8 +3062,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * * @see {@linkcode MoveRestrictionBattlerTag} */ - isMoveRestricted(moveId: Moves): boolean { - return this.getRestrictingTag(moveId) !== null; + public isMoveRestricted(moveId: Moves, pokemon?: Pokemon): boolean { + return this.getRestrictingTag(moveId, pokemon) !== null; } /** @@ -3096,7 +3096,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { */ getRestrictingTag(moveId: Moves, user?: Pokemon, target?: Pokemon): MoveRestrictionBattlerTag | null { for (const tag of this.findTags(t => t instanceof MoveRestrictionBattlerTag)) { - if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId)) { + if ((tag as MoveRestrictionBattlerTag).isMoveRestricted(moveId, user)) { return tag as MoveRestrictionBattlerTag; } else if (user && target && (tag as MoveRestrictionBattlerTag).isMoveTargetRestricted(moveId, user, target)) { return tag as MoveRestrictionBattlerTag; @@ -5139,7 +5139,7 @@ export class PokemonMove { * @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`. */ isUsable(pokemon: Pokemon, ignorePp: boolean = false, ignoreRestrictionTags: boolean = false): boolean { - if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId)) { + if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) { return false; } diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index e85c66543ac..cf66631bd96 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -114,8 +114,8 @@ export class CommandPhase extends FieldPhase { // Decides between a Disabled, Not Implemented, or No PP translation message const errorMessage = - playerPokemon.isMoveRestricted(move.moveId) - ? playerPokemon.getRestrictingTag(move.moveId)!.selectionDeniedText(playerPokemon, move.moveId) + playerPokemon.isMoveRestricted(move.moveId, playerPokemon) + ? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId) : move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator From e340abe75d2d27b17922cb12e90bf19a9b9a0f70 Mon Sep 17 00:00:00 2001 From: PigeonBar <56974298+PigeonBar@users.noreply.github.com> Date: Sun, 13 Oct 2024 20:08:47 -0400 Subject: [PATCH 44/75] [P1 Bug] Fix softlock when a phazing attack activates a reviver seed (#4654) * [P1 Bug] Fix softlock when a phazing attack activates a reviver seed * Polishing tests * Change approach to respect Parting Shot's targeting * Tests: Added checks for correct number of Pokemon on field --- src/data/move.ts | 13 +++---- src/test/moves/dragon_tail.test.ts | 54 ++++++++++++++++++++++++++++++ src/test/moves/u_turn.test.ts | 19 +++++++++++ 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/data/move.ts b/src/data/move.ts index d4f3b2ce3ee..a77e8096672 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -5484,37 +5484,38 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { */ const switchOutTarget = this.selfSwitch ? user : target; if (switchOutTarget instanceof PlayerPokemon) { + // Switch out logic for the player's Pokemon if (switchOutTarget.scene.getParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); if (switchOutTarget.hp > 0) { + switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); user.scene.prependToPhase(new SwitchPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase); return true; } return false; } else if (user.scene.currentBattle.battleType !== BattleType.WILD) { + // Switch out logic for trainer battles if (switchOutTarget.scene.getEnemyParty().filter((p) => p.isAllowedInBattle() && !p.isOnField()).length < 1) { return false; } - // Switch out logic for trainer battles - switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); if (switchOutTarget.hp > 0) { // for opponent switching out + switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH); user.scene.prependToPhase(new SwitchSummonPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, false), MoveEndPhase); } } else { + // Switch out logic for everything else (eg: WILD battles) if (user.scene.currentBattle.waveIndex % 10 === 0) { return false; } - // Switch out logic for everything else (eg: WILD battles) - switchOutTarget.leaveField(false); - if (switchOutTarget.hp) { + if (switchOutTarget.hp > 0) { + switchOutTarget.leaveField(false); user.scene.queueMessage(i18next.t("moveTriggers:fled", { pokemonName: getPokemonNameWithAffix(switchOutTarget) }), null, true, 500); // in double battles redirect potential moves off fled pokemon diff --git a/src/test/moves/dragon_tail.test.ts b/src/test/moves/dragon_tail.test.ts index eb02b09fbb4..cf801eb42c1 100644 --- a/src/test/moves/dragon_tail.test.ts +++ b/src/test/moves/dragon_tail.test.ts @@ -139,4 +139,58 @@ describe("Moves - Dragon Tail", () => { expect(enemy.isFullHp()).toBe(false); }); + + it("should force a switch upon fainting an opponent normally", async () => { + game.override.startingWave(5) + .startingLevel(1000); // To make sure Dragon Tail KO's the opponent + await game.classicMode.startBattle([ Species.DRATINI ]); + + game.move.select(Moves.DRAGON_TAIL); + + await game.toNextTurn(); + + // Make sure the enemy switched to a healthy Pokemon + const enemy = game.scene.getEnemyPokemon()!; + expect(enemy).toBeDefined(); + expect(enemy.isFullHp()).toBe(true); + + // Make sure the enemy has a fainted Pokemon in their party and not on the field + const faintedEnemy = game.scene.getEnemyParty().find(p => !p.isAllowedInBattle()); + expect(faintedEnemy).toBeDefined(); + expect(game.scene.getEnemyField().length).toBe(1); + }); + + it("should not cause a softlock when activating an opponent trainer's reviver seed", async () => { + game.override.startingWave(5) + .enemyHeldItems([{ name: "REVIVER_SEED" }]) + .startingLevel(1000); // To make sure Dragon Tail KO's the opponent + await game.classicMode.startBattle([ Species.DRATINI ]); + + game.move.select(Moves.DRAGON_TAIL); + + await game.toNextTurn(); + + // Make sure the enemy field is not empty and has a revived Pokemon + const enemy = game.scene.getEnemyPokemon()!; + expect(enemy).toBeDefined(); + expect(enemy.hp).toBe(Math.floor(enemy.getMaxHp() / 2)); + expect(game.scene.getEnemyField().length).toBe(1); + }); + + it("should not cause a softlock when activating a player's reviver seed", async () => { + game.override.startingHeldItems([{ name: "REVIVER_SEED" }]) + .enemyMoveset(Moves.DRAGON_TAIL) + .enemyLevel(1000); // To make sure Dragon Tail KO's the player + await game.classicMode.startBattle([ Species.DRATINI, Species.BULBASAUR ]); + + game.move.select(Moves.SPLASH); + + await game.toNextTurn(); + + // Make sure the player's field is not empty and has a revived Pokemon + const dratini = game.scene.getPlayerPokemon()!; + expect(dratini).toBeDefined(); + expect(dratini.hp).toBe(Math.floor(dratini.getMaxHp() / 2)); + expect(game.scene.getPlayerField().length).toBe(1); + }); }); diff --git a/src/test/moves/u_turn.test.ts b/src/test/moves/u_turn.test.ts index 17e02cb50ef..b995c20f503 100644 --- a/src/test/moves/u_turn.test.ts +++ b/src/test/moves/u_turn.test.ts @@ -96,4 +96,23 @@ describe("Moves - U-turn", () => { expect(game.scene.getEnemyPokemon()!.battleData.abilityRevealed).toBe(true); // proxy for asserting ability activated expect(game.phaseInterceptor.log).not.toContain("SwitchSummonPhase"); }, 20000); + + it("still forces a switch if u-turn KO's the opponent", async () => { + game.override.startingLevel(1000); // Ensure that U-Turn KO's the opponent + await game.classicMode.startBattle([ + Species.RAICHU, + Species.SHUCKLE + ]); + const enemy = game.scene.getEnemyPokemon()!; + + // KO the opponent with U-Turn + game.move.select(Moves.U_TURN); + game.doSelectPartyPokemon(1); + await game.phaseInterceptor.to(TurnEndPhase); + expect(enemy.isFainted()).toBe(true); + + // Check that U-Turn forced a switch + expect(game.phaseInterceptor.log).toContain("SwitchSummonPhase"); + expect(game.scene.getPlayerPokemon()!.species.speciesId).toBe(Species.SHUCKLE); + }); }); From 8981f0e7a8ee63743b60f1665967859940b1a4b8 Mon Sep 17 00:00:00 2001 From: Mumble <171087428+frutescens@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:20:55 -0700 Subject: [PATCH 45/75] Trainer party de-duplication checks static pokemon too (#4585) Co-authored-by: frutescens Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> --- src/field/trainer.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 2ec9d07e474..5110787452f 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -383,7 +383,7 @@ export default class Trainer extends Phaser.GameObjects.Container { const battle = this.scene.currentBattle; const template = this.getPartyTemplate(); - let species: PokemonSpecies; + let baseSpecies: PokemonSpecies; if (this.config.speciesPools) { const tierValue = Utils.randSeedInt(512); let tier = tierValue >= 156 ? TrainerPoolTier.COMMON : tierValue >= 32 ? TrainerPoolTier.UNCOMMON : tierValue >= 6 ? TrainerPoolTier.RARE : tierValue >= 1 ? TrainerPoolTier.SUPER_RARE : TrainerPoolTier.ULTRA_RARE; @@ -393,17 +393,17 @@ export default class Trainer extends Phaser.GameObjects.Container { tier--; } const tierPool = this.config.speciesPools[tier]; - species = getPokemonSpecies(Utils.randSeedItem(tierPool)); + baseSpecies = getPokemonSpecies(Utils.randSeedItem(tierPool)); } else { - species = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); + baseSpecies = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter); } - let ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); + let ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); let retry = false; console.log(ret.getName()); - if (pokemonPrevolutions.hasOwnProperty(species.speciesId) && ret.speciesId !== species.speciesId) { + if (pokemonPrevolutions.hasOwnProperty(baseSpecies.speciesId) && ret.speciesId !== baseSpecies.speciesId) { retry = true; } else if (template.isBalanced(battle.enemyParty.length)) { const partyMemberTypes = battle.enemyParty.map(p => p.getTypes(true)).flat(); @@ -417,7 +417,7 @@ export default class Trainer extends Phaser.GameObjects.Container { console.log("Attempting reroll of species evolution to fit specialty type..."); let evoAttempt = 0; while (retry && evoAttempt++ < 10) { - ret = getPokemonSpecies(species.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); + ret = getPokemonSpecies(baseSpecies.getTrainerSpeciesForLevel(level, true, strength, this.scene.currentBattle.waveIndex)); console.log(ret.name); if (this.config.specialtyTypes.find(t => ret.isOfType(t))) { retry = false; @@ -426,7 +426,7 @@ export default class Trainer extends Phaser.GameObjects.Container { } // Prompts reroll of party member species if species already present in the enemy party - if (this.checkDuplicateSpecies(ret)) { + if (this.checkDuplicateSpecies(ret, baseSpecies)) { console.log("Duplicate species detected, prompting reroll..."); retry = true; } @@ -442,13 +442,16 @@ export default class Trainer extends Phaser.GameObjects.Container { /** * Checks if the enemy trainer already has the Pokemon species in their party * @param {PokemonSpecies} species {@linkcode PokemonSpecies} + * @param {PokemonSpecies} baseSpecies {@linkcode PokemonSpecies} - baseSpecies of the Pokemon if species is forced to evolve * @returns `true` if the species is already present in the party */ - checkDuplicateSpecies(species: PokemonSpecies): boolean { + checkDuplicateSpecies(species: PokemonSpecies, baseSpecies: PokemonSpecies): boolean { + const staticPartyPokemon = (signatureSpecies[TrainerType[this.config.trainerType]] ?? []).flat(1); + const currentPartySpecies = this.scene.getEnemyParty().map(p => { return p.species.speciesId; }); - return currentPartySpecies.includes(species.speciesId); + return currentPartySpecies.includes(species.speciesId) || staticPartyPokemon.includes(baseSpecies.speciesId); } getPartyMemberMatchupScores(trainerSlot: TrainerSlot = TrainerSlot.NONE, forSwitch: boolean = false): [integer, integer][] { From 676322e80072518bd667d0513aa18136eed0e82b Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:42:59 +0200 Subject: [PATCH 46/75] [QOL] Add input delay for skipping egg summary (#4644) --- src/phases/egg-lapse-phase.ts | 5 +++-- src/phases/egg-summary-phase.ts | 3 --- src/ui/abstact-option-select-ui-handler.ts | 2 ++ src/ui/confirm-ui-handler.ts | 5 +++-- src/ui/egg-summary-ui-handler.ts | 24 +++++++++++++++++----- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/phases/egg-lapse-phase.ts b/src/phases/egg-lapse-phase.ts index d81d63696a5..4c57be09b79 100644 --- a/src/phases/egg-lapse-phase.ts +++ b/src/phases/egg-lapse-phase.ts @@ -33,7 +33,7 @@ export class EggLapsePhase extends Phase { if (eggsToHatchCount > 0) { if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 1) { this.scene.ui.showText(i18next.t("battle:eggHatching"), 0, () => { - // show prompt for skip + // show prompt for skip, blocking inputs for 1 second this.scene.ui.showText(i18next.t("battle:eggSkipPrompt"), 0); this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.hatchEggsSkipped(eggsToHatch); @@ -41,7 +41,8 @@ export class EggLapsePhase extends Phase { }, () => { this.hatchEggsRegular(eggsToHatch); this.end(); - } + }, + null, null, null, 1000, true ); }, 100, true); } else if (eggsToHatchCount >= this.minEggsToSkip && this.scene.eggSkipPreference === 2) { diff --git a/src/phases/egg-summary-phase.ts b/src/phases/egg-summary-phase.ts index 75c6939daf1..b673eb4887b 100644 --- a/src/phases/egg-summary-phase.ts +++ b/src/phases/egg-summary-phase.ts @@ -1,7 +1,6 @@ import BattleScene from "#app/battle-scene"; import { Phase } from "#app/phase"; import { Mode } from "#app/ui/ui"; -import EggHatchSceneHandler from "#app/ui/egg-hatch-scene-handler"; import { EggHatchData } from "#app/data/egg-hatch-data"; /** @@ -11,7 +10,6 @@ import { EggHatchData } from "#app/data/egg-hatch-data"; */ export class EggSummaryPhase extends Phase { private eggHatchData: EggHatchData[]; - private eggHatchHandler: EggHatchSceneHandler; constructor(scene: BattleScene, eggHatchData: EggHatchData[]) { super(scene); @@ -26,7 +24,6 @@ export class EggSummaryPhase extends Phase { if (i >= this.eggHatchData.length) { this.scene.ui.setModeForceTransition(Mode.EGG_HATCH_SUMMARY, this.eggHatchData).then(() => { this.scene.fadeOutBgm(undefined, false); - this.eggHatchHandler = this.scene.ui.getHandler() as EggHatchSceneHandler; }); } else { diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 9dffd3b4ad0..a12ffbc46bd 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -165,6 +165,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { if (this.config.delay) { this.blockInput = true; this.optionSelectText.setAlpha(0.5); + this.cursorObj?.setAlpha(0.8); this.scene.time.delayedCall(Utils.fixedInt(this.config.delay), () => this.unblockInput()); } @@ -256,6 +257,7 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { this.blockInput = false; this.optionSelectText.setAlpha(1); + this.cursorObj?.setAlpha(1); } getOptionsWithScroll(): OptionSelectItem[] { diff --git a/src/ui/confirm-ui-handler.ts b/src/ui/confirm-ui-handler.ts index 16dae82d091..2022508fc0d 100644 --- a/src/ui/confirm-ui-handler.ts +++ b/src/ui/confirm-ui-handler.ts @@ -76,7 +76,8 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { } } ], - delay: args.length >= 6 && args[5] !== null ? args[5] as integer : 0 + delay: args.length >= 6 && args[5] !== null ? args[5] as number : 0, + noCancel: args.length >= 7 && args[6] !== null ? args[6] as boolean : false, }; super.show([ config ]); @@ -96,7 +97,7 @@ export default class ConfirmUiHandler extends AbstractOptionSelectUiHandler { } processInput(button: Button): boolean { - if (button === Button.CANCEL && this.blockInput) { + if (button === Button.CANCEL && this.blockInput && !this.config?.noCancel) { this.unblockInput(); } diff --git a/src/ui/egg-summary-ui-handler.ts b/src/ui/egg-summary-ui-handler.ts index 519722b1505..da93168926e 100644 --- a/src/ui/egg-summary-ui-handler.ts +++ b/src/ui/egg-summary-ui-handler.ts @@ -42,6 +42,9 @@ export default class EggSummaryUiHandler extends MessageUiHandler { private scrollGridHandler : ScrollableGridUiHandler; private cursorObj: Phaser.GameObjects.Image; + /** used to add a delay before which it is not possible to exit the summary */ + private blockExit: boolean; + /** * Allows subscribers to listen for events * @@ -168,6 +171,13 @@ export default class EggSummaryUiHandler extends MessageUiHandler { this.setCursor(0); this.scene.playSoundWithoutBgm("evolution_fanfare"); + + // Prevent exiting the egg summary for 2 seconds if the egg hatching + // was skipped automatically and for 1 second otherwise + const exitBlockingDuration = (this.scene.eggSkipPreference === 2) ? 2000 : 1000; + this.blockExit = true; + this.scene.time.delayedCall(exitBlockingDuration, () => this.blockExit = false); + return true; } @@ -203,13 +213,17 @@ export default class EggSummaryUiHandler extends MessageUiHandler { const ui = this.getUi(); let success = false; - const error = false; + let error = false; if (button === Button.CANCEL) { - const phase = this.scene.getCurrentPhase(); - if (phase instanceof EggSummaryPhase) { - phase.end(); + if (!this.blockExit) { + const phase = this.scene.getCurrentPhase(); + if (phase instanceof EggSummaryPhase) { + phase.end(); + } + success = true; + } else { + error = true; } - success = true; } else { this.scrollGridHandler.processInput(button); } From e7a4d4055f2b48d1d78c1dc2cb6f3794e0c34dd2 Mon Sep 17 00:00:00 2001 From: cadi Date: Tue, 15 Oct 2024 04:39:34 +0900 Subject: [PATCH 47/75] [Move] Implement Power Trick (#2658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add `PowerTrickTag` * modify getStat() with PowerTrickTag * implement `PowerTrickAttr` * add unit test * enhance docs and tag apply logic --------- Co-authored-by: Lugiad' Co-authored-by: José Ricardo Fleury Oliveira Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Enoch Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --- src/data/battler-tags.ts | 40 ++++++++++ src/data/move.ts | 5 +- src/enums/battler-tag-type.ts | 1 + src/field/pokemon.ts | 6 +- src/test/moves/power_trick.test.ts | 113 +++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/test/moves/power_trick.test.ts diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index a5016746013..8491307fc76 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -2724,6 +2724,44 @@ export class TelekinesisTag extends BattlerTag { } } +/** + * Tag that swaps the user's base ATK stat with its base DEF stat. + * @extends BattlerTag + */ +export class PowerTrickTag extends BattlerTag { + constructor(sourceMove: Moves, sourceId: number) { + super(BattlerTagType.POWER_TRICK, BattlerTagLapseType.CUSTOM, 0, sourceMove, sourceId, true); + } + + onAdd(pokemon: Pokemon): void { + this.swapStat(pokemon); + pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + onRemove(pokemon: Pokemon): void { + this.swapStat(pokemon); + pokemon.scene.queueMessage(i18next.t("battlerTags:powerTrickActive", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + } + + /** + * Removes the Power Trick tag and reverts any stat changes if the tag is already applied. + * @param {Pokemon} pokemon The {@linkcode Pokemon} that already has the Power Trick tag. + */ + onOverlap(pokemon: Pokemon): void { + pokemon.removeTag(this.tagType); + } + + /** + * Swaps the user's base ATK stat with its base DEF stat. + * @param {Pokemon} pokemon The {@linkcode Pokemon} whose stats will be swapped. + */ + swapStat(pokemon: Pokemon): void { + const temp = pokemon.getStat(Stat.ATK, false); + pokemon.setStat(Stat.ATK, pokemon.getStat(Stat.DEF, false), false); + pokemon.setStat(Stat.DEF, temp, false); + } +} + /** * Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID. * @param sourceId - The ID of the pokemon adding the tag @@ -2899,6 +2937,8 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source return new SyrupBombTag(sourceId); case BattlerTagType.TELEKINESIS: return new TelekinesisTag(sourceMove); + case BattlerTagType.POWER_TRICK: + return new PowerTrickTag(sourceMove, sourceId); case BattlerTagType.NONE: default: return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); diff --git a/src/data/move.ts b/src/data/move.ts index a77e8096672..2d91363955a 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -6430,6 +6430,9 @@ export class TransformAttr extends MoveEffectAttr { user.summonData.gender = target.getGender(); user.summonData.fusionGender = target.getFusionGender(); + // Power Trick's effect will not preserved after using Transform + user.removeTag(BattlerTagType.POWER_TRICK); + // Copy all stats (except HP) for (const s of EFFECTIVE_STATS) { user.setStat(s, target.getStat(s, false), false); @@ -8153,7 +8156,7 @@ export function initMoves() { .attr(OpponentHighHpPowerAttr, 120) .makesContact(), new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4) - .unimplemented(), + .attr(AddBattlerTagAttr, BattlerTagType.POWER_TRICK, true), new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4) .attr(SuppressAbilitiesAttr), new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4) diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index 2efae9ad359..680dedb93cc 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -80,6 +80,7 @@ export enum BattlerTagType { DOUBLE_SHOCKED = "DOUBLE_SHOCKED", AUTOTOMIZED = "AUTOTOMIZED", MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", + POWER_TRICK = "POWER_TRICK", HEAL_BLOCK = "HEAL_BLOCK", TORMENT = "TORMENT", TAUNT = "TAUNT", diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 9ae83753e62..0204672cabd 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -19,7 +19,7 @@ import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { Status, StatusEffect, getRandomStatus } from "#app/data/status-effect"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; -import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag } from "../data/battler-tags"; +import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#app/data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "#app/data/ability"; @@ -3048,6 +3048,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { continue; } + if (tag instanceof PowerTrickTag) { + tag.swapStat(this); + } + this.summonData.tags.push(tag); } diff --git a/src/test/moves/power_trick.test.ts b/src/test/moves/power_trick.test.ts new file mode 100644 index 00000000000..a064a43dec4 --- /dev/null +++ b/src/test/moves/power_trick.test.ts @@ -0,0 +1,113 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import { Moves } from "#enums/moves"; +import { Stat } from "#enums/stat"; +import { Species } from "#enums/species"; +import { TurnEndPhase } from "#app/phases/turn-end-phase"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; + +describe("Moves - Power Trick", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .battleType("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH) + .enemySpecies(Species.MEW) + .enemyLevel(200) + .moveset([ Moves.POWER_TRICK ]) + .ability(Abilities.BALL_FETCH); + }); + + it("swaps the user's ATK and DEF stats", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const player = game.scene.getPlayerPokemon()!; + const baseATK = player.getStat(Stat.ATK, false); + const baseDEF = player.getStat(Stat.DEF, false); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + expect(player.getStat(Stat.ATK, false)).toBe(baseDEF); + expect(player.getStat(Stat.DEF, false)).toBe(baseATK); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeDefined(); + }); + + it("resets initial ATK and DEF stat swap when used consecutively", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE ]); + + const player = game.scene.getPlayerPokemon()!; + const baseATK = player.getStat(Stat.ATK, false); + const baseDEF = player.getStat(Stat.DEF, false); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + game.move.select(Moves.POWER_TRICK); + + await game.phaseInterceptor.to(TurnEndPhase); + + expect(player.getStat(Stat.ATK, false)).toBe(baseATK); + expect(player.getStat(Stat.DEF, false)).toBe(baseDEF); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined(); + }); + + it("should pass effect when using BATON_PASS", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.override.moveset([ Moves.POWER_TRICK, Moves.BATON_PASS ]); + + const player = game.scene.getPlayerPokemon()!; + player.addTag(BattlerTagType.POWER_TRICK); + + game.move.select(Moves.BATON_PASS); + game.doSelectPartyPokemon(1); + + await game.phaseInterceptor.to(TurnEndPhase); + + const switchedPlayer = game.scene.getPlayerPokemon()!; + const baseATK = switchedPlayer.getStat(Stat.ATK); + const baseDEF = switchedPlayer.getStat(Stat.DEF); + + expect(switchedPlayer.getStat(Stat.ATK, false)).toBe(baseDEF); + expect(switchedPlayer.getStat(Stat.DEF, false)).toBe(baseATK); + expect(switchedPlayer.getTag(BattlerTagType.POWER_TRICK)).toBeDefined(); + }); + + it("should remove effect after using Transform", async () => { + await game.classicMode.startBattle([ Species.SHUCKLE, Species.SHUCKLE ]); + await game.override.moveset([ Moves.POWER_TRICK, Moves.TRANSFORM ]); + + const player = game.scene.getPlayerPokemon()!; + player.addTag(BattlerTagType.POWER_TRICK); + + game.move.select(Moves.TRANSFORM); + + await game.phaseInterceptor.to(TurnEndPhase); + + const enemy = game.scene.getEnemyPokemon()!; + const baseATK = enemy.getStat(Stat.ATK); + const baseDEF = enemy.getStat(Stat.DEF); + + expect(player.getStat(Stat.ATK, false)).toBe(baseATK); + expect(player.getStat(Stat.DEF, false)).toBe(baseDEF); + expect(player.getTag(BattlerTagType.POWER_TRICK)).toBeUndefined(); + }); +}); 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 48/75] [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 49/75] [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 50/75] [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 51/75] [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 52/75] [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 53/75] [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 54/75] [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 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 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 55/75] [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 56/75] [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 57/75] [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 58/75] [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 59/75] [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 60/75] [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 61/75] [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 zcmV-(5sdDMP)Px`6G=otRCt`tU4Kwj*PZ`dd|iK}*^=FMf@w@19aKQ6Lh@$$l@@Ai16bm~240OR z?a2C^^+$F$8LSE+;i?d48<5y!qG2 z4nAzXdvPj3#3+Rc2s=~dZYcmds_}3t@GJpaU+P4an~(q~L^W0A+@L%MjyL}rC-?0e z7dIgROx9e`z7WMkldg%z$$k4^0KkJwbF^gP3T1x;6jatKZhE$V39NDGA zKhBV9V(dK}uXhr9DHLQ4Sg-aUk64^cX4@)IS_wnJH~>IH^DsieI22@w1LgG!*WTsX zxAH=y(izoLbq*+IprLsf0F*j5%b=zUC}toOj6*0Ghlb{1&-&6bsA5t|5Dh#}!-A{< zOJ+yMmPN*?1mige_>|I4S>BEFg8qdlnnh!x(f;ERb2lto8mlD>jr&Fo9{?;`{Dcnw zXxvu|W7kir$?L~^wFIyrJAf%)`NADFTD$|ea_K$!X}Ec3na=7 zQ`4W)l7-VJo-=ntLiFs0hG7^!LI^=mnhyUUgb;&tr|R7eNp~tC-Kn%1zQUJN2vB znPVNh1R>I^u9+EeZpth#k^ycv+|WLV8<9v^2Rz*LqP3fl60o4<5Csx-3EZHnC$jS_ z0gZM;yBbF)+zqH_(Gh^iP?cjRA8qrxGFkgV6jNJsS)23Pn#<;HShF}zOBQMl4M%pv zBUkn8hTr%*!$*!>C8OFfwOH`8wG9Q4RnAwx(COaw@VHqyf}*)c`O@L_4IM8$@>F3V<&G z%ee)jQYNJU0Phm0*m;#^T(@c|rf-=Jz#O4yhO0oB0k`zCGOs7nk@ZKRe<2!g>Lvme zC$^fq;mY(4TC(usi7n=Cc;(P@S^~Ix@l%HH%Ax1TZ-2U1hksl-^c*qv6lL6g-zhC_ zI%;b_HhV7ioO{dGE(+XZ>9K8I7TI|QK$S&7P@XyfVDZJ0epovmu1hf=t%xrQ+#?*B zOjkJonmV`60W%ZD5xru@P8HI<;IY|rrD@~!#$!h@t`d0ZsMsk5z=F!iT$a8e^$=WiRhxUairmgoqZO+?z-!pf^;#v1=$->^Di;-)?o@e!38)iIX_(;#Q zB>bOxp0FT2&l2OKBE2^#T-O1ksmIpdI=zv$?H!w~cNY9HHyd6S^=G>q5$eBpr^G+!(i>x%u3k*w-J^-PwHw=8+FnqlS3HfPjhYtVfJxGYLV#Tj+ziY$# z`b7YM%x|qg>YQb$uU{mUSv{F81*<0wfI0wx?%%u=kr(l5LcKBlF)s64O*Wo~bR|rk zvrHJVNKaRa@5p*a;+Rvy+S0;~k4Q9@-b87?p`KSvdW6 zlertFJ@lBCEcCs$$J`AMkq#{ZEH?5D-yzaLj-B42!#@s@4q{aH72STj;l^ZhVwOIU zius`=tIcvEy=XXT02`B=Q2InF0H9^tX;a-3#W2k{rr>^llt=>c$B( zK(R5oX>8=%%;*7o8YxFXM+^X^NVS~uoKgU+A#GEqqO$O#WYg_d0%-uzq?=-6aufbN zWZyAG91BMPwv-8K<_3|iSAW8F;Ejf{NB-xBlB_l>s~o4<5pk{oe3ppeNAy|ZjfMep zH+&?Nq$Lac8wSkX@WY`xEdksyYn|cyaHx(P{I3=r{_){Z9WmOT&eHpYMcdO^r0wY} z>*2OLmXqo6vWye;2UdwSj+PTS^RjJwI?MjNVd-~fp)4>5rQexl<9LWTRq(d9 zr8xhY!^nRIC=1L%{xd*h@7n<22ucNIfjO4F8*CXU3(RqJLOW`CbttL;RN*KAk6TGM zKOhl&RFO@$TZg=!n=K!_NY6(VS(0y4S)|8zD6^FXr+p!cnXJjBf|gf@%-ygwp?Ak71j?(dfT(Qo%<*WT6aYICUgn*uj5_K7+`u~Q-C=$%q?-N-3BW4H zWR@TrL|X!-(~v(40DHRGHmf(GX8{X-@`)y+Dzq;|G5yc$d-}fr`N-4vDa-Xdec!)3 z^7Q=&*WS?*z~}G$isAd<+B@W6yPnStKe+Y|G4}4+uK!y!1;yoPjeL5_%c8cMzME~U z;1gQwx}dS8&8ARLT#mBRB9xUD*^Y_0#+Eh|6qkG2N;BYwl9GH+jyhAZtt>090jL-NYN=J#vu#GK0b3H)z`Vegg_h9AJD_q&NxqHe z+f*J8$3p)?8aX@WLp4%_?09OztWOLoQf>t_~YAhdx>K& zSnmHPN0wO5O@1h8oo8P>Y)kQL19br4=*SHZC9SjUb|<+zWp#xCu#Q!tr+U=xwShW3 zl(Y^Z)OlD*0*J;jMW{0c$WyoBv*3DZ#wycMhC0ok{iLJao#ZT?$c};^pcR0q?)=1( zO*B#I-0WCc`$816d+=x4obMj|nYkMV;#X?PLhpqaBhMTD@)bSL8-Dj64TJph6*Btb zDINY1yYc}TBe_4+e{Bc=(>trJ*Ve)F;CaeXndd}0BAM^swh+}MJ#M}ayo{Qp$E|l8 z$BkfmXSJt}Os~S9;Jh-b@FzHoEBpzTu?#9YB0YNs23|JbX{3=X001Hpdv(tD<)tSq z0jtEP4P0qh&@|U@b~3#Re}Z!}VxybhS&fRkTIumhyA#@4~}=5Dwl{!uMi*mB{&&E4=OY10zG6r;%S-6Ur$6TonV)FX0ndqSWzfG6sZ6fP!mgk9NB+_2 zn<;w!(doDS=5BcQT|IY76TfU2zO(NV(sJymxAr99?7M^*wM|EEzi-i06lDSc^2$TV z$uG0olVJbmhF@$N)QXw;E1b3D^neg^4JEBLH?ryN!sQ^?BtXR1{?@aeRQn zb%3oQHK1HhepysC=Ajh8XNj0w8O^PttE17}4Re>5Y01LT-HqmMc=(u}-Eh&@4a0Z% z7$H@f{#WbpF+z-t9ePGj;aCTbF28|_KpCk^I3(}NGPebajLTFaRAW15XB6&*LrJB z5{BAq&E4?c!Wu1E_+xvG`ES3wo=tQdoclNKKbH2TPhEbO`0{j1{Pa(Ej`5@RM*wED z1ICdv&E$XIY!~*Ef9I&wM^aew1*p@pmKZ+I{3hX0yhC-`_ex;oH|CNIpZhN<$^vl& zA#yHoZ121=*ZP8q|C{#`wjB7{I)jB`Pxc5k+{VC^y2J+^GIBhfW(&*Ecx!3832H<7k(80;Q!pm z(!qTC=Q|KSnhpS%(ba}EesgYZA3TdS{zPmCux9=}5$-*#nSamNLX`aYY%7rD$H)Gm z_3t11G63MpU$0}u558t|I5Nc#V9-o`A%GA57Wg8g@cF;sKT_`o09=b*XukWthV_B9 zmh4N*3*v02`Kd|TFM>!9t_1`qz}W+)PU4aTTYxwni}2A*%&0aUV%7y*n6@4B0BnE5 z`CG`1XPN;3WebB?A6N@tdT=e$@`3<>zRqT(j40&02#^Im=ZabJvZBWzhlZ& z0Km7C&0l#kivIPKa37#)9-S@z4T9KnryU$E1pvff82bk8G|8$yb6PB7F8uz0!?Blh z8Q8gTA%5&zwBc!D{a^jzcjj)G`TYu87Cwr*-_zOj;fvB^A6=ed$#dTSJ^qC2R{L|G zjOlguziRwHWRS#5j^)tn0000EWmrjOO-%qQ00008000000002eQ Date: Wed, 16 Oct 2024 14:55:23 -0400 Subject: [PATCH 62/75] [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 63/75] [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 64/75] [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 65/75] [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 66/75] 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 67/75] [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 68/75] [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; From 39abac65be26c2004ea1ca9a686cc8d7b35efb65 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Sat, 19 Oct 2024 18:44:36 -0700 Subject: [PATCH 69/75] Add eslint rule to enforce indenting of `case` statements (#4692) --- eslint.config.js | 16 +- src/battle-scene.ts | 586 ++++----- src/data/ability.ts | 60 +- src/data/arena-tag.ts | 174 +-- src/data/balance/biomes.ts | 16 +- src/data/balance/starters.ts | 38 +- src/data/battle-anims.ts | 322 ++--- src/data/battler-tags.ts | 392 +++--- src/data/berry.ts | 198 +-- src/data/challenge.ts | 132 +- src/data/egg.ts | 136 +- src/data/exp.ts | 60 +- src/data/gender.ts | 16 +- src/data/move.ts | 1114 ++++++++--------- .../encounters/a-trainers-test-encounter.ts | 52 +- .../utils/encounter-phase-utils.ts | 26 +- src/data/nature.ts | 140 +-- src/data/pokeball.ts | 108 +- src/data/pokemon-species.ts | 200 +-- src/data/status-effect.ts | 44 +- src/data/terrain.ts | 76 +- src/data/trainer-config.ts | 314 ++--- src/data/type.ts | 628 +++++----- src/data/variant.ts | 24 +- src/data/weather.ts | 466 +++---- src/field/anims.ts | 30 +- src/field/arena.ts | 560 ++++----- src/field/damage-number-handler.ts | 30 +- src/field/pokemon.ts | 424 +++---- src/field/trainer.ts | 76 +- src/game-mode.ts | 148 +-- src/loading-scene.ts | 24 +- src/messages.ts | 32 +- src/modifier/modifier-type.ts | 226 ++-- src/modifier/modifier.ts | 30 +- src/phases/command-phase.ts | 250 ++-- src/phases/damage-phase.ts | 20 +- src/phases/encounter-phase.ts | 46 +- src/phases/faint-phase.ts | 22 +- src/phases/move-phase.ts | 34 +- src/phases/pokemon-anim-phase.ts | 28 +- src/phases/post-turn-status-effect-phase.ts | 20 +- src/phases/select-modifier-phase.ts | 136 +- src/phases/turn-start-phase.ts | 90 +- src/system/achv.ts | 254 ++-- src/system/game-data.ts | 82 +- src/system/settings/settings-gamepad.ts | 110 +- src/system/settings/settings-keyboard.ts | 92 +- src/system/settings/settings.ts | 450 +++---- src/system/unlockables.ts | 16 +- .../version_migration/versions/v1_0_4.ts | 18 +- src/system/voucher.ts | 48 +- .../mystery-encounter/encounter-test-utils.ts | 26 +- src/touch-controls.ts | 28 +- src/ui-inputs.ts | 38 +- src/ui/abstact-option-select-ui-handler.ts | 28 +- src/ui/achvs-ui-handler.ts | 130 +- src/ui/arena-flyout.ts | 144 +-- src/ui/ball-ui-handler.ts | 12 +- src/ui/challenges-select-ui-handler.ts | 98 +- src/ui/command-ui-handler.ts | 78 +- src/ui/daily-run-scoreboard.ts | 14 +- src/ui/dropdown.ts | 42 +- src/ui/egg-gacha-ui-handler.ts | 282 ++--- src/ui/fight-ui-handler.ts | 40 +- src/ui/game-stats-ui-handler.ts | 20 +- src/ui/login-form-ui-handler.ts | 24 +- src/ui/menu-ui-handler.ts | 274 ++-- src/ui/message-ui-handler.ts | 24 +- src/ui/modifier-select-ui-handler.ts | 154 +-- src/ui/mystery-encounter-ui-handler.ts | 192 +-- src/ui/party-ui-handler.ts | 272 ++-- src/ui/pokemon-icon-anim-handler.ts | 12 +- src/ui/registration-form-ui-handler.ts | 12 +- src/ui/run-history-ui-handler.ts | 66 +- src/ui/run-info-ui-handler.ts | 216 ++-- src/ui/save-slot-select-ui-handler.ts | 102 +- src/ui/scrollable-grid-handler.ts | 74 +- .../settings/abstract-binding-ui-handler.ts | 28 +- .../abstract-control-settings-ui-handler.ts | 132 +- .../settings/abstract-settings-ui-handler.ts | 98 +- src/ui/settings/navigationMenu.ts | 12 +- .../settings/settings-display-ui-handler.ts | 146 +-- src/ui/starter-select-ui-handler.ts | 754 +++++------ src/ui/summary-ui-handler.ts | 622 ++++----- src/ui/target-select-ui-handler.ts | 40 +- src/ui/text.ts | 328 ++--- src/ui/ui-theme.ts | 12 +- src/utils.ts | 58 +- 89 files changed, 6633 insertions(+), 6633 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index c371f073f76..2f2b466c66f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,5 @@ import tseslint from '@typescript-eslint/eslint-plugin'; -import stylisticTs from '@stylistic/eslint-plugin-ts' +import stylisticTs from '@stylistic/eslint-plugin-ts'; import parser from '@typescript-eslint/parser'; import importX from 'eslint-plugin-import-x'; @@ -16,15 +16,15 @@ export default [ '@typescript-eslint': tseslint }, rules: { - "eqeqeq": ["error", "always"], // Enforces the use of === and !== instead of == and != - "indent": ["error", 2], // Enforces a 2-space indentation + "eqeqeq": ["error", "always"], // Enforces the use of `===` and `!==` instead of `==` and `!=` + "indent": ["error", 2, { "SwitchCase": 1 }], // Enforces a 2-space indentation, enforces indentation of `case ...:` statements "quotes": ["error", "double"], // Enforces the use of double quotes for strings - "no-var": "error", // Disallows the use of var, enforcing let or const instead - "prefer-const": "error", // Prefers the use of const for variables that are never reassigned + "no-var": "error", // Disallows the use of `var`, enforcing `let` or `const` instead + "prefer-const": "error", // Enforces the use of `const` for variables that are never reassigned "no-undef": "off", // Disables the rule that disallows the use of undeclared variables (TypeScript handles this) "@typescript-eslint/no-unused-vars": [ "error", { "args": "none", // Allows unused function parameters. Useful for functions with specific signatures where not all parameters are always used. - "ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the rest. + "ignoreRestSiblings": true // Allows unused variables that are part of a rest property in object destructuring. Useful for excluding certain properties from an object while using the others. }], "eol-last": ["error", "always"], // Enforces at least one newline at the end of files "@stylistic/ts/semi": ["error", "always"], // Requires semicolons for TypeScript-specific syntax @@ -32,14 +32,14 @@ export default [ "no-extra-semi": ["error"], // Disallows unnecessary semicolons for TypeScript-specific syntax "brace-style": "off", // Note: you must disable the base rule as it can report incorrect errors "curly": ["error", "all"], // Enforces the use of curly braces for all control statements - "@stylistic/ts/brace-style": ["error", "1tbs"], + "@stylistic/ts/brace-style": ["error", "1tbs"], // Enforces the following brace style: https://eslint.style/rules/js/brace-style#_1tbs "no-trailing-spaces": ["error", { // Disallows trailing whitespace at the end of lines "skipBlankLines": false, // Enforces the rule even on blank lines "ignoreComments": false // Enforces the rule on lines containing comments }], "space-before-blocks": ["error", "always"], // Enforces a space before blocks "keyword-spacing": ["error", { "before": true, "after": true }], // Enforces spacing before and after keywords - "comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after comma + "comma-spacing": ["error", { "before": false, "after": true }], // Enforces spacing after commas "import-x/extensions": ["error", "never", { "json": "always" }], // Enforces no extension for imports unless json "array-bracket-spacing": ["error", "always", { "objectsInArrays": false, "arraysInArrays": false }], // Enforces consistent spacing inside array brackets "object-curly-spacing": ["error", "always", { "arraysInObjects": false, "objectsInObjects": false }], // Enforces consistent spacing inside braces of object literals, destructuring assignments, and import/export specifiers diff --git a/src/battle-scene.ts b/src/battle-scene.ts index c6fff1e209a..0d482a5f1a5 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1359,69 +1359,69 @@ export default class BattleScene extends SceneBase { } switch (species.speciesId) { - case Species.UNOWN: - case Species.SHELLOS: - case Species.GASTRODON: - case Species.BASCULIN: - case Species.DEERLING: - case Species.SAWSBUCK: - case Species.FROAKIE: - case Species.FROGADIER: - case Species.SCATTERBUG: - case Species.SPEWPA: - case Species.VIVILLON: - case Species.FLABEBE: - case Species.FLOETTE: - case Species.FLORGES: - case Species.FURFROU: - case Species.PUMPKABOO: - case Species.GOURGEIST: - case Species.ORICORIO: - case Species.MAGEARNA: - case Species.ZARUDE: - case Species.SQUAWKABILLY: - case Species.TATSUGIRI: - case Species.PALDEA_TAUROS: - return Utils.randSeedInt(species.forms.length); - case Species.PIKACHU: - return Utils.randSeedInt(8); - case Species.EEVEE: - return Utils.randSeedInt(2); - case Species.GRENINJA: - return Utils.randSeedInt(2); - case Species.ZYGARDE: - return Utils.randSeedInt(4); - case Species.MINIOR: - return Utils.randSeedInt(6); - case Species.ALCREMIE: - return Utils.randSeedInt(9); - case Species.MEOWSTIC: - case Species.INDEEDEE: - case Species.BASCULEGION: - case Species.OINKOLOGNE: - return gender === Gender.FEMALE ? 1 : 0; - case Species.TOXTRICITY: - const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]; - if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) { - return 1; - } - return 0; - case Species.GIMMIGHOUL: - // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs - if (this.gameMode.hasMysteryEncounters) { - return 1; // Wandering form - } else { + case Species.UNOWN: + case Species.SHELLOS: + case Species.GASTRODON: + case Species.BASCULIN: + case Species.DEERLING: + case Species.SAWSBUCK: + case Species.FROAKIE: + case Species.FROGADIER: + case Species.SCATTERBUG: + case Species.SPEWPA: + case Species.VIVILLON: + case Species.FLABEBE: + case Species.FLOETTE: + case Species.FLORGES: + case Species.FURFROU: + case Species.PUMPKABOO: + case Species.GOURGEIST: + case Species.ORICORIO: + case Species.MAGEARNA: + case Species.ZARUDE: + case Species.SQUAWKABILLY: + case Species.TATSUGIRI: + case Species.PALDEA_TAUROS: return Utils.randSeedInt(species.forms.length); - } + case Species.PIKACHU: + return Utils.randSeedInt(8); + case Species.EEVEE: + return Utils.randSeedInt(2); + case Species.GRENINJA: + return Utils.randSeedInt(2); + case Species.ZYGARDE: + return Utils.randSeedInt(4); + case Species.MINIOR: + return Utils.randSeedInt(6); + case Species.ALCREMIE: + return Utils.randSeedInt(9); + case Species.MEOWSTIC: + case Species.INDEEDEE: + case Species.BASCULEGION: + case Species.OINKOLOGNE: + return gender === Gender.FEMALE ? 1 : 0; + case Species.TOXTRICITY: + const lowkeyNatures = [ Nature.LONELY, Nature.BOLD, Nature.RELAXED, Nature.TIMID, Nature.SERIOUS, Nature.MODEST, Nature.MILD, Nature.QUIET, Nature.BASHFUL, Nature.CALM, Nature.GENTLE, Nature.CAREFUL ]; + if (nature !== undefined && lowkeyNatures.indexOf(nature) > -1) { + return 1; + } + return 0; + case Species.GIMMIGHOUL: + // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs + if (this.gameMode.hasMysteryEncounters) { + return 1; // Wandering form + } else { + return Utils.randSeedInt(species.forms.length); + } } if (ignoreArena) { switch (species.speciesId) { - case Species.BURMY: - case Species.WORMADAM: - case Species.ROTOM: - case Species.LYCANROC: - return Utils.randSeedInt(species.forms.length); + case Species.BURMY: + case Species.WORMADAM: + case Species.ROTOM: + case Species.LYCANROC: + return Utils.randSeedInt(species.forms.length); } return 0; } @@ -1886,17 +1886,17 @@ export default class BattleScene extends SceneBase { const soundDetails = sound.key.split("/"); switch (soundDetails[0]) { - case "battle_anims": - case "cry": - if (soundDetails[1].startsWith("PRSFX- ")) { - sound.setVolume(this.masterVolume * this.fieldVolume * 0.5); - } else { - sound.setVolume(this.masterVolume * this.fieldVolume); - } - break; - case "se": - case "ui": - sound.setVolume(this.masterVolume * this.seVolume); + case "battle_anims": + case "cry": + if (soundDetails[1].startsWith("PRSFX- ")) { + sound.setVolume(this.masterVolume * this.fieldVolume * 0.5); + } else { + sound.setVolume(this.masterVolume * this.fieldVolume); + } + break; + case "se": + case "ui": + sound.setVolume(this.masterVolume * this.seVolume); } } } @@ -1936,31 +1936,31 @@ export default class BattleScene extends SceneBase { const keyDetails = key.split("/"); config["volume"] = config["volume"] ?? 1; switch (keyDetails[0]) { - case "level_up_fanfare": - case "item_fanfare": - case "minor_fanfare": - case "heal": - case "evolution": - case "evolution_fanfare": + case "level_up_fanfare": + case "item_fanfare": + case "minor_fanfare": + case "heal": + case "evolution": + case "evolution_fanfare": // These sounds are loaded in as BGM, but played as sound effects // When these sounds are updated in updateVolume(), they are treated as BGM however because they are placed in the BGM Cache through being called by playSoundWithoutBGM() - config["volume"] *= (this.masterVolume * this.bgmVolume); - break; - case "battle_anims": - case "cry": - config["volume"] *= (this.masterVolume * this.fieldVolume); - //PRSFX sound files are unusually loud - if (keyDetails[1].startsWith("PRSFX- ")) { - config["volume"] *= 0.5; - } - break; - case "ui": + config["volume"] *= (this.masterVolume * this.bgmVolume); + break; + case "battle_anims": + case "cry": + config["volume"] *= (this.masterVolume * this.fieldVolume); + //PRSFX sound files are unusually loud + if (keyDetails[1].startsWith("PRSFX- ")) { + config["volume"] *= 0.5; + } + break; + case "ui": //As of, right now this applies to the "select", "menu_open", "error" sound effects - config["volume"] *= (this.masterVolume * this.uiVolume); - break; - case "se": - config["volume"] *= (this.masterVolume * this.seVolume); - break; + config["volume"] *= (this.masterVolume * this.uiVolume); + break; + case "se": + config["volume"] *= (this.masterVolume * this.seVolume); + break; } this.sound.play(key, config); return this.sound.get(key) as AnySound; @@ -1989,208 +1989,208 @@ export default class BattleScene extends SceneBase { getBgmLoopPoint(bgmName: string): number { switch (bgmName) { - case "battle_kanto_champion": //B2W2 Kanto Champion Battle - return 13.950; - case "battle_johto_champion": //B2W2 Johto Champion Battle - return 23.498; - case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle - return 11.328; - case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle - return 11.762; - case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle - return 12.235; - case "battle_champion_alder": //BW Unova Champion Battle - return 27.653; - case "battle_champion_iris": //B2W2 Unova Champion Battle - return 10.145; - case "battle_kalos_champion": //XY Kalos Champion Battle - return 10.380; - case "battle_alola_champion": //USUM Alola Champion Battle - return 13.025; - case "battle_galar_champion": //SWSH Galar Champion Battle - return 61.635; - case "battle_champion_geeta": //SV Champion Geeta Battle - return 37.447; - case "battle_champion_nemona": //SV Champion Nemona Battle - return 14.914; - case "battle_champion_kieran": //SV Champion Kieran Battle - return 7.206; - case "battle_hoenn_elite": //ORAS Elite Four Battle - return 11.350; - case "battle_unova_elite": //BW Elite Four Battle - return 17.730; - case "battle_kalos_elite": //XY Elite Four Battle - return 12.340; - case "battle_alola_elite": //SM Elite Four Battle - return 19.212; - case "battle_galar_elite": //SWSH League Tournament Battle - return 164.069; - case "battle_paldea_elite": //SV Elite Four Battle - return 12.770; - case "battle_bb_elite": //SV BB League Elite Four Battle - return 19.434; - case "battle_final_encounter": //PMD RTDX Rayquaza's Domain - return 19.159; - case "battle_final": //BW Ghetsis Battle - return 16.453; - case "battle_kanto_gym": //B2W2 Kanto Gym Battle - return 13.857; - case "battle_johto_gym": //B2W2 Johto Gym Battle - return 12.911; - case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle - return 12.379; - case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle - return 13.122; - case "battle_unova_gym": //BW Unova Gym Battle - return 19.145; - case "battle_kalos_gym": //XY Kalos Gym Battle - return 44.810; - case "battle_galar_gym": //SWSH Galar Gym Battle - return 171.262; - case "battle_paldea_gym": //SV Paldea Gym Battle - return 127.489; - case "battle_legendary_kanto": //XY Kanto Legendary Battle - return 32.966; - case "battle_legendary_raikou": //HGSS Raikou Battle - return 12.632; - case "battle_legendary_entei": //HGSS Entei Battle - return 2.905; - case "battle_legendary_suicune": //HGSS Suicune Battle - return 12.636; - case "battle_legendary_lugia": //HGSS Lugia Battle - return 19.770; - case "battle_legendary_ho_oh": //HGSS Ho-oh Battle - return 17.668; - case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle - return 49.500; - case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle - return 21.130; - case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle - return 10.547; - case "battle_legendary_rayquaza": //ORAS Rayquaza Battle - return 10.495; - case "battle_legendary_deoxys": //ORAS Deoxys Battle - return 13.333; - case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle - return 16.887; - case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle - return 22.770; - case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle - return 16.009; - case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle - return 18.961; - case "battle_legendary_giratina": //ORAS Giratina Battle - return 10.451; - case "battle_legendary_arceus": //HGSS Arceus Battle - return 9.595; - case "battle_legendary_unova": //BW Unova Legendary Battle - return 13.855; - case "battle_legendary_kyurem": //BW Kyurem Battle - return 18.314; - case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle - return 18.329; - case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle - return 26.468; - case "battle_legendary_tapu": //SM Tapu Battle - return 0.000; - case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle - return 6.525; - case "battle_legendary_ub": //SM Ultra Beast Battle - return 9.818; - case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle - return 5.211; - case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle - return 10.344; - case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle - return 11.424; - case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle - return 12.503; - case "battle_legendary_calyrex": //SWSH Calyrex Battle - return 50.641; - case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle - return 18.155; - case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle - return 0.175; - case "battle_legendary_ruinous": //SV Treasures of Ruin Battle - return 6.333; - case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle - return 6.442; - case "battle_legendary_loyal_three": //SV Loyal Three Battle - return 6.500; - case "battle_legendary_ogerpon": //SV Ogerpon Battle - return 14.335; - case "battle_legendary_terapagos": //SV Terapagos Battle - return 24.377; - case "battle_legendary_pecharunt": //SV Pecharunt Battle - return 6.508; - case "battle_rival": //BW Rival Battle - return 14.110; - case "battle_rival_2": //BW N Battle - return 17.714; - case "battle_rival_3": //BW Final N Battle - return 17.586; - case "battle_trainer": //BW Trainer Battle - return 13.686; - case "battle_wild": //BW Wild Battle - return 12.703; - case "battle_wild_strong": //BW Strong Wild Battle - return 13.940; - case "end_summit": //PMD RTDX Sky Tower Summit - return 30.025; - case "battle_rocket_grunt": //HGSS Team Rocket Battle - return 12.707; - case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle - return 12.062; - case "battle_galactic_grunt": //BDSP Team Galactic Battle - return 13.043; - case "battle_plasma_grunt": //BW Team Plasma Battle - return 12.974; - case "battle_flare_grunt": //XY Team Flare Battle - return 4.228; - case "battle_aether_grunt": // SM Aether Foundation Battle - return 16.00; - case "battle_skull_grunt": // SM Team Skull Battle - return 20.87; - case "battle_macro_grunt": // SWSH Trainer Battle - return 11.56; - case "battle_star_grunt": //SV Team Star Battle - return 133.362; - case "battle_galactic_admin": //BDSP Team Galactic Admin Battle - return 11.997; - case "battle_skull_admin": //SM Team Skull Admin Battle - return 15.463; - case "battle_oleana": //SWSH Oleana Battle - return 14.110; - case "battle_star_admin": //SV Team Star Boss Battle - return 9.493; - case "battle_rocket_boss": //USUM Giovanni Battle - return 9.115; - case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle - return 14.847; - case "battle_galactic_boss": //BDSP Cyrus Battle - return 106.962; - case "battle_plasma_boss": //B2W2 Ghetsis Battle - return 25.624; - case "battle_flare_boss": //XY Lysandre Battle - return 8.085; - case "battle_aether_boss": //SM Lusamine Battle - return 11.33; - case "battle_skull_boss": //SM Guzma Battle - return 13.13; - case "battle_macro_boss": //SWSH Rose Battle - return 11.42; - case "battle_star_boss": //SV Cassiopeia Battle - return 25.764; - case "mystery_encounter_gen_5_gts": // BW GTS - return 8.52; - case "mystery_encounter_gen_6_gts": // XY GTS - return 9.24; - case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff - return 4.78; - case "mystery_encounter_weird_dream": // EoS Temporal Spire - return 41.42; - case "mystery_encounter_delibirdy": // Firel Delibirdy - return 82.28; + case "battle_kanto_champion": //B2W2 Kanto Champion Battle + return 13.950; + case "battle_johto_champion": //B2W2 Johto Champion Battle + return 23.498; + case "battle_hoenn_champion_g5": //B2W2 Hoenn Champion Battle + return 11.328; + case "battle_hoenn_champion_g6": //ORAS Hoenn Champion Battle + return 11.762; + case "battle_sinnoh_champion": //B2W2 Sinnoh Champion Battle + return 12.235; + case "battle_champion_alder": //BW Unova Champion Battle + return 27.653; + case "battle_champion_iris": //B2W2 Unova Champion Battle + return 10.145; + case "battle_kalos_champion": //XY Kalos Champion Battle + return 10.380; + case "battle_alola_champion": //USUM Alola Champion Battle + return 13.025; + case "battle_galar_champion": //SWSH Galar Champion Battle + return 61.635; + case "battle_champion_geeta": //SV Champion Geeta Battle + return 37.447; + case "battle_champion_nemona": //SV Champion Nemona Battle + return 14.914; + case "battle_champion_kieran": //SV Champion Kieran Battle + return 7.206; + case "battle_hoenn_elite": //ORAS Elite Four Battle + return 11.350; + case "battle_unova_elite": //BW Elite Four Battle + return 17.730; + case "battle_kalos_elite": //XY Elite Four Battle + return 12.340; + case "battle_alola_elite": //SM Elite Four Battle + return 19.212; + case "battle_galar_elite": //SWSH League Tournament Battle + return 164.069; + case "battle_paldea_elite": //SV Elite Four Battle + return 12.770; + case "battle_bb_elite": //SV BB League Elite Four Battle + return 19.434; + case "battle_final_encounter": //PMD RTDX Rayquaza's Domain + return 19.159; + case "battle_final": //BW Ghetsis Battle + return 16.453; + case "battle_kanto_gym": //B2W2 Kanto Gym Battle + return 13.857; + case "battle_johto_gym": //B2W2 Johto Gym Battle + return 12.911; + case "battle_hoenn_gym": //B2W2 Hoenn Gym Battle + return 12.379; + case "battle_sinnoh_gym": //B2W2 Sinnoh Gym Battle + return 13.122; + case "battle_unova_gym": //BW Unova Gym Battle + return 19.145; + case "battle_kalos_gym": //XY Kalos Gym Battle + return 44.810; + case "battle_galar_gym": //SWSH Galar Gym Battle + return 171.262; + case "battle_paldea_gym": //SV Paldea Gym Battle + return 127.489; + case "battle_legendary_kanto": //XY Kanto Legendary Battle + return 32.966; + case "battle_legendary_raikou": //HGSS Raikou Battle + return 12.632; + case "battle_legendary_entei": //HGSS Entei Battle + return 2.905; + case "battle_legendary_suicune": //HGSS Suicune Battle + return 12.636; + case "battle_legendary_lugia": //HGSS Lugia Battle + return 19.770; + case "battle_legendary_ho_oh": //HGSS Ho-oh Battle + return 17.668; + case "battle_legendary_regis_g5": //B2W2 Legendary Titan Battle + return 49.500; + case "battle_legendary_regis_g6": //ORAS Legendary Titan Battle + return 21.130; + case "battle_legendary_gro_kyo": //ORAS Groudon & Kyogre Battle + return 10.547; + case "battle_legendary_rayquaza": //ORAS Rayquaza Battle + return 10.495; + case "battle_legendary_deoxys": //ORAS Deoxys Battle + return 13.333; + case "battle_legendary_lake_trio": //ORAS Lake Guardians Battle + return 16.887; + case "battle_legendary_sinnoh": //ORAS Sinnoh Legendary Battle + return 22.770; + case "battle_legendary_dia_pal": //ORAS Dialga & Palkia Battle + return 16.009; + case "battle_legendary_origin_forme": //LA Origin Dialga & Palkia Battle + return 18.961; + case "battle_legendary_giratina": //ORAS Giratina Battle + return 10.451; + case "battle_legendary_arceus": //HGSS Arceus Battle + return 9.595; + case "battle_legendary_unova": //BW Unova Legendary Battle + return 13.855; + case "battle_legendary_kyurem": //BW Kyurem Battle + return 18.314; + case "battle_legendary_res_zek": //BW Reshiram & Zekrom Battle + return 18.329; + case "battle_legendary_xern_yvel": //XY Xerneas & Yveltal Battle + return 26.468; + case "battle_legendary_tapu": //SM Tapu Battle + return 0.000; + case "battle_legendary_sol_lun": //SM Solgaleo & Lunala Battle + return 6.525; + case "battle_legendary_ub": //SM Ultra Beast Battle + return 9.818; + case "battle_legendary_dusk_dawn": //USUM Dusk Mane & Dawn Wings Necrozma Battle + return 5.211; + case "battle_legendary_ultra_nec": //USUM Ultra Necrozma Battle + return 10.344; + case "battle_legendary_zac_zam": //SWSH Zacian & Zamazenta Battle + return 11.424; + case "battle_legendary_glas_spec": //SWSH Glastrier & Spectrier Battle + return 12.503; + case "battle_legendary_calyrex": //SWSH Calyrex Battle + return 50.641; + case "battle_legendary_riders": //SWSH Ice & Shadow Rider Calyrex Battle + return 18.155; + case "battle_legendary_birds_galar": //SWSH Galarian Legendary Birds Battle + return 0.175; + case "battle_legendary_ruinous": //SV Treasures of Ruin Battle + return 6.333; + case "battle_legendary_kor_mir": //SV Depths of Area Zero Battle + return 6.442; + case "battle_legendary_loyal_three": //SV Loyal Three Battle + return 6.500; + case "battle_legendary_ogerpon": //SV Ogerpon Battle + return 14.335; + case "battle_legendary_terapagos": //SV Terapagos Battle + return 24.377; + case "battle_legendary_pecharunt": //SV Pecharunt Battle + return 6.508; + case "battle_rival": //BW Rival Battle + return 14.110; + case "battle_rival_2": //BW N Battle + return 17.714; + case "battle_rival_3": //BW Final N Battle + return 17.586; + case "battle_trainer": //BW Trainer Battle + return 13.686; + case "battle_wild": //BW Wild Battle + return 12.703; + case "battle_wild_strong": //BW Strong Wild Battle + return 13.940; + case "end_summit": //PMD RTDX Sky Tower Summit + return 30.025; + case "battle_rocket_grunt": //HGSS Team Rocket Battle + return 12.707; + case "battle_aqua_magma_grunt": //ORAS Team Aqua & Magma Battle + return 12.062; + case "battle_galactic_grunt": //BDSP Team Galactic Battle + return 13.043; + case "battle_plasma_grunt": //BW Team Plasma Battle + return 12.974; + case "battle_flare_grunt": //XY Team Flare Battle + return 4.228; + case "battle_aether_grunt": // SM Aether Foundation Battle + return 16.00; + case "battle_skull_grunt": // SM Team Skull Battle + return 20.87; + case "battle_macro_grunt": // SWSH Trainer Battle + return 11.56; + case "battle_star_grunt": //SV Team Star Battle + return 133.362; + case "battle_galactic_admin": //BDSP Team Galactic Admin Battle + return 11.997; + case "battle_skull_admin": //SM Team Skull Admin Battle + return 15.463; + case "battle_oleana": //SWSH Oleana Battle + return 14.110; + case "battle_star_admin": //SV Team Star Boss Battle + return 9.493; + case "battle_rocket_boss": //USUM Giovanni Battle + return 9.115; + case "battle_aqua_magma_boss": //ORAS Archie & Maxie Battle + return 14.847; + case "battle_galactic_boss": //BDSP Cyrus Battle + return 106.962; + case "battle_plasma_boss": //B2W2 Ghetsis Battle + return 25.624; + case "battle_flare_boss": //XY Lysandre Battle + return 8.085; + case "battle_aether_boss": //SM Lusamine Battle + return 11.33; + case "battle_skull_boss": //SM Guzma Battle + return 13.13; + case "battle_macro_boss": //SWSH Rose Battle + return 11.42; + case "battle_star_boss": //SV Cassiopeia Battle + return 25.764; + case "mystery_encounter_gen_5_gts": // BW GTS + return 8.52; + case "mystery_encounter_gen_6_gts": // XY GTS + return 9.24; + case "mystery_encounter_fun_and_games": // EoS Guildmaster Wigglytuff + return 4.78; + case "mystery_encounter_weird_dream": // EoS Temporal Spire + return 41.42; + case "mystery_encounter_delibirdy": // Firel Delibirdy + return 82.28; } return 0; diff --git a/src/data/ability.ts b/src/data/ability.ts index 5d2ccfc9d36..33f6e0522f7 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2579,24 +2579,24 @@ export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr { // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(Abilities.DELTA_STREAM) + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) && pokemon.scene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; - } - break; + turnOffWeather = true; + } + break; } if (simulated) { @@ -4079,24 +4079,24 @@ export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr { // Clear weather only if user's ability matches the weather and no other pokemon has the ability. switch (weatherType) { - case (WeatherType.HARSH_SUN): - if (pokemon.hasAbility(Abilities.DESOLATE_LAND) + case (WeatherType.HARSH_SUN): + if (pokemon.hasAbility(Abilities.DESOLATE_LAND) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.HEAVY_RAIN): - if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) + turnOffWeather = true; + } + break; + case (WeatherType.HEAVY_RAIN): + if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) { - turnOffWeather = true; - } - break; - case (WeatherType.STRONG_WINDS): - if (pokemon.hasAbility(Abilities.DELTA_STREAM) + turnOffWeather = true; + } + break; + case (WeatherType.STRONG_WINDS): + if (pokemon.hasAbility(Abilities.DELTA_STREAM) && pokemon.scene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) { - turnOffWeather = true; - } - break; + turnOffWeather = true; + } + break; } if (simulated) { diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 2bd6ae09877..6507d34dcfd 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -88,13 +88,13 @@ export abstract class ArenaTag { */ public getAffectedPokemon(scene: BattleScene): Pokemon[] { switch (this.side) { - case ArenaTagSide.PLAYER: - return scene.getPlayerField() ?? []; - case ArenaTagSide.ENEMY: - return scene.getEnemyField() ?? []; - case ArenaTagSide.BOTH: - default: - return scene.getField(true) ?? []; + case ArenaTagSide.PLAYER: + return scene.getPlayerField() ?? []; + case ArenaTagSide.ENEMY: + return scene.getEnemyField() ?? []; + case ArenaTagSide.BOTH: + default: + return scene.getField(true) ?? []; } } } @@ -332,11 +332,11 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean = const move = allMoves[moveId]; switch (move.moveTarget) { - case MoveTarget.ALL_ENEMIES: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_OTHERS: - case MoveTarget.ALL_NEAR_OTHERS: - return true; + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_OTHERS: + case MoveTarget.ALL_NEAR_OTHERS: + return true; } return false; }; @@ -807,24 +807,24 @@ class StealthRockTag extends ArenaTrapTag { let damageHpRatio: number = 0; switch (effectiveness) { - case 0: - damageHpRatio = 0; - break; - case 0.25: - damageHpRatio = 0.03125; - break; - case 0.5: - damageHpRatio = 0.0625; - break; - case 1: - damageHpRatio = 0.125; - break; - case 2: - damageHpRatio = 0.25; - break; - case 4: - damageHpRatio = 0.5; - break; + case 0: + damageHpRatio = 0; + break; + case 0.25: + damageHpRatio = 0.03125; + break; + case 0.5: + damageHpRatio = 0.0625; + break; + case 1: + damageHpRatio = 0.125; + break; + case 2: + damageHpRatio = 0.25; + break; + case 4: + damageHpRatio = 0.5; + break; } return damageHpRatio; @@ -1185,63 +1185,63 @@ class GrassWaterPledgeTag extends ArenaTag { // TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { - case ArenaTagType.MIST: - return new MistTag(turnCount, sourceId, side); - case ArenaTagType.QUICK_GUARD: - return new QuickGuardTag(sourceId, side); - case ArenaTagType.WIDE_GUARD: - return new WideGuardTag(sourceId, side); - case ArenaTagType.MAT_BLOCK: - return new MatBlockTag(sourceId, side); - case ArenaTagType.CRAFTY_SHIELD: - return new CraftyShieldTag(sourceId, side); - case ArenaTagType.NO_CRIT: - return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct? - case ArenaTagType.MUD_SPORT: - return new MudSportTag(turnCount, sourceId); - case ArenaTagType.WATER_SPORT: - return new WaterSportTag(turnCount, sourceId); - case ArenaTagType.ION_DELUGE: - return new IonDelugeTag(sourceMove); - case ArenaTagType.SPIKES: - return new SpikesTag(sourceId, side); - case ArenaTagType.TOXIC_SPIKES: - return new ToxicSpikesTag(sourceId, side); - case ArenaTagType.FUTURE_SIGHT: - case ArenaTagType.DOOM_DESIRE: - return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang - case ArenaTagType.WISH: - return new WishTag(turnCount, sourceId, side); - case ArenaTagType.STEALTH_ROCK: - return new StealthRockTag(sourceId, side); - case ArenaTagType.STICKY_WEB: - return new StickyWebTag(sourceId, side); - case ArenaTagType.TRICK_ROOM: - return new TrickRoomTag(turnCount, sourceId); - case ArenaTagType.GRAVITY: - return new GravityTag(turnCount); - case ArenaTagType.REFLECT: - return new ReflectTag(turnCount, sourceId, side); - case ArenaTagType.LIGHT_SCREEN: - return new LightScreenTag(turnCount, sourceId, side); - case ArenaTagType.AURORA_VEIL: - return new AuroraVeilTag(turnCount, sourceId, side); - case ArenaTagType.TAILWIND: - return new TailwindTag(turnCount, sourceId, side); - case ArenaTagType.HAPPY_HOUR: - return new HappyHourTag(turnCount, sourceId, side); - case ArenaTagType.SAFEGUARD: - return new SafeguardTag(turnCount, sourceId, side); - case ArenaTagType.IMPRISON: - return new ImprisonTag(sourceId, side); - case ArenaTagType.FIRE_GRASS_PLEDGE: - return new FireGrassPledgeTag(sourceId, side); - case ArenaTagType.WATER_FIRE_PLEDGE: - return new WaterFirePledgeTag(sourceId, side); - case ArenaTagType.GRASS_WATER_PLEDGE: - return new GrassWaterPledgeTag(sourceId, side); - default: - return null; + case ArenaTagType.MIST: + return new MistTag(turnCount, sourceId, side); + case ArenaTagType.QUICK_GUARD: + return new QuickGuardTag(sourceId, side); + case ArenaTagType.WIDE_GUARD: + return new WideGuardTag(sourceId, side); + case ArenaTagType.MAT_BLOCK: + return new MatBlockTag(sourceId, side); + case ArenaTagType.CRAFTY_SHIELD: + return new CraftyShieldTag(sourceId, side); + case ArenaTagType.NO_CRIT: + return new NoCritTag(turnCount, sourceMove!, sourceId, side); // TODO: is this bang correct? + case ArenaTagType.MUD_SPORT: + return new MudSportTag(turnCount, sourceId); + case ArenaTagType.WATER_SPORT: + return new WaterSportTag(turnCount, sourceId); + case ArenaTagType.ION_DELUGE: + return new IonDelugeTag(sourceMove); + case ArenaTagType.SPIKES: + return new SpikesTag(sourceId, side); + case ArenaTagType.TOXIC_SPIKES: + return new ToxicSpikesTag(sourceId, side); + case ArenaTagType.FUTURE_SIGHT: + case ArenaTagType.DOOM_DESIRE: + return new DelayedAttackTag(tagType, sourceMove, sourceId, targetIndex!); // TODO:questionable bang + case ArenaTagType.WISH: + return new WishTag(turnCount, sourceId, side); + case ArenaTagType.STEALTH_ROCK: + return new StealthRockTag(sourceId, side); + case ArenaTagType.STICKY_WEB: + return new StickyWebTag(sourceId, side); + case ArenaTagType.TRICK_ROOM: + return new TrickRoomTag(turnCount, sourceId); + case ArenaTagType.GRAVITY: + return new GravityTag(turnCount); + case ArenaTagType.REFLECT: + return new ReflectTag(turnCount, sourceId, side); + case ArenaTagType.LIGHT_SCREEN: + return new LightScreenTag(turnCount, sourceId, side); + case ArenaTagType.AURORA_VEIL: + return new AuroraVeilTag(turnCount, sourceId, side); + case ArenaTagType.TAILWIND: + return new TailwindTag(turnCount, sourceId, side); + case ArenaTagType.HAPPY_HOUR: + return new HappyHourTag(turnCount, sourceId, side); + case ArenaTagType.SAFEGUARD: + return new SafeguardTag(turnCount, sourceId, side); + case ArenaTagType.IMPRISON: + return new ImprisonTag(sourceId, side); + case ArenaTagType.FIRE_GRASS_PLEDGE: + return new FireGrassPledgeTag(sourceId, side); + case ArenaTagType.WATER_FIRE_PLEDGE: + return new WaterFirePledgeTag(sourceId, side); + case ArenaTagType.GRASS_WATER_PLEDGE: + return new GrassWaterPledgeTag(sourceId, side); + default: + return null; } } diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 7ef83b654db..2ce693c360b 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -13,14 +13,14 @@ export function getBiomeName(biome: Biome | -1) { return i18next.t("biome:unknownLocation"); } switch (biome) { - case Biome.GRASS: - return i18next.t("biome:GRASS"); - case Biome.RUINS: - return i18next.t("biome:RUINS"); - case Biome.END: - return i18next.t("biome:END"); - default: - return i18next.t(`biome:${Biome[biome].toUpperCase()}`); + case Biome.GRASS: + return i18next.t("biome:GRASS"); + case Biome.RUINS: + return i18next.t("biome:RUINS"); + case Biome.END: + return i18next.t("biome:END"); + default: + return i18next.t(`biome:${Biome[biome].toUpperCase()}`); } } diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index bf3a1f7ad56..d6a1f0c3eaf 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -15,25 +15,25 @@ export const FRIENDSHIP_LOSS_FROM_FAINT = 10; */ export function getStarterValueFriendshipCap(starterCost: number): number { switch (starterCost) { - case 1: - return 20; - case 2: - return 40; - case 3: - return 60; - case 4: - return 100; - case 5: - return 140; - case 6: - return 200; - case 7: - return 280; - case 8: - case 9: - return 450; - default: - return 600; + case 1: + return 20; + case 2: + return 40; + case 3: + return 60; + case 4: + return 100; + case 5: + return 140; + case 6: + return 200; + case 7: + return 280; + case 8: + case 9: + return 450; + default: + return 600; } } diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 939f834cccc..03bf0809fa6 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -134,15 +134,15 @@ export class AnimConfig { for (const te of frameTimedEvents[fte]) { let timedEvent: AnimTimedEvent | undefined; switch (te.eventType) { - case "AnimTimedSoundEvent": - timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); - break; - case "AnimTimedAddBgEvent": - timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te); - break; - case "AnimTimedUpdateBgEvent": - timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); - break; + case "AnimTimedSoundEvent": + timedEvent = new AnimTimedSoundEvent(te.frameIndex, te.resourceName, te); + break; + case "AnimTimedAddBgEvent": + timedEvent = new AnimTimedAddBgEvent(te.frameIndex, te.resourceName, te); + break; + case "AnimTimedUpdateBgEvent": + timedEvent = new AnimTimedUpdateBgEvent(te.frameIndex, te.resourceName, te); + break; } timedEvent && timedEvents.push(timedEvent); @@ -243,12 +243,12 @@ class AnimFrame { if (!init) { let target = AnimFrameTarget.GRAPHIC; switch (pattern) { - case -2: - target = AnimFrameTarget.TARGET; - break; - case -1: - target = AnimFrameTarget.USER; - break; + case -2: + target = AnimFrameTarget.TARGET; + break; + case -1: + target = AnimFrameTarget.USER; + break; } this.target = target; this.graphicFrame = pattern >= 0 ? pattern : 0; @@ -803,23 +803,23 @@ export abstract class BattleAnim { let scaleX = (frame.zoomX / 100) * (!frame.mirror ? 1 : -1); const scaleY = (frame.zoomY / 100); switch (frame.focus) { - case AnimFocus.TARGET: - x += targetInitialX - targetFocusX; - y += (targetInitialY - targetHalfHeight) - targetFocusY; - break; - case AnimFocus.USER: - x += userInitialX - userFocusX; - y += (userInitialY - userHalfHeight) - userFocusY; - break; - case AnimFocus.USER_TARGET: - const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3], - this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y); - x = point[0]; - y = point[1]; - if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) { - scaleX = scaleX * -1; - } - break; + case AnimFocus.TARGET: + x += targetInitialX - targetFocusX; + y += (targetInitialY - targetHalfHeight) - targetFocusY; + break; + case AnimFocus.USER: + x += userInitialX - userFocusX; + y += (userInitialY - userHalfHeight) - userFocusY; + break; + case AnimFocus.USER_TARGET: + const point = transformPoint(this.srcLine[0], this.srcLine[1], this.srcLine[2], this.srcLine[3], + this.dstLine[0], this.dstLine[1] - userHalfHeight, this.dstLine[2], this.dstLine[3] - targetHalfHeight, x, y); + x = point[0]; + y = point[1]; + if (frame.target === AnimFrameTarget.GRAPHIC && isReversed(this.srcLine[0], this.srcLine[2], this.dstLine[0], this.dstLine[2])) { + scaleX = scaleX * -1; + } + break; } const angle = -frame.angle; const key = frame.target === AnimFrameTarget.GRAPHIC ? g++ : frame.target === AnimFrameTarget.USER ? u++ : t++; @@ -993,44 +993,44 @@ export abstract class BattleAnim { spritePriorities[graphicIndex] = frame.priority; const setSpritePriority = (priority: integer) => { switch (priority) { - case 0: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? - break; - case 1: - scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); - break; - case 2: - switch (frame.focus) { - case AnimFocus.USER: - if (this.bgSprite) { - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); - } else { - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + case 0: + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, scene.getNonSwitchedEnemyPokemon() || scene.getNonSwitchedPlayerPokemon()!); // This bang assumes that if (the EnemyPokemon is undefined, then the PlayerPokemon function must return an object), correct assumption? + break; + case 1: + scene.field.moveTo(moveSprite, scene.field.getAll().length - 1); + break; + case 2: + switch (frame.focus) { + case AnimFocus.USER: + if (this.bgSprite) { + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.bgSprite); + } else { + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + } + break; + case AnimFocus.TARGET: + scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + break; + default: + setSpritePriority(1); + break; } break; - case AnimFocus.TARGET: - scene.field.moveBelow(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + case 3: + switch (frame.focus) { + case AnimFocus.USER: + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? + break; + case AnimFocus.TARGET: + scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? + break; + default: + setSpritePriority(1); + break; + } break; default: setSpritePriority(1); - break; - } - break; - case 3: - switch (frame.focus) { - case AnimFocus.USER: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.user!); // TODO: is this bang correct? - break; - case AnimFocus.TARGET: - scene.field.moveAbove(moveSprite as Phaser.GameObjects.GameObject, this.target!); // TODO: is this bang correct? - break; - default: - setSpritePriority(1); - break; - } - break; - default: - setSpritePriority(1); } }; setSpritePriority(frame.priority); @@ -1396,108 +1396,108 @@ export async function populateAnims() { const fieldName = field.slice(0, field.indexOf(":")); const fieldData = field.slice(fieldName.length + 1, field.lastIndexOf("\n")).trim(); switch (fieldName) { - case "array": - const framesData = fieldData.split(" - - - ").slice(1); - for (let fd = 0; fd < framesData.length; fd++) { - anim.frames.push([]); - const frameData = framesData[fd]; - const focusFramesData = frameData.split(" - - "); - for (let tf = 0; tf < focusFramesData.length; tf++) { - const values = focusFramesData[tf].replace(/ \- /g, "").split("\n"); - const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]), - parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]), - parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]), - parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus); - anim.frames[fd].push(targetFrame); + case "array": + const framesData = fieldData.split(" - - - ").slice(1); + for (let fd = 0; fd < framesData.length; fd++) { + anim.frames.push([]); + const frameData = framesData[fd]; + const focusFramesData = frameData.split(" - - "); + for (let tf = 0; tf < focusFramesData.length; tf++) { + const values = focusFramesData[tf].replace(/ \- /g, "").split("\n"); + const targetFrame = new AnimFrame(parseFloat(values[0]), parseFloat(values[1]), parseFloat(values[2]), parseFloat(values[11]), parseFloat(values[3]), + parseInt(values[4]) === 1, parseInt(values[6]) === 1, parseInt(values[5]), parseInt(values[7]), parseInt(values[8]), parseInt(values[12]), parseInt(values[13]), + parseInt(values[14]), parseInt(values[15]), parseInt(values[16]), parseInt(values[17]), parseInt(values[18]), parseInt(values[19]), + parseInt(values[21]), parseInt(values[22]), parseInt(values[23]), parseInt(values[24]), parseInt(values[20]) === 1, parseInt(values[25]), parseInt(values[26]) as AnimFocus); + anim.frames[fd].push(targetFrame); + } } - } - break; - case "graphic": - const graphic = fieldData !== "''" ? fieldData : ""; - anim.graphic = graphic.indexOf(".") > -1 - ? graphic.slice(0, fieldData.indexOf(".")) - : graphic; - break; - case "timing": - const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1); - for (let t = 0; t < timingEntries.length; t++) { - const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") - .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); - const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct? - let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct? - const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct? - let timedEvent: AnimTimedEvent | undefined; - switch (timingType) { - case 0: - if (resourceName && resourceName.indexOf(".") === -1) { - let ext: string | undefined; - [ "wav", "mp3", "m4a" ].every(e => { - if (seNames.indexOf(`${resourceName}.${e}`) > -1) { - ext = e; - return false; + break; + case "graphic": + const graphic = fieldData !== "''" ? fieldData : ""; + anim.graphic = graphic.indexOf(".") > -1 + ? graphic.slice(0, fieldData.indexOf(".")) + : graphic; + break; + case "timing": + const timingEntries = fieldData.split("- !ruby/object:PBAnimTiming ").slice(1); + for (let t = 0; t < timingEntries.length; t++) { + const timingData = timingEntries[t].replace(/\n/g, " ").replace(/[ ]{2,}/g, " ").replace(/[a-z]+: ! '', /ig, "").replace(/name: (.*?),/, "name: \"$1\",") + .replace(/flashColor: !ruby\/object:Color { alpha: ([\d\.]+), blue: ([\d\.]+), green: ([\d\.]+), red: ([\d\.]+)}/, "flashRed: $4, flashGreen: $3, flashBlue: $2, flashAlpha: $1"); + const frameIndex = parseInt(/frame: (\d+)/.exec(timingData)![1]); // TODO: is the bang correct? + let resourceName = /name: "(.*?)"/.exec(timingData)![1].replace("''", ""); // TODO: is the bang correct? + const timingType = parseInt(/timingType: (\d)/.exec(timingData)![1]); // TODO: is the bang correct? + let timedEvent: AnimTimedEvent | undefined; + switch (timingType) { + case 0: + if (resourceName && resourceName.indexOf(".") === -1) { + let ext: string | undefined; + [ "wav", "mp3", "m4a" ].every(e => { + if (seNames.indexOf(`${resourceName}.${e}`) > -1) { + ext = e; + return false; + } + return true; + }); + if (!ext) { + ext = ".wav"; + } + resourceName += `.${ext}`; } - return true; - }); - if (!ext) { - ext = ".wav"; + timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName); + break; + case 1: + timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); + break; + case 2: + timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); + break; + } + if (!timedEvent) { + continue; + } + const propPattern = /([a-z]+): (.*?)(?:,|\})/ig; + let propMatch: RegExpExecArray; + while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct? + const prop = propMatch[1]; + let value: any = propMatch[2]; + switch (prop) { + case "bgX": + case "bgY": + value = parseFloat(value); + break; + case "volume": + case "pitch": + case "opacity": + case "colorRed": + case "colorGreen": + case "colorBlue": + case "colorAlpha": + case "duration": + case "flashScope": + case "flashRed": + case "flashGreen": + case "flashBlue": + case "flashAlpha": + case "flashDuration": + value = parseInt(value); + break; + } + if (timedEvent.hasOwnProperty(prop)) { + timedEvent[prop] = value; } - resourceName += `.${ext}`; } - timedEvent = new AnimTimedSoundEvent(frameIndex, resourceName); - break; - case 1: - timedEvent = new AnimTimedAddBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); - break; - case 2: - timedEvent = new AnimTimedUpdateBgEvent(frameIndex, resourceName.slice(0, resourceName.indexOf("."))); - break; - } - if (!timedEvent) { - continue; - } - const propPattern = /([a-z]+): (.*?)(?:,|\})/ig; - let propMatch: RegExpExecArray; - while ((propMatch = propPattern.exec(timingData)!)) { // TODO: is this bang correct? - const prop = propMatch[1]; - let value: any = propMatch[2]; - switch (prop) { - case "bgX": - case "bgY": - value = parseFloat(value); - break; - case "volume": - case "pitch": - case "opacity": - case "colorRed": - case "colorGreen": - case "colorBlue": - case "colorAlpha": - case "duration": - case "flashScope": - case "flashRed": - case "flashGreen": - case "flashBlue": - case "flashAlpha": - case "flashDuration": - value = parseInt(value); - break; + if (!anim.frameTimedEvents.has(frameIndex)) { + anim.frameTimedEvents.set(frameIndex, []); } - if (timedEvent.hasOwnProperty(prop)) { - timedEvent[prop] = value; - } - } - if (!anim.frameTimedEvents.has(frameIndex)) { - anim.frameTimedEvents.set(frameIndex, []); - } anim.frameTimedEvents.get(frameIndex)!.push(timedEvent); // TODO: is this bang correct? - } - break; - case "position": - anim.position = parseInt(fieldData); - break; - case "hue": - anim.hue = parseInt(fieldData); - break; + } + break; + case "position": + anim.position = parseInt(fieldData); + break; + case "hue": + anim.hue = parseInt(fieldData); + break; } } } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 8491307fc76..4977a8da5a9 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -919,14 +919,14 @@ export class EncoreTag extends BattlerTag { } switch (repeatableMove.move) { - case Moves.MIMIC: - case Moves.MIRROR_MOVE: - case Moves.TRANSFORM: - case Moves.STRUGGLE: - case Moves.SKETCH: - case Moves.SLEEP_TALK: - case Moves.ENCORE: - return false; + case Moves.MIMIC: + case Moves.MIRROR_MOVE: + case Moves.TRANSFORM: + case Moves.STRUGGLE: + case Moves.SKETCH: + case Moves.SLEEP_TALK: + case Moves.ENCORE: + return false; } if (allMoves[repeatableMove.move].hasAttr(ChargeAttr) && repeatableMove.result === MoveResult.OTHER) { @@ -1641,12 +1641,12 @@ export class HighestStatBoostTag extends AbilityBattlerTag { this.stat = highestStat; switch (this.stat) { - case Stat.SPD: - this.multiplier = 1.5; - break; - default: - this.multiplier = 1.3; - break; + case Stat.SPD: + this.multiplier = 1.5; + break; + default: + this.multiplier = 1.3; + break; } pokemon.scene.queueMessage(i18next.t("battlerTags:highestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: i18next.t(getStatKey(highestStat)) }), null, false, null, true); @@ -2436,15 +2436,15 @@ export class SubstituteTag extends BattlerTag { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { switch (lapseType) { - case BattlerTagLapseType.PRE_MOVE: - this.onPreMove(pokemon); - break; - case BattlerTagLapseType.AFTER_MOVE: - this.onAfterMove(pokemon); - break; - case BattlerTagLapseType.HIT: - this.onHit(pokemon); - break; + case BattlerTagLapseType.PRE_MOVE: + this.onPreMove(pokemon); + break; + case BattlerTagLapseType.AFTER_MOVE: + this.onAfterMove(pokemon); + break; + case BattlerTagLapseType.HIT: + this.onHit(pokemon); + break; } return lapseType !== BattlerTagLapseType.CUSTOM; // only remove this tag on custom lapse } @@ -2769,179 +2769,179 @@ export class PowerTrickTag extends BattlerTag { */ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, sourceMove: Moves, sourceId: number): BattlerTag { switch (tagType) { - case BattlerTagType.RECHARGING: - return new RechargingTag(sourceMove); - case BattlerTagType.BEAK_BLAST_CHARGING: - return new BeakBlastChargingTag(); - case BattlerTagType.SHELL_TRAP: - return new ShellTrapTag(); - case BattlerTagType.FLINCHED: - return new FlinchedTag(sourceMove); - case BattlerTagType.INTERRUPTED: - return new InterruptedTag(sourceMove); - case BattlerTagType.CONFUSED: - return new ConfusedTag(turnCount, sourceMove); - case BattlerTagType.INFATUATED: - return new InfatuatedTag(sourceMove, sourceId); - case BattlerTagType.SEEDED: - return new SeedTag(sourceId); - case BattlerTagType.NIGHTMARE: - return new NightmareTag(); - case BattlerTagType.FRENZY: - return new FrenzyTag(turnCount, sourceMove, sourceId); - case BattlerTagType.CHARGING: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); - case BattlerTagType.ENCORE: - return new EncoreTag(sourceId); - case BattlerTagType.HELPING_HAND: - return new HelpingHandTag(sourceId); - case BattlerTagType.INGRAIN: - return new IngrainTag(sourceId); - case BattlerTagType.AQUA_RING: - return new AquaRingTag(); - case BattlerTagType.DROWSY: - return new DrowsyTag(); - case BattlerTagType.TRAPPED: - return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); - case BattlerTagType.NO_RETREAT: - return new NoRetreatTag(sourceId); - case BattlerTagType.BIND: - return new BindTag(turnCount, sourceId); - case BattlerTagType.WRAP: - return new WrapTag(turnCount, sourceId); - case BattlerTagType.FIRE_SPIN: - return new FireSpinTag(turnCount, sourceId); - case BattlerTagType.WHIRLPOOL: - return new WhirlpoolTag(turnCount, sourceId); - case BattlerTagType.CLAMP: - return new ClampTag(turnCount, sourceId); - case BattlerTagType.SAND_TOMB: - return new SandTombTag(turnCount, sourceId); - case BattlerTagType.MAGMA_STORM: - return new MagmaStormTag(turnCount, sourceId); - case BattlerTagType.SNAP_TRAP: - return new SnapTrapTag(turnCount, sourceId); - case BattlerTagType.THUNDER_CAGE: - return new ThunderCageTag(turnCount, sourceId); - case BattlerTagType.INFESTATION: - return new InfestationTag(turnCount, sourceId); - case BattlerTagType.PROTECTED: - return new ProtectedTag(sourceMove); - case BattlerTagType.SPIKY_SHIELD: - return new ContactDamageProtectedTag(sourceMove, 8); - case BattlerTagType.KINGS_SHIELD: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1); - case BattlerTagType.OBSTRUCT: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2); - case BattlerTagType.SILK_TRAP: - return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); - case BattlerTagType.BANEFUL_BUNKER: - return new ContactPoisonProtectedTag(sourceMove); - case BattlerTagType.BURNING_BULWARK: - return new ContactBurnProtectedTag(sourceMove); - case BattlerTagType.ENDURING: - return new EnduringTag(sourceMove); - case BattlerTagType.STURDY: - return new SturdyTag(sourceMove); - case BattlerTagType.PERISH_SONG: - return new PerishSongTag(turnCount); - case BattlerTagType.CENTER_OF_ATTENTION: - return new CenterOfAttentionTag(sourceMove); - case BattlerTagType.TRUANT: - return new TruantTag(); - case BattlerTagType.SLOW_START: - return new SlowStartTag(); - case BattlerTagType.PROTOSYNTHESIS: - return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); - case BattlerTagType.QUARK_DRIVE: - return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); - case BattlerTagType.FLYING: - case BattlerTagType.UNDERGROUND: - case BattlerTagType.UNDERWATER: - case BattlerTagType.HIDDEN: - return new SemiInvulnerableTag(tagType, turnCount, sourceMove); - case BattlerTagType.FIRE_BOOST: - return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false); - case BattlerTagType.CRIT_BOOST: - return new CritBoostTag(tagType, sourceMove); - case BattlerTagType.DRAGON_CHEER: - return new DragonCheerTag(); - case BattlerTagType.ALWAYS_CRIT: - case BattlerTagType.IGNORE_ACCURACY: - return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); - case BattlerTagType.ALWAYS_GET_HIT: - case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: - return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); - case BattlerTagType.BYPASS_SLEEP: - return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); - case BattlerTagType.IGNORE_FLYING: - return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.ROOSTED: - return new RoostedTag(); - case BattlerTagType.BURNED_UP: - return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.DOUBLE_SHOCKED: - return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); - case BattlerTagType.SALT_CURED: - return new SaltCuredTag(sourceId); - case BattlerTagType.CURSED: - return new CursedTag(sourceId); - case BattlerTagType.CHARGED: - return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); - case BattlerTagType.FLOATING: - return new FloatingTag(tagType, sourceMove); - case BattlerTagType.MINIMIZED: - return new MinimizeTag(); - case BattlerTagType.DESTINY_BOND: - return new DestinyBondTag(sourceMove, sourceId); - case BattlerTagType.ICE_FACE: - return new IceFaceBlockDamageTag(tagType); - case BattlerTagType.DISGUISE: - return new FormBlockDamageTag(tagType); - case BattlerTagType.STOCKPILING: - return new StockpilingTag(sourceMove); - case BattlerTagType.OCTOLOCK: - return new OctolockTag(sourceId); - case BattlerTagType.DISABLED: - return new DisabledTag(sourceId); - case BattlerTagType.IGNORE_GHOST: - return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); - case BattlerTagType.IGNORE_DARK: - return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); - case BattlerTagType.GULP_MISSILE_ARROKUDA: - case BattlerTagType.GULP_MISSILE_PIKACHU: - return new GulpMissileTag(tagType, sourceMove); - case BattlerTagType.TAR_SHOT: - return new TarShotTag(); - case BattlerTagType.ELECTRIFIED: - return new ElectrifiedTag(); - case BattlerTagType.THROAT_CHOPPED: - return new ThroatChoppedTag(); - case BattlerTagType.GORILLA_TACTICS: - return new GorillaTacticsTag(); - case BattlerTagType.SUBSTITUTE: - return new SubstituteTag(sourceMove, sourceId); - case BattlerTagType.AUTOTOMIZED: - return new AutotomizedTag(); - case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: - return new MysteryEncounterPostSummonTag(); - case BattlerTagType.HEAL_BLOCK: - return new HealBlockTag(turnCount, sourceMove); - case BattlerTagType.TORMENT: - return new TormentTag(sourceId); - case BattlerTagType.TAUNT: - return new TauntTag(); - case BattlerTagType.IMPRISON: - return new ImprisonTag(sourceId); - case BattlerTagType.SYRUP_BOMB: - return new SyrupBombTag(sourceId); - case BattlerTagType.TELEKINESIS: - return new TelekinesisTag(sourceMove); - case BattlerTagType.POWER_TRICK: - return new PowerTrickTag(sourceMove, sourceId); - case BattlerTagType.NONE: - default: - return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); + case BattlerTagType.RECHARGING: + return new RechargingTag(sourceMove); + case BattlerTagType.BEAK_BLAST_CHARGING: + return new BeakBlastChargingTag(); + case BattlerTagType.SHELL_TRAP: + return new ShellTrapTag(); + case BattlerTagType.FLINCHED: + return new FlinchedTag(sourceMove); + case BattlerTagType.INTERRUPTED: + return new InterruptedTag(sourceMove); + case BattlerTagType.CONFUSED: + return new ConfusedTag(turnCount, sourceMove); + case BattlerTagType.INFATUATED: + return new InfatuatedTag(sourceMove, sourceId); + case BattlerTagType.SEEDED: + return new SeedTag(sourceId); + case BattlerTagType.NIGHTMARE: + return new NightmareTag(); + case BattlerTagType.FRENZY: + return new FrenzyTag(turnCount, sourceMove, sourceId); + case BattlerTagType.CHARGING: + return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, 1, sourceMove, sourceId); + case BattlerTagType.ENCORE: + return new EncoreTag(sourceId); + case BattlerTagType.HELPING_HAND: + return new HelpingHandTag(sourceId); + case BattlerTagType.INGRAIN: + return new IngrainTag(sourceId); + case BattlerTagType.AQUA_RING: + return new AquaRingTag(); + case BattlerTagType.DROWSY: + return new DrowsyTag(); + case BattlerTagType.TRAPPED: + return new TrappedTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); + case BattlerTagType.NO_RETREAT: + return new NoRetreatTag(sourceId); + case BattlerTagType.BIND: + return new BindTag(turnCount, sourceId); + case BattlerTagType.WRAP: + return new WrapTag(turnCount, sourceId); + case BattlerTagType.FIRE_SPIN: + return new FireSpinTag(turnCount, sourceId); + case BattlerTagType.WHIRLPOOL: + return new WhirlpoolTag(turnCount, sourceId); + case BattlerTagType.CLAMP: + return new ClampTag(turnCount, sourceId); + case BattlerTagType.SAND_TOMB: + return new SandTombTag(turnCount, sourceId); + case BattlerTagType.MAGMA_STORM: + return new MagmaStormTag(turnCount, sourceId); + case BattlerTagType.SNAP_TRAP: + return new SnapTrapTag(turnCount, sourceId); + case BattlerTagType.THUNDER_CAGE: + return new ThunderCageTag(turnCount, sourceId); + case BattlerTagType.INFESTATION: + return new InfestationTag(turnCount, sourceId); + case BattlerTagType.PROTECTED: + return new ProtectedTag(sourceMove); + case BattlerTagType.SPIKY_SHIELD: + return new ContactDamageProtectedTag(sourceMove, 8); + case BattlerTagType.KINGS_SHIELD: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1); + case BattlerTagType.OBSTRUCT: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2); + case BattlerTagType.SILK_TRAP: + return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); + case BattlerTagType.BANEFUL_BUNKER: + return new ContactPoisonProtectedTag(sourceMove); + case BattlerTagType.BURNING_BULWARK: + return new ContactBurnProtectedTag(sourceMove); + case BattlerTagType.ENDURING: + return new EnduringTag(sourceMove); + case BattlerTagType.STURDY: + return new SturdyTag(sourceMove); + case BattlerTagType.PERISH_SONG: + return new PerishSongTag(turnCount); + case BattlerTagType.CENTER_OF_ATTENTION: + return new CenterOfAttentionTag(sourceMove); + case BattlerTagType.TRUANT: + return new TruantTag(); + case BattlerTagType.SLOW_START: + return new SlowStartTag(); + case BattlerTagType.PROTOSYNTHESIS: + return new WeatherHighestStatBoostTag(tagType, Abilities.PROTOSYNTHESIS, WeatherType.SUNNY, WeatherType.HARSH_SUN); + case BattlerTagType.QUARK_DRIVE: + return new TerrainHighestStatBoostTag(tagType, Abilities.QUARK_DRIVE, TerrainType.ELECTRIC); + case BattlerTagType.FLYING: + case BattlerTagType.UNDERGROUND: + case BattlerTagType.UNDERWATER: + case BattlerTagType.HIDDEN: + return new SemiInvulnerableTag(tagType, turnCount, sourceMove); + case BattlerTagType.FIRE_BOOST: + return new TypeBoostTag(tagType, sourceMove, Type.FIRE, 1.5, false); + case BattlerTagType.CRIT_BOOST: + return new CritBoostTag(tagType, sourceMove); + case BattlerTagType.DRAGON_CHEER: + return new DragonCheerTag(); + case BattlerTagType.ALWAYS_CRIT: + case BattlerTagType.IGNORE_ACCURACY: + return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove); + case BattlerTagType.ALWAYS_GET_HIT: + case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: + return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove); + case BattlerTagType.BYPASS_SLEEP: + return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, turnCount, sourceMove); + case BattlerTagType.IGNORE_FLYING: + return new GroundedTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.ROOSTED: + return new RoostedTag(); + case BattlerTagType.BURNED_UP: + return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.DOUBLE_SHOCKED: + return new RemovedTypeTag(tagType, BattlerTagLapseType.CUSTOM, sourceMove); + case BattlerTagType.SALT_CURED: + return new SaltCuredTag(sourceId); + case BattlerTagType.CURSED: + return new CursedTag(sourceId); + case BattlerTagType.CHARGED: + return new TypeBoostTag(tagType, sourceMove, Type.ELECTRIC, 2, true); + case BattlerTagType.FLOATING: + return new FloatingTag(tagType, sourceMove); + case BattlerTagType.MINIMIZED: + return new MinimizeTag(); + case BattlerTagType.DESTINY_BOND: + return new DestinyBondTag(sourceMove, sourceId); + case BattlerTagType.ICE_FACE: + return new IceFaceBlockDamageTag(tagType); + case BattlerTagType.DISGUISE: + return new FormBlockDamageTag(tagType); + case BattlerTagType.STOCKPILING: + return new StockpilingTag(sourceMove); + case BattlerTagType.OCTOLOCK: + return new OctolockTag(sourceId); + case BattlerTagType.DISABLED: + return new DisabledTag(sourceId); + case BattlerTagType.IGNORE_GHOST: + return new ExposedTag(tagType, sourceMove, Type.GHOST, [ Type.NORMAL, Type.FIGHTING ]); + case BattlerTagType.IGNORE_DARK: + return new ExposedTag(tagType, sourceMove, Type.DARK, [ Type.PSYCHIC ]); + case BattlerTagType.GULP_MISSILE_ARROKUDA: + case BattlerTagType.GULP_MISSILE_PIKACHU: + return new GulpMissileTag(tagType, sourceMove); + case BattlerTagType.TAR_SHOT: + return new TarShotTag(); + case BattlerTagType.ELECTRIFIED: + return new ElectrifiedTag(); + case BattlerTagType.THROAT_CHOPPED: + return new ThroatChoppedTag(); + case BattlerTagType.GORILLA_TACTICS: + return new GorillaTacticsTag(); + case BattlerTagType.SUBSTITUTE: + return new SubstituteTag(sourceMove, sourceId); + case BattlerTagType.AUTOTOMIZED: + return new AutotomizedTag(); + case BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON: + return new MysteryEncounterPostSummonTag(); + case BattlerTagType.HEAL_BLOCK: + return new HealBlockTag(turnCount, sourceMove); + case BattlerTagType.TORMENT: + return new TormentTag(sourceId); + case BattlerTagType.TAUNT: + return new TauntTag(); + case BattlerTagType.IMPRISON: + return new ImprisonTag(sourceId); + case BattlerTagType.SYRUP_BOMB: + return new SyrupBombTag(sourceId); + case BattlerTagType.TELEKINESIS: + return new TelekinesisTag(sourceMove); + case BattlerTagType.POWER_TRICK: + return new PowerTrickTag(sourceMove, sourceId); + case BattlerTagType.NONE: + default: + return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId); } } diff --git a/src/data/berry.ts b/src/data/berry.ts index 01325ee39dd..7243c4c1b2e 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -22,42 +22,42 @@ export type BerryPredicate = (pokemon: Pokemon) => boolean; export function getBerryPredicate(berryType: BerryType): BerryPredicate { switch (berryType) { - case BerryType.SITRUS: - return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5; - case BerryType.LUM: - return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); - case BerryType.ENIGMA: - return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length; - case BerryType.LIECHI: - case BerryType.GANLON: - case BerryType.PETAYA: - case BerryType.APICOT: - case BerryType.SALAC: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth - const stat: BattleStat = berryType - BerryType.ENIGMA; - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; - }; - case BerryType.LANSAT: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); - }; - case BerryType.STARF: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return pokemon.getHpRatio() < 0.25; - }; - case BerryType.LEPPA: - return (pokemon: Pokemon) => { - const threshold = new Utils.NumberHolder(0.25); - applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); - return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); - }; + case BerryType.SITRUS: + return (pokemon: Pokemon) => pokemon.getHpRatio() < 0.5; + case BerryType.LUM: + return (pokemon: Pokemon) => !!pokemon.status || !!pokemon.getTag(BattlerTagType.CONFUSED); + case BerryType.ENIGMA: + return (pokemon: Pokemon) => !!pokemon.turnData.attacksReceived.filter(a => a.result === HitResult.SUPER_EFFECTIVE).length; + case BerryType.LIECHI: + case BerryType.GANLON: + case BerryType.PETAYA: + case BerryType.APICOT: + case BerryType.SALAC: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth + const stat: BattleStat = berryType - BerryType.ENIGMA; + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6; + }; + case BerryType.LANSAT: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < 0.25 && !pokemon.getTag(BattlerTagType.CRIT_BOOST); + }; + case BerryType.STARF: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return pokemon.getHpRatio() < 0.25; + }; + case BerryType.LEPPA: + return (pokemon: Pokemon) => { + const threshold = new Utils.NumberHolder(0.25); + applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, false, threshold); + return !!pokemon.getMoveset().find(m => !m?.getPpRatio()); + }; } } @@ -65,70 +65,70 @@ export type BerryEffectFunc = (pokemon: Pokemon) => void; export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { switch (berryType) { - case BerryType.SITRUS: - case BerryType.ENIGMA: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); - pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); - }; - case BerryType.LUM: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - if (pokemon.status) { - pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); - } - pokemon.resetStatus(true, true); - pokemon.updateInfo(); - }; - case BerryType.LIECHI: - case BerryType.GANLON: - case BerryType.PETAYA: - case BerryType.APICOT: - case BerryType.SALAC: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth - const stat: BattleStat = berryType - BerryType.ENIGMA; - const statStages = new Utils.NumberHolder(1); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); - }; - case BerryType.LANSAT: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - pokemon.addTag(BattlerTagType.CRIT_BOOST); - }; - case BerryType.STARF: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); - const stages = new Utils.NumberHolder(2); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); - pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); - }; - case BerryType.LEPPA: - return (pokemon: Pokemon) => { - if (pokemon.battleData) { - pokemon.battleData.berriesEaten.push(berryType); - } - const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? - if (ppRestoreMove !== undefined) { + case BerryType.SITRUS: + case BerryType.ENIGMA: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const hpHealed = new Utils.NumberHolder(Utils.toDmgValue(pokemon.getMaxHp() / 4)); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, hpHealed); + pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), + hpHealed.value, i18next.t("battle:hpHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), berryName: getBerryName(berryType) }), true)); + }; + case BerryType.LUM: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + if (pokemon.status) { + pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); + } + pokemon.resetStatus(true, true); + pokemon.updateInfo(); + }; + case BerryType.LIECHI: + case BerryType.GANLON: + case BerryType.PETAYA: + case BerryType.APICOT: + case BerryType.SALAC: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + // Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth + const stat: BattleStat = berryType - BerryType.ENIGMA; + const statStages = new Utils.NumberHolder(1); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, statStages); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); + }; + case BerryType.LANSAT: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + pokemon.addTag(BattlerTagType.CRIT_BOOST); + }; + case BerryType.STARF: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const randStat = Utils.randSeedInt(Stat.SPD, Stat.ATK); + const stages = new Utils.NumberHolder(2); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, false, stages); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); + }; + case BerryType.LEPPA: + return (pokemon: Pokemon) => { + if (pokemon.battleData) { + pokemon.battleData.berriesEaten.push(berryType); + } + const ppRestoreMove = pokemon.getMoveset().find(m => !m?.getPpRatio()) ? pokemon.getMoveset().find(m => !m?.getPpRatio()) : pokemon.getMoveset().find(m => m!.getPpRatio() < 1); // TODO: is this bang correct? + if (ppRestoreMove !== undefined) { ppRestoreMove!.ppUsed = Math.max(ppRestoreMove!.ppUsed - 10, 0); pokemon.scene.queueMessage(i18next.t("battle:ppHealBerry", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: ppRestoreMove!.getName(), berryName: getBerryName(berryType) })); - } - }; + } + }; } } diff --git a/src/data/challenge.ts b/src/data/challenge.ts index 7ecdfbf36e1..a64a90e5d14 100644 --- a/src/data/challenge.ts +++ b/src/data/challenge.ts @@ -448,21 +448,21 @@ export class SingleGenerationChallenge extends Challenge { applyFixedBattle(waveIndex: Number, battleConfig: FixedBattleConfig): boolean { let trainerTypes: TrainerType[] = []; switch (waveIndex) { - case 182: - trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; - break; - case 184: - trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; - break; - case 186: - trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; - break; - case 188: - trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; - break; - case 190: - trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; - break; + case 182: + trainerTypes = [ TrainerType.LORELEI, TrainerType.WILL, TrainerType.SIDNEY, TrainerType.AARON, TrainerType.SHAUNTAL, TrainerType.MALVA, Utils.randSeedItem([ TrainerType.HALA, TrainerType.MOLAYNE ]), TrainerType.MARNIE_ELITE, TrainerType.RIKA ]; + break; + case 184: + trainerTypes = [ TrainerType.BRUNO, TrainerType.KOGA, TrainerType.PHOEBE, TrainerType.BERTHA, TrainerType.MARSHAL, TrainerType.SIEBOLD, TrainerType.OLIVIA, TrainerType.NESSA_ELITE, TrainerType.POPPY ]; + break; + case 186: + trainerTypes = [ TrainerType.AGATHA, TrainerType.BRUNO, TrainerType.GLACIA, TrainerType.FLINT, TrainerType.GRIMSLEY, TrainerType.WIKSTROM, TrainerType.ACEROLA, Utils.randSeedItem([ TrainerType.BEA_ELITE, TrainerType.ALLISTER_ELITE ]), TrainerType.LARRY_ELITE ]; + break; + case 188: + trainerTypes = [ TrainerType.LANCE, TrainerType.KAREN, TrainerType.DRAKE, TrainerType.LUCIAN, TrainerType.CAITLIN, TrainerType.DRASNA, TrainerType.KAHILI, TrainerType.RAIHAN_ELITE, TrainerType.HASSEL ]; + break; + case 190: + trainerTypes = [ TrainerType.BLUE, Utils.randSeedItem([ TrainerType.RED, TrainerType.LANCE_CHAMPION ]), Utils.randSeedItem([ TrainerType.STEVEN, TrainerType.WALLACE ]), TrainerType.CYNTHIA, Utils.randSeedItem([ TrainerType.ALDER, TrainerType.IRIS ]), TrainerType.DIANTHA, TrainerType.HAU, TrainerType.LEON, Utils.randSeedItem([ TrainerType.GEETA, TrainerType.NEMONA ]) ]; + break; } if (trainerTypes.length === 0) { return false; @@ -891,45 +891,45 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType gameMode.challenges.forEach(c => { if (c.value !== 0) { switch (challengeType) { - case ChallengeType.STARTER_CHOICE: - ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.STARTER_POINTS: - ret ||= c.applyStarterPoints(args[0]); - break; - case ChallengeType.STARTER_COST: - ret ||= c.applyStarterCost(args[0], args[1]); - break; - case ChallengeType.STARTER_MODIFY: - ret ||= c.applyStarterModify(args[0]); - break; - case ChallengeType.POKEMON_IN_BATTLE: - ret ||= c.applyPokemonInBattle(args[0], args[1]); - break; - case ChallengeType.FIXED_BATTLES: - ret ||= c.applyFixedBattle(args[0], args[1]); - break; - case ChallengeType.TYPE_EFFECTIVENESS: - ret ||= c.applyTypeEffectiveness(args[0]); - break; - case ChallengeType.AI_LEVEL: - ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.AI_MOVE_SLOTS: - ret ||= c.applyMoveSlot(args[0], args[1]); - break; - case ChallengeType.PASSIVE_ACCESS: - ret ||= c.applyPassiveAccess(args[0], args[1]); - break; - case ChallengeType.GAME_MODE_MODIFY: - ret ||= c.applyGameModeModify(gameMode); - break; - case ChallengeType.MOVE_ACCESS: - ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); - break; - case ChallengeType.MOVE_WEIGHT: - ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); - break; + case ChallengeType.STARTER_CHOICE: + ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.STARTER_POINTS: + ret ||= c.applyStarterPoints(args[0]); + break; + case ChallengeType.STARTER_COST: + ret ||= c.applyStarterCost(args[0], args[1]); + break; + case ChallengeType.STARTER_MODIFY: + ret ||= c.applyStarterModify(args[0]); + break; + case ChallengeType.POKEMON_IN_BATTLE: + ret ||= c.applyPokemonInBattle(args[0], args[1]); + break; + case ChallengeType.FIXED_BATTLES: + ret ||= c.applyFixedBattle(args[0], args[1]); + break; + case ChallengeType.TYPE_EFFECTIVENESS: + ret ||= c.applyTypeEffectiveness(args[0]); + break; + case ChallengeType.AI_LEVEL: + ret ||= c.applyLevelChange(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.AI_MOVE_SLOTS: + ret ||= c.applyMoveSlot(args[0], args[1]); + break; + case ChallengeType.PASSIVE_ACCESS: + ret ||= c.applyPassiveAccess(args[0], args[1]); + break; + case ChallengeType.GAME_MODE_MODIFY: + ret ||= c.applyGameModeModify(gameMode); + break; + case ChallengeType.MOVE_ACCESS: + ret ||= c.applyMoveAccessLevel(args[0], args[1], args[2], args[3]); + break; + case ChallengeType.MOVE_WEIGHT: + ret ||= c.applyMoveWeight(args[0], args[1], args[2], args[3]); + break; } } }); @@ -943,18 +943,18 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType */ export function copyChallenge(source: Challenge | any): Challenge { switch (source.id) { - case Challenges.SINGLE_GENERATION: - return SingleGenerationChallenge.loadChallenge(source); - case Challenges.SINGLE_TYPE: - return SingleTypeChallenge.loadChallenge(source); - case Challenges.LOWER_MAX_STARTER_COST: - return LowerStarterMaxCostChallenge.loadChallenge(source); - case Challenges.LOWER_STARTER_POINTS: - return LowerStarterPointsChallenge.loadChallenge(source); - case Challenges.FRESH_START: - return FreshStartChallenge.loadChallenge(source); - case Challenges.INVERSE_BATTLE: - return InverseBattleChallenge.loadChallenge(source); + case Challenges.SINGLE_GENERATION: + return SingleGenerationChallenge.loadChallenge(source); + case Challenges.SINGLE_TYPE: + return SingleTypeChallenge.loadChallenge(source); + case Challenges.LOWER_MAX_STARTER_COST: + return LowerStarterMaxCostChallenge.loadChallenge(source); + case Challenges.LOWER_STARTER_POINTS: + return LowerStarterPointsChallenge.loadChallenge(source); + case Challenges.FRESH_START: + return FreshStartChallenge.loadChallenge(source); + case Challenges.INVERSE_BATTLE: + return InverseBattleChallenge.loadChallenge(source); } throw new Error("Unknown challenge copied"); } diff --git a/src/data/egg.ts b/src/data/egg.ts index c475fc729e6..0f88c53b748 100644 --- a/src/data/egg.ts +++ b/src/data/egg.ts @@ -262,14 +262,14 @@ export class Egg { return "Manaphy"; } switch (this.tier) { - case EggTier.RARE: - return i18next.t("egg:greatTier"); - case EggTier.EPIC: - return i18next.t("egg:ultraTier"); - case EggTier.LEGENDARY: - return i18next.t("egg:masterTier"); - default: - return i18next.t("egg:defaultTier"); + case EggTier.RARE: + return i18next.t("egg:greatTier"); + case EggTier.EPIC: + return i18next.t("egg:ultraTier"); + case EggTier.LEGENDARY: + return i18next.t("egg:masterTier"); + default: + return i18next.t("egg:defaultTier"); } } @@ -288,19 +288,19 @@ export class Egg { public getEggTypeDescriptor(scene: BattleScene): string { switch (this.sourceType) { - case EggSourceType.SAME_SPECIES_EGG: - return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); - case EggSourceType.GACHA_LEGENDARY: - return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; - case EggSourceType.GACHA_SHINY: - return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); - case EggSourceType.GACHA_MOVE: - return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove"); - case EggSourceType.EVENT: - return this._eggDescriptor ?? i18next.t("egg:eventType"); - default: - console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); - return ""; + case EggSourceType.SAME_SPECIES_EGG: + return this._eggDescriptor ?? i18next.t("egg:sameSpeciesEgg", { species: getPokemonSpecies(this._species).getName() }); + case EggSourceType.GACHA_LEGENDARY: + return this._eggDescriptor ?? `${i18next.t("egg:gachaTypeLegendary")} (${getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(scene, this.timestamp)).getName()})`; + case EggSourceType.GACHA_SHINY: + return this._eggDescriptor ?? i18next.t("egg:gachaTypeShiny"); + case EggSourceType.GACHA_MOVE: + return this._eggDescriptor ?? i18next.t("egg:gachaTypeMove"); + case EggSourceType.EVENT: + return this._eggDescriptor ?? i18next.t("egg:eventType"); + default: + console.warn("getEggTypeDescriptor case not defined. Returning default empty string"); + return ""; } } @@ -315,14 +315,14 @@ export class Egg { private rollEggMoveIndex() { let baseChance = GACHA_DEFAULT_RARE_EGGMOVE_RATE; switch (this._sourceType) { - case EggSourceType.SAME_SPECIES_EGG: - baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; - break; - case EggSourceType.GACHA_MOVE: - baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE; - break; - default: - break; + case EggSourceType.SAME_SPECIES_EGG: + baseChance = SAME_SPECIES_EGG_RARE_EGGMOVE_RATE; + break; + case EggSourceType.GACHA_MOVE: + baseChance = GACHA_MOVE_UP_RARE_EGGMOVE_RATE; + break; + default: + break; } const tierMultiplier = this.isManaphyEgg() ? 2 : Math.pow(2, 3 - this.tier); @@ -335,12 +335,12 @@ export class Egg { } switch (eggTier ?? this._tier) { - case EggTier.COMMON: - return HATCH_WAVES_COMMON_EGG; - case EggTier.RARE: - return HATCH_WAVES_RARE_EGG; - case EggTier.EPIC: - return HATCH_WAVES_EPIC_EGG; + case EggTier.COMMON: + return HATCH_WAVES_COMMON_EGG; + case EggTier.RARE: + return HATCH_WAVES_RARE_EGG; + case EggTier.EPIC: + return HATCH_WAVES_EPIC_EGG; } return HATCH_WAVES_LEGENDARY_EGG; } @@ -379,22 +379,22 @@ export class Egg { let maxStarterValue: integer; switch (this.tier) { - case EggTier.RARE: - minStarterValue = 4; - maxStarterValue = 5; - break; - case EggTier.EPIC: - minStarterValue = 6; - maxStarterValue = 7; - break; - case EggTier.LEGENDARY: - minStarterValue = 8; - maxStarterValue = 9; - break; - default: - minStarterValue = 1; - maxStarterValue = 3; - break; + case EggTier.RARE: + minStarterValue = 4; + maxStarterValue = 5; + break; + case EggTier.EPIC: + minStarterValue = 6; + maxStarterValue = 7; + break; + case EggTier.LEGENDARY: + minStarterValue = 8; + maxStarterValue = 9; + break; + default: + minStarterValue = 1; + maxStarterValue = 3; + break; } const ignoredSpecies = [ Species.PHIONE, Species.MANAPHY, Species.ETERNATUS ]; @@ -469,14 +469,14 @@ export class Egg { private rollShiny(): boolean { let shinyChance = GACHA_DEFAULT_SHINY_RATE; switch (this._sourceType) { - case EggSourceType.GACHA_SHINY: - shinyChance = GACHA_SHINY_UP_SHINY_RATE; - break; - case EggSourceType.SAME_SPECIES_EGG: - shinyChance = SAME_SPECIES_EGG_SHINY_RATE; - break; - default: - break; + case EggSourceType.GACHA_SHINY: + shinyChance = GACHA_SHINY_UP_SHINY_RATE; + break; + case EggSourceType.SAME_SPECIES_EGG: + shinyChance = SAME_SPECIES_EGG_SHINY_RATE; + break; + default: + break; } return !Utils.randSeedInt(shinyChance); @@ -523,15 +523,15 @@ export class Egg { return; } switch (this.tier) { - case EggTier.RARE: - scene.gameData.gameStats.rareEggsPulled++; - break; - case EggTier.EPIC: - scene.gameData.gameStats.epicEggsPulled++; - break; - case EggTier.LEGENDARY: - scene.gameData.gameStats.legendaryEggsPulled++; - break; + case EggTier.RARE: + scene.gameData.gameStats.rareEggsPulled++; + break; + case EggTier.EPIC: + scene.gameData.gameStats.epicEggsPulled++; + break; + case EggTier.LEGENDARY: + scene.gameData.gameStats.legendaryEggsPulled++; + break; } } diff --git a/src/data/exp.ts b/src/data/exp.ts index 3b332eb7cf2..c03abddadfc 100644 --- a/src/data/exp.ts +++ b/src/data/exp.ts @@ -28,24 +28,24 @@ export function getLevelTotalExp(level: integer, growthRate: GrowthRate): intege let ret: integer; switch (growthRate) { - case GrowthRate.ERRATIC: - ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500; - break; - case GrowthRate.FAST: - ret = Math.pow(level, 3) * 4 / 5; - break; - case GrowthRate.MEDIUM_FAST: - ret = Math.pow(level, 3); - break; - case GrowthRate.MEDIUM_SLOW: - ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140; - break; - case GrowthRate.SLOW: - ret = Math.pow(level, 3) * 5 / 4; - break; - case GrowthRate.FLUCTUATING: - ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level); - break; + case GrowthRate.ERRATIC: + ret = (Math.pow(level, 4) + (Math.pow(level, 3) * 2000)) / 3500; + break; + case GrowthRate.FAST: + ret = Math.pow(level, 3) * 4 / 5; + break; + case GrowthRate.MEDIUM_FAST: + ret = Math.pow(level, 3); + break; + case GrowthRate.MEDIUM_SLOW: + ret = (Math.pow(level, 3) * 6 / 5) - (15 * Math.pow(level, 2)) + (100 * level) - 140; + break; + case GrowthRate.SLOW: + ret = Math.pow(level, 3) * 5 / 4; + break; + case GrowthRate.FLUCTUATING: + ret = (Math.pow(level, 3) * ((level / 2) + 8)) * 4 / (100 + level); + break; } if (growthRate !== GrowthRate.MEDIUM_FAST) { @@ -61,17 +61,17 @@ export function getLevelRelExp(level: integer, growthRate: GrowthRate): number { export function getGrowthRateColor(growthRate: GrowthRate, shadow?: boolean) { switch (growthRate) { - case GrowthRate.ERRATIC: - return !shadow ? "#f85888" : "#906060"; - case GrowthRate.FAST: - return !shadow ? "#f8d030" : "#b8a038"; - case GrowthRate.MEDIUM_FAST: - return !shadow ? "#78c850" : "#588040"; - case GrowthRate.MEDIUM_SLOW: - return !shadow ? "#6890f0" : "#807870"; - case GrowthRate.SLOW: - return !shadow ? "#f08030" : "#c03028"; - case GrowthRate.FLUCTUATING: - return !shadow ? "#a040a0" : "#483850"; + case GrowthRate.ERRATIC: + return !shadow ? "#f85888" : "#906060"; + case GrowthRate.FAST: + return !shadow ? "#f8d030" : "#b8a038"; + case GrowthRate.MEDIUM_FAST: + return !shadow ? "#78c850" : "#588040"; + case GrowthRate.MEDIUM_SLOW: + return !shadow ? "#6890f0" : "#807870"; + case GrowthRate.SLOW: + return !shadow ? "#f08030" : "#c03028"; + case GrowthRate.FLUCTUATING: + return !shadow ? "#a040a0" : "#483850"; } } diff --git a/src/data/gender.ts b/src/data/gender.ts index 0d4b76d8bd1..dae7723dd85 100644 --- a/src/data/gender.ts +++ b/src/data/gender.ts @@ -6,20 +6,20 @@ export enum Gender { export function getGenderSymbol(gender: Gender) { switch (gender) { - case Gender.MALE: - return "♂"; - case Gender.FEMALE: - return "♀"; + case Gender.MALE: + return "♂"; + case Gender.FEMALE: + return "♀"; } return ""; } export function getGenderColor(gender: Gender, shadow?: boolean) { switch (gender) { - case Gender.MALE: - return shadow ? "#006090" : "#40c8f8"; - case Gender.FEMALE: - return shadow ? "#984038" : "#f89890"; + case Gender.MALE: + return shadow ? "#006090" : "#40c8f8"; + case Gender.FEMALE: + return shadow ? "#984038" : "#f89890"; } return "#ffffff"; } diff --git a/src/data/move.ts b/src/data/move.ts index 57307b49061..309a2d3a7eb 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -274,16 +274,16 @@ export default class Move implements Localizable { */ isMultiTarget(): boolean { switch (this.moveTarget) { - case MoveTarget.ALL_OTHERS: - case MoveTarget.ALL_NEAR_OTHERS: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_ENEMIES: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.ALL: - case MoveTarget.USER_SIDE: - case MoveTarget.ENEMY_SIDE: - case MoveTarget.BOTH_SIDES: - return true; + case MoveTarget.ALL_OTHERS: + case MoveTarget.ALL_NEAR_OTHERS: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_ENEMIES: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.ALL: + case MoveTarget.USER_SIDE: + case MoveTarget.ENEMY_SIDE: + case MoveTarget.BOTH_SIDES: + return true; } return false; } @@ -295,13 +295,13 @@ export default class Move implements Localizable { isAllyTarget(): boolean { switch (this.moveTarget) { - case MoveTarget.USER: - case MoveTarget.NEAR_ALLY: - case MoveTarget.ALLY: - case MoveTarget.USER_OR_NEAR_ALLY: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.USER_SIDE: - return true; + case MoveTarget.USER: + case MoveTarget.NEAR_ALLY: + case MoveTarget.ALLY: + case MoveTarget.USER_OR_NEAR_ALLY: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.USER_SIDE: + return true; } return false; } @@ -320,16 +320,16 @@ export default class Move implements Localizable { } switch (type) { - case Type.GRASS: - if (this.hasFlag(MoveFlags.POWDER_MOVE)) { - return true; - } - break; - case Type.DARK: - if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) { - return true; - } - break; + case Type.GRASS: + if (this.hasFlag(MoveFlags.POWDER_MOVE)) { + return true; + } + break; + case Type.DARK: + if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) { + return true; + } + break; } return false; } @@ -616,26 +616,26 @@ export default class Move implements Localizable { checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon | null): boolean { // special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact switch (flag) { - case MoveFlags.MAKES_CONTACT: - if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { - return false; - } - break; - case MoveFlags.IGNORE_ABILITIES: - if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { - const abilityEffectsIgnored = new Utils.BooleanHolder(false); - applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); - if (abilityEffectsIgnored.value) { + case MoveFlags.MAKES_CONTACT: + if (user.hasAbilityWithAttr(IgnoreContactAbAttr) || this.hitsSubstitute(user, target)) { + return false; + } + break; + case MoveFlags.IGNORE_ABILITIES: + if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) { + const abilityEffectsIgnored = new Utils.BooleanHolder(false); + applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, false, this); + if (abilityEffectsIgnored.value) { + return true; + } + } + break; + case MoveFlags.IGNORE_PROTECT: + if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) + && this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) { return true; } - } - break; - case MoveFlags.IGNORE_PROTECT: - if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) - && this.checkFlag(MoveFlags.MAKES_CONTACT, user, null)) { - return true; - } - break; + break; } return !!(this.flags & flag); @@ -1745,17 +1745,17 @@ export abstract class WeatherHealAttr extends HealAttr { export class PlantHealAttr extends WeatherHealAttr { getWeatherHealRatio(weatherType: WeatherType): number { switch (weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - return 2 / 3; - case WeatherType.RAIN: - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - case WeatherType.SNOW: - case WeatherType.HEAVY_RAIN: - return 0.25; - default: - return 0.5; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + return 2 / 3; + case WeatherType.RAIN: + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + case WeatherType.SNOW: + case WeatherType.HEAVY_RAIN: + return 0.25; + default: + return 0.5; } } } @@ -1763,10 +1763,10 @@ export class PlantHealAttr extends WeatherHealAttr { export class SandHealAttr extends WeatherHealAttr { getWeatherHealRatio(weatherType: WeatherType): number { switch (weatherType) { - case WeatherType.SANDSTORM: - return 2 / 3; - default: - return 0.5; + case WeatherType.SANDSTORM: + return 2 / 3; + default: + return 0.5; } } } @@ -1996,33 +1996,33 @@ export class MultiHitAttr extends MoveAttr { */ getHitCount(user: Pokemon, target: Pokemon): integer { switch (this.multiHitType) { - case MultiHitType._2_TO_5: - { - const rand = user.randSeedInt(16); - const hitValue = new Utils.IntegerHolder(rand); - applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); - if (hitValue.value >= 10) { - return 2; - } else if (hitValue.value >= 4) { - return 3; - } else if (hitValue.value >= 2) { - return 4; - } else { - return 5; + case MultiHitType._2_TO_5: + { + const rand = user.randSeedInt(16); + const hitValue = new Utils.IntegerHolder(rand); + applyAbAttrs(MaxMultiHitAbAttr, user, null, false, hitValue); + if (hitValue.value >= 10) { + return 2; + } else if (hitValue.value >= 4) { + return 3; + } else if (hitValue.value >= 2) { + return 4; + } else { + return 5; + } } - } - case MultiHitType._2: - return 2; - case MultiHitType._3: - return 3; - case MultiHitType._10: - return 10; - case MultiHitType.BEAT_UP: - const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); - // No status means the ally pokemon can contribute to Beat Up - return party.reduce((total, pokemon) => { - return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); - }, 0); + case MultiHitType._2: + return 2; + case MultiHitType._3: + return 3; + case MultiHitType._10: + return 10; + case MultiHitType.BEAT_UP: + const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + // No status means the ally pokemon can contribute to Beat Up + return party.reduce((total, pokemon) => { + return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + }, 0); } } } @@ -2845,26 +2845,26 @@ export class StatStageChangeAttr extends MoveEffectAttr { } let noEffect = false; switch (stat) { - case Stat.ATK: - if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); - } - break; - case Stat.DEF: - if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); - } - break; - case Stat.SPATK: - if (this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); - } - break; - case Stat.SPDEF: - if (!this.selfTarget) { - noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); - } - break; + case Stat.ATK: + if (this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + } + break; + case Stat.DEF: + if (!this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); + } + break; + case Stat.SPATK: + if (this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + } + break; + case Stat.SPDEF: + if (!this.selfTarget) { + noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); + } + break; } if (noEffect) { continue; @@ -2933,19 +2933,19 @@ export class SecretPowerAttr extends 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; + 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; } @@ -2970,62 +2970,62 @@ export class SecretPowerAttr extends 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; + 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; } @@ -3309,21 +3309,21 @@ export class LessPPMorePowerAttr extends VariablePowerAttr { const power = args[0] as Utils.NumberHolder; switch (ppRemains) { - case 0: - power.value = 200; - break; - case 1: - power.value = 80; - break; - case 2: - power.value = 60; - break; - case 3: - power.value = 50; - break; - default: - power.value = 40; - break; + case 0: + power.value = 200; + break; + case 1: + power.value = 80; + break; + case 2: + power.value = 60; + break; + case 3: + power.value = 50; + break; + default: + power.value = 40; + break; } return true; } @@ -3543,24 +3543,24 @@ export class LowHpPowerAttr extends VariablePowerAttr { const hpRatio = user.getHpRatio(); switch (true) { - case (hpRatio < 0.0417): - power.value = 200; - break; - case (hpRatio < 0.1042): - power.value = 150; - break; - case (hpRatio < 0.2083): - power.value = 100; - break; - case (hpRatio < 0.3542): - power.value = 80; - break; - case (hpRatio < 0.6875): - power.value = 40; - break; - default: - power.value = 20; - break; + case (hpRatio < 0.0417): + power.value = 200; + break; + case (hpRatio < 0.1042): + power.value = 150; + break; + case (hpRatio < 0.2083): + power.value = 100; + break; + case (hpRatio < 0.3542): + power.value = 80; + break; + case (hpRatio < 0.6875): + power.value = 40; + break; + default: + power.value = 20; + break; } return true; @@ -3580,21 +3580,21 @@ export class CompareWeightPowerAttr extends VariablePowerAttr { const relativeWeight = (targetWeight / userWeight) * 100; switch (true) { - case (relativeWeight < 20.01): - power.value = 120; - break; - case (relativeWeight < 25.01): - power.value = 100; - break; - case (relativeWeight < 33.35): - power.value = 80; - break; - case (relativeWeight < 50.01): - power.value = 60; - break; - default: - power.value = 40; - break; + case (relativeWeight < 20.01): + power.value = 120; + break; + case (relativeWeight < 25.01): + power.value = 100; + break; + case (relativeWeight < 33.35): + power.value = 80; + break; + case (relativeWeight < 50.01): + power.value = 60; + break; + default: + power.value = 40; + break; } return true; @@ -3710,13 +3710,13 @@ export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr { const power = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.RAIN: - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - case WeatherType.SNOW: - case WeatherType.HEAVY_RAIN: - power.value *= 0.5; - return true; + case WeatherType.RAIN: + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + case WeatherType.SNOW: + case WeatherType.HEAVY_RAIN: + power.value *= 0.5; + return true; } } @@ -4113,14 +4113,14 @@ export class ThunderAccuracyAttr extends VariableAccuracyAttr { const accuracy = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - accuracy.value = 50; - return true; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - accuracy.value = -1; - return true; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + accuracy.value = 50; + return true; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + accuracy.value = -1; + return true; } } @@ -4139,10 +4139,10 @@ export class StormAccuracyAttr extends VariableAccuracyAttr { const accuracy = args[0] as Utils.NumberHolder; const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE; switch (weatherType) { - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - accuracy.value = -1; - return true; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + accuracy.value = -1; + return true; } } @@ -4372,21 +4372,21 @@ export class TechnoBlastTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Shock Drive - moveType.value = Type.ELECTRIC; - break; - case 2: // Burn Drive - moveType.value = Type.FIRE; - break; - case 3: // Chill Drive - moveType.value = Type.ICE; - break; - case 4: // Douse Drive - moveType.value = Type.WATER; - break; - default: - moveType.value = Type.NORMAL; - break; + case 1: // Shock Drive + moveType.value = Type.ELECTRIC; + break; + case 2: // Burn Drive + moveType.value = Type.FIRE; + break; + case 3: // Chill Drive + moveType.value = Type.ICE; + break; + case 4: // Douse Drive + moveType.value = Type.WATER; + break; + default: + moveType.value = Type.NORMAL; + break; } return true; } @@ -4406,12 +4406,12 @@ export class AuraWheelTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Hangry Mode - moveType.value = Type.DARK; - break; - default: // Full Belly Mode - moveType.value = Type.ELECTRIC; - break; + case 1: // Hangry Mode + moveType.value = Type.DARK; + break; + default: // Full Belly Mode + moveType.value = Type.ELECTRIC; + break; } return true; } @@ -4431,15 +4431,15 @@ export class RagingBullTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Blaze breed - moveType.value = Type.FIRE; - break; - case 2: // Aqua breed - moveType.value = Type.WATER; - break; - default: - moveType.value = Type.FIGHTING; - break; + case 1: // Blaze breed + moveType.value = Type.FIRE; + break; + case 2: // Aqua breed + moveType.value = Type.WATER; + break; + default: + moveType.value = Type.FIGHTING; + break; } return true; } @@ -4459,22 +4459,22 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr { const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies?.formIndex; switch (form) { - case 1: // Wellspring Mask - case 5: // Wellspring Mask Tera - moveType.value = Type.WATER; - break; - case 2: // Hearthflame Mask - case 6: // Hearthflame Mask Tera - moveType.value = Type.FIRE; - break; - case 3: // Cornerstone Mask - case 7: // Cornerstone Mask Tera - moveType.value = Type.ROCK; - break; - case 4: // Teal Mask Tera - default: - moveType.value = Type.GRASS; - break; + case 1: // Wellspring Mask + case 5: // Wellspring Mask Tera + moveType.value = Type.WATER; + break; + case 2: // Hearthflame Mask + case 6: // Hearthflame Mask Tera + moveType.value = Type.FIRE; + break; + case 3: // Cornerstone Mask + case 7: // Cornerstone Mask Tera + moveType.value = Type.ROCK; + break; + case 4: // Teal Mask Tera + default: + moveType.value = Type.GRASS; + break; } return true; } @@ -4492,23 +4492,23 @@ export class WeatherBallTypeAttr extends VariableMoveTypeAttr { if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) { switch (user.scene.arena.weather?.weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - moveType.value = Type.FIRE; - break; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - moveType.value = Type.WATER; - break; - case WeatherType.SANDSTORM: - moveType.value = Type.ROCK; - break; - case WeatherType.HAIL: - case WeatherType.SNOW: - moveType.value = Type.ICE; - break; - default: - return false; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + moveType.value = Type.FIRE; + break; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + moveType.value = Type.WATER; + break; + case WeatherType.SANDSTORM: + moveType.value = Type.ROCK; + break; + case WeatherType.HAIL: + case WeatherType.SNOW: + moveType.value = Type.ICE; + break; + default: + return false; } return true; } @@ -4543,20 +4543,20 @@ export class TerrainPulseTypeAttr extends VariableMoveTypeAttr { const currentTerrain = user.scene.arena.getTerrainType(); switch (currentTerrain) { - case TerrainType.MISTY: - moveType.value = Type.FAIRY; - break; - case TerrainType.ELECTRIC: - moveType.value = Type.ELECTRIC; - break; - case TerrainType.GRASSY: - moveType.value = Type.GRASS; - break; - case TerrainType.PSYCHIC: - moveType.value = Type.PSYCHIC; - break; - default: - return false; + case TerrainType.MISTY: + moveType.value = Type.FAIRY; + break; + case TerrainType.ELECTRIC: + moveType.value = Type.ELECTRIC; + break; + case TerrainType.GRASSY: + moveType.value = Type.GRASS; + break; + case TerrainType.PSYCHIC: + moveType.value = Type.PSYCHIC; + break; + default: + return false; } return true; } @@ -4656,26 +4656,26 @@ export class CombinedPledgeTypeAttr extends VariableMoveTypeAttr { } switch (move.id) { - case Moves.FIRE_PLEDGE: - if (combinedPledgeMove === Moves.WATER_PLEDGE) { - moveType.value = Type.WATER; - return true; - } - return false; - case Moves.WATER_PLEDGE: - if (combinedPledgeMove === Moves.GRASS_PLEDGE) { - moveType.value = Type.GRASS; - return true; - } - return false; - case Moves.GRASS_PLEDGE: - if (combinedPledgeMove === Moves.FIRE_PLEDGE) { - moveType.value = Type.FIRE; - return true; - } - return false; - default: - return false; + case Moves.FIRE_PLEDGE: + if (combinedPledgeMove === Moves.WATER_PLEDGE) { + moveType.value = Type.WATER; + return true; + } + return false; + case Moves.WATER_PLEDGE: + if (combinedPledgeMove === Moves.GRASS_PLEDGE) { + moveType.value = Type.GRASS; + return true; + } + return false; + case Moves.GRASS_PLEDGE: + if (combinedPledgeMove === Moves.FIRE_PLEDGE) { + moveType.value = Type.FIRE; + return true; + } + return false; + default: + return false; } } } @@ -4920,48 +4920,48 @@ export class AddBattlerTagAttr extends MoveEffectAttr { getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer | void { switch (this.tagType) { - case BattlerTagType.RECHARGING: - case BattlerTagType.PERISH_SONG: - return -16; - case BattlerTagType.FLINCHED: - case BattlerTagType.CONFUSED: - case BattlerTagType.INFATUATED: - case BattlerTagType.NIGHTMARE: - case BattlerTagType.DROWSY: - case BattlerTagType.DISABLED: - case BattlerTagType.HEAL_BLOCK: - case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: - return -5; - case BattlerTagType.SEEDED: - case BattlerTagType.SALT_CURED: - case BattlerTagType.CURSED: - case BattlerTagType.FRENZY: - case BattlerTagType.TRAPPED: - case BattlerTagType.BIND: - case BattlerTagType.WRAP: - case BattlerTagType.FIRE_SPIN: - case BattlerTagType.WHIRLPOOL: - case BattlerTagType.CLAMP: - case BattlerTagType.SAND_TOMB: - case BattlerTagType.MAGMA_STORM: - case BattlerTagType.SNAP_TRAP: - case BattlerTagType.THUNDER_CAGE: - case BattlerTagType.INFESTATION: - return -3; - case BattlerTagType.ENCORE: - return -2; - case BattlerTagType.MINIMIZED: - case BattlerTagType.ALWAYS_GET_HIT: - return 0; - case BattlerTagType.INGRAIN: - case BattlerTagType.IGNORE_ACCURACY: - case BattlerTagType.AQUA_RING: - return 3; - case BattlerTagType.PROTECTED: - case BattlerTagType.FLYING: - case BattlerTagType.CRIT_BOOST: - case BattlerTagType.ALWAYS_CRIT: - return 5; + case BattlerTagType.RECHARGING: + case BattlerTagType.PERISH_SONG: + return -16; + case BattlerTagType.FLINCHED: + case BattlerTagType.CONFUSED: + case BattlerTagType.INFATUATED: + case BattlerTagType.NIGHTMARE: + case BattlerTagType.DROWSY: + case BattlerTagType.DISABLED: + case BattlerTagType.HEAL_BLOCK: + case BattlerTagType.RECEIVE_DOUBLE_DAMAGE: + return -5; + case BattlerTagType.SEEDED: + case BattlerTagType.SALT_CURED: + case BattlerTagType.CURSED: + case BattlerTagType.FRENZY: + case BattlerTagType.TRAPPED: + case BattlerTagType.BIND: + case BattlerTagType.WRAP: + case BattlerTagType.FIRE_SPIN: + case BattlerTagType.WHIRLPOOL: + case BattlerTagType.CLAMP: + case BattlerTagType.SAND_TOMB: + case BattlerTagType.MAGMA_STORM: + case BattlerTagType.SNAP_TRAP: + case BattlerTagType.THUNDER_CAGE: + case BattlerTagType.INFESTATION: + return -3; + case BattlerTagType.ENCORE: + return -2; + case BattlerTagType.MINIMIZED: + case BattlerTagType.ALWAYS_GET_HIT: + return 0; + case BattlerTagType.INGRAIN: + case BattlerTagType.IGNORE_ACCURACY: + case BattlerTagType.AQUA_RING: + return 3; + case BattlerTagType.PROTECTED: + case BattlerTagType.FLYING: + case BattlerTagType.CRIT_BOOST: + case BattlerTagType.ALWAYS_CRIT: + return 5; } } @@ -5934,19 +5934,19 @@ export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr { } let selectTargets: BattlerIndex[]; switch (true) { - case (moveTargets.multiple || moveTargets.targets.length === 1): { - selectTargets = moveTargets.targets; - break; - } - case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { - selectTargets = [ target.getBattlerIndex() ]; - break; - } - default: { - moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); - selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; - break; - } + case (moveTargets.multiple || moveTargets.targets.length === 1): { + selectTargets = moveTargets.targets; + break; + } + case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): { + selectTargets = [ target.getBattlerIndex() ]; + break; + } + default: { + moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex())); + selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; + break; + } } const targets = selectTargets; user.getMoveQueue().push({ move: move?.moveId!, targets: targets, ignorePP: true }); // TODO: is this bang correct? @@ -5990,131 +5990,131 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { let moveId; switch (user.scene.arena.getTerrainType()) { // this allows terrains to 'override' the biome move - case TerrainType.NONE: - switch (user.scene.arena.biomeType) { - case Biome.TOWN: - moveId = Moves.ROUND; + case TerrainType.NONE: + switch (user.scene.arena.biomeType) { + case Biome.TOWN: + moveId = Moves.ROUND; + break; + case Biome.METROPOLIS: + moveId = Moves.TRI_ATTACK; + break; + case Biome.SLUM: + moveId = Moves.SLUDGE_BOMB; + break; + case Biome.PLAINS: + moveId = Moves.SILVER_WIND; + break; + case Biome.GRASS: + moveId = Moves.GRASS_KNOT; + break; + case Biome.TALL_GRASS: + moveId = Moves.POLLEN_PUFF; + break; + case Biome.MEADOW: + moveId = Moves.GIGA_DRAIN; + break; + case Biome.FOREST: + moveId = Moves.BUG_BUZZ; + break; + case Biome.JUNGLE: + moveId = Moves.LEAF_STORM; + break; + case Biome.SEA: + moveId = Moves.HYDRO_PUMP; + break; + case Biome.SWAMP: + moveId = Moves.MUD_BOMB; + break; + case Biome.BEACH: + moveId = Moves.SCALD; + break; + case Biome.LAKE: + moveId = Moves.BUBBLE_BEAM; + break; + case Biome.SEABED: + moveId = Moves.BRINE; + break; + case Biome.ISLAND: + moveId = Moves.LEAF_TORNADO; + break; + case Biome.MOUNTAIN: + moveId = Moves.AIR_SLASH; + break; + case Biome.BADLANDS: + moveId = Moves.EARTH_POWER; + break; + case Biome.DESERT: + moveId = Moves.SCORCHING_SANDS; + break; + case Biome.WASTELAND: + moveId = Moves.DRAGON_PULSE; + break; + case Biome.CONSTRUCTION_SITE: + moveId = Moves.STEEL_BEAM; + break; + case Biome.CAVE: + moveId = Moves.POWER_GEM; + break; + case Biome.ICE_CAVE: + moveId = Moves.ICE_BEAM; + break; + case Biome.SNOWY_FOREST: + moveId = Moves.FROST_BREATH; + break; + case Biome.VOLCANO: + moveId = Moves.LAVA_PLUME; + break; + case Biome.GRAVEYARD: + moveId = Moves.SHADOW_BALL; + break; + case Biome.RUINS: + moveId = Moves.ANCIENT_POWER; + break; + case Biome.TEMPLE: + moveId = Moves.EXTRASENSORY; + break; + case Biome.DOJO: + moveId = Moves.FOCUS_BLAST; + break; + case Biome.FAIRY_CAVE: + moveId = Moves.ALLURING_VOICE; + break; + case Biome.ABYSS: + moveId = Moves.OMINOUS_WIND; + break; + case Biome.SPACE: + moveId = Moves.DRACO_METEOR; + break; + case Biome.FACTORY: + moveId = Moves.FLASH_CANNON; + break; + case Biome.LABORATORY: + moveId = Moves.ZAP_CANNON; + break; + case Biome.POWER_PLANT: + moveId = Moves.CHARGE_BEAM; + break; + case Biome.END: + moveId = Moves.ETERNABEAM; + break; + } break; - case Biome.METROPOLIS: + case TerrainType.MISTY: + moveId = Moves.MOONBLAST; + break; + case TerrainType.ELECTRIC: + moveId = Moves.THUNDERBOLT; + break; + case TerrainType.GRASSY: + moveId = Moves.ENERGY_BALL; + break; + case TerrainType.PSYCHIC: + moveId = Moves.PSYCHIC; + break; + default: + // Just in case there's no match moveId = Moves.TRI_ATTACK; break; - case Biome.SLUM: - moveId = Moves.SLUDGE_BOMB; - break; - case Biome.PLAINS: - moveId = Moves.SILVER_WIND; - break; - case Biome.GRASS: - moveId = Moves.GRASS_KNOT; - break; - case Biome.TALL_GRASS: - moveId = Moves.POLLEN_PUFF; - break; - case Biome.MEADOW: - moveId = Moves.GIGA_DRAIN; - break; - case Biome.FOREST: - moveId = Moves.BUG_BUZZ; - break; - case Biome.JUNGLE: - moveId = Moves.LEAF_STORM; - break; - case Biome.SEA: - moveId = Moves.HYDRO_PUMP; - break; - case Biome.SWAMP: - moveId = Moves.MUD_BOMB; - break; - case Biome.BEACH: - moveId = Moves.SCALD; - break; - case Biome.LAKE: - moveId = Moves.BUBBLE_BEAM; - break; - case Biome.SEABED: - moveId = Moves.BRINE; - break; - case Biome.ISLAND: - moveId = Moves.LEAF_TORNADO; - break; - case Biome.MOUNTAIN: - moveId = Moves.AIR_SLASH; - break; - case Biome.BADLANDS: - moveId = Moves.EARTH_POWER; - break; - case Biome.DESERT: - moveId = Moves.SCORCHING_SANDS; - break; - case Biome.WASTELAND: - moveId = Moves.DRAGON_PULSE; - break; - case Biome.CONSTRUCTION_SITE: - moveId = Moves.STEEL_BEAM; - break; - case Biome.CAVE: - moveId = Moves.POWER_GEM; - break; - case Biome.ICE_CAVE: - moveId = Moves.ICE_BEAM; - break; - case Biome.SNOWY_FOREST: - moveId = Moves.FROST_BREATH; - break; - case Biome.VOLCANO: - moveId = Moves.LAVA_PLUME; - break; - case Biome.GRAVEYARD: - moveId = Moves.SHADOW_BALL; - break; - case Biome.RUINS: - moveId = Moves.ANCIENT_POWER; - break; - case Biome.TEMPLE: - moveId = Moves.EXTRASENSORY; - break; - case Biome.DOJO: - moveId = Moves.FOCUS_BLAST; - break; - case Biome.FAIRY_CAVE: - moveId = Moves.ALLURING_VOICE; - break; - case Biome.ABYSS: - moveId = Moves.OMINOUS_WIND; - break; - case Biome.SPACE: - moveId = Moves.DRACO_METEOR; - break; - case Biome.FACTORY: - moveId = Moves.FLASH_CANNON; - break; - case Biome.LABORATORY: - moveId = Moves.ZAP_CANNON; - break; - case Biome.POWER_PLANT: - moveId = Moves.CHARGE_BEAM; - break; - case Biome.END: - moveId = Moves.ETERNABEAM; - break; - } - break; - case TerrainType.MISTY: - moveId = Moves.MOONBLAST; - break; - case TerrainType.ELECTRIC: - moveId = Moves.THUNDERBOLT; - break; - case TerrainType.GRASSY: - moveId = Moves.ENERGY_BALL; - break; - case TerrainType.PSYCHIC: - moveId = Moves.PSYCHIC; - break; - default: - // Just in case there's no match - moveId = Moves.TRI_ATTACK; - break; } user.getMoveQueue().push({ move: moveId, targets: [ target.getBattlerIndex() ], ignorePP: true }); @@ -7152,47 +7152,47 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet { let multiple = false; switch (moveTarget) { - case MoveTarget.USER: - case MoveTarget.PARTY: - set = [ user ]; - break; - case MoveTarget.NEAR_OTHER: - case MoveTarget.OTHER: - case MoveTarget.ALL_NEAR_OTHERS: - case MoveTarget.ALL_OTHERS: - set = (opponents.concat([ user.getAlly() ])); - multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; - break; - case MoveTarget.NEAR_ENEMY: - case MoveTarget.ALL_NEAR_ENEMIES: - case MoveTarget.ALL_ENEMIES: - case MoveTarget.ENEMY_SIDE: - set = opponents; - multiple = moveTarget !== MoveTarget.NEAR_ENEMY; - break; - case MoveTarget.RANDOM_NEAR_ENEMY: - set = [ opponents[user.randSeedInt(opponents.length)] ]; - break; - case MoveTarget.ATTACKER: - return { targets: [ -1 as BattlerIndex ], multiple: false }; - case MoveTarget.NEAR_ALLY: - case MoveTarget.ALLY: - set = [ user.getAlly() ]; - break; - case MoveTarget.USER_OR_NEAR_ALLY: - case MoveTarget.USER_AND_ALLIES: - case MoveTarget.USER_SIDE: - set = [ user, user.getAlly() ]; - multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; - break; - case MoveTarget.ALL: - case MoveTarget.BOTH_SIDES: - set = [ user, user.getAlly() ].concat(opponents); - multiple = true; - break; - case MoveTarget.CURSE: - set = user.getTypes(true).includes(Type.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ]; - break; + case MoveTarget.USER: + case MoveTarget.PARTY: + set = [ user ]; + break; + case MoveTarget.NEAR_OTHER: + case MoveTarget.OTHER: + case MoveTarget.ALL_NEAR_OTHERS: + case MoveTarget.ALL_OTHERS: + set = (opponents.concat([ user.getAlly() ])); + multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS; + break; + case MoveTarget.NEAR_ENEMY: + case MoveTarget.ALL_NEAR_ENEMIES: + case MoveTarget.ALL_ENEMIES: + case MoveTarget.ENEMY_SIDE: + set = opponents; + multiple = moveTarget !== MoveTarget.NEAR_ENEMY; + break; + case MoveTarget.RANDOM_NEAR_ENEMY: + set = [ opponents[user.randSeedInt(opponents.length)] ]; + break; + case MoveTarget.ATTACKER: + return { targets: [ -1 as BattlerIndex ], multiple: false }; + case MoveTarget.NEAR_ALLY: + case MoveTarget.ALLY: + set = [ user.getAlly() ]; + break; + case MoveTarget.USER_OR_NEAR_ALLY: + case MoveTarget.USER_AND_ALLIES: + case MoveTarget.USER_SIDE: + set = [ user, user.getAlly() ]; + multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY; + break; + case MoveTarget.ALL: + case MoveTarget.BOTH_SIDES: + set = [ user, user.getAlly() ].concat(opponents); + multiple = true; + break; + case MoveTarget.CURSE: + set = user.getTypes(true).includes(Type.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ]; + break; } return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple }; 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 56d80c9598c..f0155b4f2a4 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -44,32 +44,32 @@ export const ATrainersTestEncounter: MysteryEncounter = let spriteKeys; let trainerNameKey: string; switch (randSeedInt(5)) { - default: - case 0: - trainerType = TrainerType.BUCK; - spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL); - trainerNameKey = "buck"; - break; - case 1: - trainerType = TrainerType.CHERYL; - spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY); - trainerNameKey = "cheryl"; - break; - case 2: - trainerType = TrainerType.MARLEY; - spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE); - trainerNameKey = "marley"; - break; - case 3: - trainerType = TrainerType.MIRA; - spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1); - trainerNameKey = "mira"; - break; - case 4: - trainerType = TrainerType.RILEY; - spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1); - trainerNameKey = "riley"; - break; + default: + case 0: + trainerType = TrainerType.BUCK; + spriteKeys = getSpriteKeysFromSpecies(Species.CLAYDOL); + trainerNameKey = "buck"; + break; + case 1: + trainerType = TrainerType.CHERYL; + spriteKeys = getSpriteKeysFromSpecies(Species.BLISSEY); + trainerNameKey = "cheryl"; + break; + case 2: + trainerType = TrainerType.MARLEY; + spriteKeys = getSpriteKeysFromSpecies(Species.ARCANINE); + trainerNameKey = "marley"; + break; + case 3: + trainerType = TrainerType.MIRA; + spriteKeys = getSpriteKeysFromSpecies(Species.ALAKAZAM, false, 1); + trainerNameKey = "mira"; + break; + case 4: + trainerType = TrainerType.RILEY; + spriteKeys = getSpriteKeysFromSpecies(Species.LUCARIO, false, 1); + trainerNameKey = "riley"; + break; } // Dialogue and tokens for trainer diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index f76dff1fec7..485b955f998 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -1069,19 +1069,19 @@ export function calculateRareSpawnAggregateStats(scene: BattleScene, luckValue: const tier = tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; switch (tier) { - default: - case BiomePoolTier.BOSS: - ++bossEncountersByRarity[0]; - break; - case BiomePoolTier.BOSS_RARE: - ++bossEncountersByRarity[1]; - break; - case BiomePoolTier.BOSS_SUPER_RARE: - ++bossEncountersByRarity[2]; - break; - case BiomePoolTier.BOSS_ULTRA_RARE: - ++bossEncountersByRarity[3]; - break; + default: + case BiomePoolTier.BOSS: + ++bossEncountersByRarity[0]; + break; + case BiomePoolTier.BOSS_RARE: + ++bossEncountersByRarity[1]; + break; + case BiomePoolTier.BOSS_SUPER_RARE: + ++bossEncountersByRarity[2]; + break; + case BiomePoolTier.BOSS_ULTRA_RARE: + ++bossEncountersByRarity[3]; + break; } } diff --git a/src/data/nature.ts b/src/data/nature.ts index c614be465c3..edac06f1a4f 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -37,76 +37,76 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals export function getNatureStatMultiplier(nature: Nature, stat: Stat): number { switch (stat) { - case Stat.ATK: - switch (nature) { - case Nature.LONELY: - case Nature.BRAVE: - case Nature.ADAMANT: - case Nature.NAUGHTY: - return 1.1; - case Nature.BOLD: - case Nature.TIMID: - case Nature.MODEST: - case Nature.CALM: - return 0.9; - } - break; - case Stat.DEF: - switch (nature) { - case Nature.BOLD: - case Nature.RELAXED: - case Nature.IMPISH: - case Nature.LAX: - return 1.1; - case Nature.LONELY: - case Nature.HASTY: - case Nature.MILD: - case Nature.GENTLE: - return 0.9; - } - break; - case Stat.SPATK: - switch (nature) { - case Nature.MODEST: - case Nature.MILD: - case Nature.QUIET: - case Nature.RASH: - return 1.1; - case Nature.ADAMANT: - case Nature.IMPISH: - case Nature.JOLLY: - case Nature.CAREFUL: - return 0.9; - } - break; - case Stat.SPDEF: - switch (nature) { - case Nature.CALM: - case Nature.GENTLE: - case Nature.SASSY: - case Nature.CAREFUL: - return 1.1; - case Nature.NAUGHTY: - case Nature.LAX: - case Nature.NAIVE: - case Nature.RASH: - return 0.9; - } - break; - case Stat.SPD: - switch (nature) { - case Nature.TIMID: - case Nature.HASTY: - case Nature.JOLLY: - case Nature.NAIVE: - return 1.1; - case Nature.BRAVE: - case Nature.RELAXED: - case Nature.QUIET: - case Nature.SASSY: - return 0.9; - } - break; + case Stat.ATK: + switch (nature) { + case Nature.LONELY: + case Nature.BRAVE: + case Nature.ADAMANT: + case Nature.NAUGHTY: + return 1.1; + case Nature.BOLD: + case Nature.TIMID: + case Nature.MODEST: + case Nature.CALM: + return 0.9; + } + break; + case Stat.DEF: + switch (nature) { + case Nature.BOLD: + case Nature.RELAXED: + case Nature.IMPISH: + case Nature.LAX: + return 1.1; + case Nature.LONELY: + case Nature.HASTY: + case Nature.MILD: + case Nature.GENTLE: + return 0.9; + } + break; + case Stat.SPATK: + switch (nature) { + case Nature.MODEST: + case Nature.MILD: + case Nature.QUIET: + case Nature.RASH: + return 1.1; + case Nature.ADAMANT: + case Nature.IMPISH: + case Nature.JOLLY: + case Nature.CAREFUL: + return 0.9; + } + break; + case Stat.SPDEF: + switch (nature) { + case Nature.CALM: + case Nature.GENTLE: + case Nature.SASSY: + case Nature.CAREFUL: + return 1.1; + case Nature.NAUGHTY: + case Nature.LAX: + case Nature.NAIVE: + case Nature.RASH: + return 0.9; + } + break; + case Stat.SPD: + switch (nature) { + case Nature.TIMID: + case Nature.HASTY: + case Nature.JOLLY: + case Nature.NAIVE: + return 1.1; + case Nature.BRAVE: + case Nature.RELAXED: + case Nature.QUIET: + case Nature.SASSY: + return 0.9; + } + break; } return 1; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 59ff4ed86ce..57a78e2cd61 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -8,77 +8,77 @@ export const MAX_PER_TYPE_POKEBALLS: integer = 99; export function getPokeballAtlasKey(type: PokeballType): string { switch (type) { - case PokeballType.POKEBALL: - return "pb"; - case PokeballType.GREAT_BALL: - return "gb"; - case PokeballType.ULTRA_BALL: - return "ub"; - case PokeballType.ROGUE_BALL: - return "rb"; - case PokeballType.MASTER_BALL: - return "mb"; - case PokeballType.LUXURY_BALL: - return "lb"; + case PokeballType.POKEBALL: + return "pb"; + case PokeballType.GREAT_BALL: + return "gb"; + case PokeballType.ULTRA_BALL: + return "ub"; + case PokeballType.ROGUE_BALL: + return "rb"; + case PokeballType.MASTER_BALL: + return "mb"; + case PokeballType.LUXURY_BALL: + return "lb"; } } export function getPokeballName(type: PokeballType): string { let ret: string; switch (type) { - case PokeballType.POKEBALL: - ret = i18next.t("pokeball:pokeBall"); - break; - case PokeballType.GREAT_BALL: - ret = i18next.t("pokeball:greatBall"); - break; - case PokeballType.ULTRA_BALL: - ret = i18next.t("pokeball:ultraBall"); - break; - case PokeballType.ROGUE_BALL: - ret = i18next.t("pokeball:rogueBall"); - break; - case PokeballType.MASTER_BALL: - ret = i18next.t("pokeball:masterBall"); - break; - case PokeballType.LUXURY_BALL: - ret = i18next.t("pokeball:luxuryBall"); - break; + case PokeballType.POKEBALL: + ret = i18next.t("pokeball:pokeBall"); + break; + case PokeballType.GREAT_BALL: + ret = i18next.t("pokeball:greatBall"); + break; + case PokeballType.ULTRA_BALL: + ret = i18next.t("pokeball:ultraBall"); + break; + case PokeballType.ROGUE_BALL: + ret = i18next.t("pokeball:rogueBall"); + break; + case PokeballType.MASTER_BALL: + ret = i18next.t("pokeball:masterBall"); + break; + case PokeballType.LUXURY_BALL: + ret = i18next.t("pokeball:luxuryBall"); + break; } return ret; } export function getPokeballCatchMultiplier(type: PokeballType): number { switch (type) { - case PokeballType.POKEBALL: - return 1; - case PokeballType.GREAT_BALL: - return 1.5; - case PokeballType.ULTRA_BALL: - return 2; - case PokeballType.ROGUE_BALL: - return 3; - case PokeballType.MASTER_BALL: - return -1; - case PokeballType.LUXURY_BALL: - return 1; + case PokeballType.POKEBALL: + return 1; + case PokeballType.GREAT_BALL: + return 1.5; + case PokeballType.ULTRA_BALL: + return 2; + case PokeballType.ROGUE_BALL: + return 3; + case PokeballType.MASTER_BALL: + return -1; + case PokeballType.LUXURY_BALL: + return 1; } } export function getPokeballTintColor(type: PokeballType): number { switch (type) { - case PokeballType.POKEBALL: - return 0xd52929; - case PokeballType.GREAT_BALL: - return 0x94b4de; - case PokeballType.ULTRA_BALL: - return 0xe6cd31; - case PokeballType.ROGUE_BALL: - return 0xd52929; - case PokeballType.MASTER_BALL: - return 0xa441bd; - case PokeballType.LUXURY_BALL: - return 0xffde6a; + case PokeballType.POKEBALL: + return 0xd52929; + case PokeballType.GREAT_BALL: + return 0x94b4de; + case PokeballType.ULTRA_BALL: + return 0xe6cd31; + case PokeballType.ROGUE_BALL: + return 0xd52929; + case PokeballType.MASTER_BALL: + return 0xa441bd; + case PokeballType.LUXURY_BALL: + return 0xffde6a; } } diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index eb1b761e306..947ac939989 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -238,8 +238,8 @@ export abstract class PokemonSpeciesForm { isRareRegional(): boolean { switch (this.getRegion()) { - case Region.HISUI: - return true; + case Region.HISUI: + return true; } return false; @@ -265,14 +265,14 @@ export abstract class PokemonSpeciesForm { getBaseExp(): number { let ret = this.baseExp; switch (this.getFormSpriteKey()) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - case SpeciesFormKey.PRIMAL: - case SpeciesFormKey.GIGANTAMAX: - case SpeciesFormKey.ETERNAMAX: - ret *= 1.5; - break; + case SpeciesFormKey.MEGA: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + case SpeciesFormKey.PRIMAL: + case SpeciesFormKey.GIGANTAMAX: + case SpeciesFormKey.ETERNAMAX: + ret *= 1.5; + break; } return ret; } @@ -346,29 +346,29 @@ export abstract class PokemonSpeciesForm { } switch (this.speciesId) { - case Species.HIPPOPOTAS: - case Species.HIPPOWDON: - case Species.UNFEZANT: - case Species.FRILLISH: - case Species.JELLICENT: - case Species.PYROAR: - ret += female ? "-f" : ""; - break; + case Species.HIPPOPOTAS: + case Species.HIPPOWDON: + case Species.UNFEZANT: + case Species.FRILLISH: + case Species.JELLICENT: + case Species.PYROAR: + ret += female ? "-f" : ""; + break; } let formSpriteKey = this.getFormSpriteKey(formIndex); if (formSpriteKey) { switch (this.speciesId) { - case Species.DUDUNSPARCE: - break; - case Species.ZACIAN: - case Species.ZAMAZENTA: - if (formSpriteKey.startsWith("behemoth")) { - formSpriteKey = "crowned"; - } - default: - ret += `-${formSpriteKey}`; - break; + case Species.DUDUNSPARCE: + break; + case Species.ZACIAN: + case Species.ZAMAZENTA: + if (formSpriteKey.startsWith("behemoth")) { + formSpriteKey = "crowned"; + } + default: + ret += `-${formSpriteKey}`; + break; } } @@ -383,15 +383,15 @@ export abstract class PokemonSpeciesForm { let speciesId = this.speciesId; if (this.speciesId > 2000) { switch (this.speciesId) { - case Species.GALAR_SLOWPOKE: - break; - case Species.ETERNAL_FLOETTE: - break; - case Species.BLOODMOON_URSALUNA: - break; - default: - speciesId = speciesId % 2000; - break; + case Species.GALAR_SLOWPOKE: + break; + case Species.ETERNAL_FLOETTE: + break; + case Species.BLOODMOON_URSALUNA: + break; + default: + speciesId = speciesId % 2000; + break; } } let ret = speciesId.toString(); @@ -403,44 +403,44 @@ export abstract class PokemonSpeciesForm { } const formKey = forms[formIndex || 0].formKey; switch (formKey) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - case SpeciesFormKey.GIGANTAMAX: - case SpeciesFormKey.GIGANTAMAX_SINGLE: - case SpeciesFormKey.GIGANTAMAX_RAPID: - case "white": - case "black": - case "therian": - case "sky": - case "gorging": - case "gulping": - case "no-ice": - case "hangry": - case "crowned": - case "eternamax": - case "four": - case "droopy": - case "stretchy": - case "hero": - case "roaming": - case "complete": - case "10-complete": - case "10": - case "10-pc": - case "super": - case "unbound": - case "pau": - case "pompom": - case "sensu": - case "dusk": - case "midnight": - case "school": - case "dawn-wings": - case "dusk-mane": - case "ultra": - ret += `-${formKey}`; - break; + case SpeciesFormKey.MEGA: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + case SpeciesFormKey.GIGANTAMAX: + case SpeciesFormKey.GIGANTAMAX_SINGLE: + case SpeciesFormKey.GIGANTAMAX_RAPID: + case "white": + case "black": + case "therian": + case "sky": + case "gorging": + case "gulping": + case "no-ice": + case "hangry": + case "crowned": + case "eternamax": + case "four": + case "droopy": + case "stretchy": + case "hero": + case "roaming": + case "complete": + case "10-complete": + case "10": + case "10-pc": + case "super": + case "unbound": + case "pau": + case "pompom": + case "sensu": + case "dusk": + case "midnight": + case "school": + case "dawn-wings": + case "dusk-mane": + case "ultra": + ret += `-${formKey}`; + break; } } return ret; @@ -636,19 +636,19 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali const form = this.forms[formIndex]; let key: string | null; switch (form.formKey) { - case SpeciesFormKey.MEGA: - case SpeciesFormKey.PRIMAL: - case SpeciesFormKey.ETERNAMAX: - case SpeciesFormKey.MEGA_X: - case SpeciesFormKey.MEGA_Y: - key = form.formKey; - break; - default: - if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { - key = "gigantamax"; - } else { - key = null; - } + case SpeciesFormKey.MEGA: + case SpeciesFormKey.PRIMAL: + case SpeciesFormKey.ETERNAMAX: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + key = form.formKey; + break; + default: + if (form.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1) { + key = "gigantamax"; + } else { + key = null; + } } if (key) { @@ -690,18 +690,18 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali */ private getStrengthLevelDiff(strength: PartyMemberStrength): integer { switch (Math.min(strength, PartyMemberStrength.STRONGER)) { - case PartyMemberStrength.WEAKEST: - return 60; - case PartyMemberStrength.WEAKER: - return 40; - case PartyMemberStrength.WEAK: - return 20; - case PartyMemberStrength.AVERAGE: - return 8; - case PartyMemberStrength.STRONG: - return 4; - default: - return 0; + case PartyMemberStrength.WEAKEST: + return 60; + case PartyMemberStrength.WEAKER: + return 40; + case PartyMemberStrength.WEAK: + return 20; + case PartyMemberStrength.AVERAGE: + return 8; + case PartyMemberStrength.STRONG: + return 4; + default: + return 0; } } diff --git a/src/data/status-effect.ts b/src/data/status-effect.ts index ffe32a02aeb..4319985f43a 100644 --- a/src/data/status-effect.ts +++ b/src/data/status-effect.ts @@ -26,20 +26,20 @@ export class Status { function getStatusEffectMessageKey(statusEffect: StatusEffect | undefined): string { switch (statusEffect) { - case StatusEffect.POISON: - return "statusEffect:poison"; - case StatusEffect.TOXIC: - return "statusEffect:toxic"; - case StatusEffect.PARALYSIS: - return "statusEffect:paralysis"; - case StatusEffect.SLEEP: - return "statusEffect:sleep"; - case StatusEffect.FREEZE: - return "statusEffect:freeze"; - case StatusEffect.BURN: - return "statusEffect:burn"; - default: - return "statusEffect:none"; + case StatusEffect.POISON: + return "statusEffect:poison"; + case StatusEffect.TOXIC: + return "statusEffect:toxic"; + case StatusEffect.PARALYSIS: + return "statusEffect:paralysis"; + case StatusEffect.SLEEP: + return "statusEffect:sleep"; + case StatusEffect.FREEZE: + return "statusEffect:freeze"; + case StatusEffect.BURN: + return "statusEffect:burn"; + default: + return "statusEffect:none"; } } @@ -90,14 +90,14 @@ export function getStatusEffectDescriptor(statusEffect: StatusEffect): string { export function getStatusEffectCatchRateMultiplier(statusEffect: StatusEffect): number { switch (statusEffect) { - case StatusEffect.POISON: - case StatusEffect.TOXIC: - case StatusEffect.PARALYSIS: - case StatusEffect.BURN: - return 1.5; - case StatusEffect.SLEEP: - case StatusEffect.FREEZE: - return 2.5; + case StatusEffect.POISON: + case StatusEffect.TOXIC: + case StatusEffect.PARALYSIS: + case StatusEffect.BURN: + return 1.5; + case StatusEffect.SLEEP: + case StatusEffect.FREEZE: + return 2.5; } return 1; diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 6db68b92239..d8ee8d67925 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -34,21 +34,21 @@ export class Terrain { getAttackTypeMultiplier(attackType: Type): number { switch (this.terrainType) { - case TerrainType.ELECTRIC: - if (attackType === Type.ELECTRIC) { - return 1.3; - } - break; - case TerrainType.GRASSY: - if (attackType === Type.GRASS) { - return 1.3; - } - break; - case TerrainType.PSYCHIC: - if (attackType === Type.PSYCHIC) { - return 1.3; - } - break; + case TerrainType.ELECTRIC: + if (attackType === Type.ELECTRIC) { + return 1.3; + } + break; + case TerrainType.GRASSY: + if (attackType === Type.GRASS) { + return 1.3; + } + break; + case TerrainType.PSYCHIC: + if (attackType === Type.PSYCHIC) { + return 1.3; + } + break; } return 1; @@ -56,13 +56,13 @@ export class Terrain { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { switch (this.terrainType) { - case TerrainType.PSYCHIC: - if (!move.hasAttr(ProtectAttr)) { - const priority = new Utils.IntegerHolder(move.priority); - applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); - // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain - return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); - } + case TerrainType.PSYCHIC: + if (!move.hasAttr(ProtectAttr)) { + const priority = new Utils.IntegerHolder(move.priority); + applyAbAttrs(ChangeMovePriorityAbAttr, user, null, false, move, priority); + // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain + return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded()); + } } return false; @@ -71,14 +71,14 @@ export class Terrain { export function getTerrainName(terrainType: TerrainType): string { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:misty"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electric"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassy"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychic"); + case TerrainType.MISTY: + return i18next.t("terrain:misty"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electric"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassy"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychic"); } return ""; @@ -87,14 +87,14 @@ export function getTerrainName(terrainType: TerrainType): string { export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] { switch (terrainType) { - case TerrainType.MISTY: - return [ 232, 136, 200 ]; - case TerrainType.ELECTRIC: - return [ 248, 248, 120 ]; - case TerrainType.GRASSY: - return [ 120, 200, 80 ]; - case TerrainType.PSYCHIC: - return [ 160, 64, 160 ]; + case TerrainType.MISTY: + return [ 232, 136, 200 ]; + case TerrainType.ELECTRIC: + return [ 248, 248, 120 ]; + case TerrainType.GRASSY: + return [ 120, 200, 80 ]; + case TerrainType.PSYCHIC: + return [ 160, 64, 160 ]; } return [ 0, 0, 0 ]; diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index bbeecba84e6..fcc13975270 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -299,65 +299,65 @@ export class TrainerConfig { getDerivedType(trainerTypeToDeriveFrom: TrainerType | null = null): TrainerType { let trainerType = trainerTypeToDeriveFrom ? trainerTypeToDeriveFrom : this.trainerType; switch (trainerType) { - case TrainerType.RIVAL_2: - case TrainerType.RIVAL_3: - case TrainerType.RIVAL_4: - case TrainerType.RIVAL_5: - case TrainerType.RIVAL_6: - trainerType = TrainerType.RIVAL; - break; - case TrainerType.LANCE_CHAMPION: - trainerType = TrainerType.LANCE; - break; - case TrainerType.LARRY_ELITE: - trainerType = TrainerType.LARRY; - break; - case TrainerType.ROCKET_BOSS_GIOVANNI_1: - case TrainerType.ROCKET_BOSS_GIOVANNI_2: - trainerType = TrainerType.GIOVANNI; - break; - case TrainerType.MAXIE_2: - trainerType = TrainerType.MAXIE; - break; - case TrainerType.ARCHIE_2: - trainerType = TrainerType.ARCHIE; - break; - case TrainerType.CYRUS_2: - trainerType = TrainerType.CYRUS; - break; - case TrainerType.GHETSIS_2: - trainerType = TrainerType.GHETSIS; - break; - case TrainerType.LYSANDRE_2: - trainerType = TrainerType.LYSANDRE; - break; - case TrainerType.LUSAMINE_2: - trainerType = TrainerType.LUSAMINE; - break; - case TrainerType.GUZMA_2: - trainerType = TrainerType.GUZMA; - break; - case TrainerType.ROSE_2: - trainerType = TrainerType.ROSE; - break; - case TrainerType.PENNY_2: - trainerType = TrainerType.PENNY; - break; - case TrainerType.MARNIE_ELITE: - trainerType = TrainerType.MARNIE; - break; - case TrainerType.NESSA_ELITE: - trainerType = TrainerType.NESSA; - break; - case TrainerType.BEA_ELITE: - trainerType = TrainerType.BEA; - break; - case TrainerType.ALLISTER_ELITE: - trainerType = TrainerType.ALLISTER; - break; - case TrainerType.RAIHAN_ELITE: - trainerType = TrainerType.RAIHAN; - break; + case TrainerType.RIVAL_2: + case TrainerType.RIVAL_3: + case TrainerType.RIVAL_4: + case TrainerType.RIVAL_5: + case TrainerType.RIVAL_6: + trainerType = TrainerType.RIVAL; + break; + case TrainerType.LANCE_CHAMPION: + trainerType = TrainerType.LANCE; + break; + case TrainerType.LARRY_ELITE: + trainerType = TrainerType.LARRY; + break; + case TrainerType.ROCKET_BOSS_GIOVANNI_1: + case TrainerType.ROCKET_BOSS_GIOVANNI_2: + trainerType = TrainerType.GIOVANNI; + break; + case TrainerType.MAXIE_2: + trainerType = TrainerType.MAXIE; + break; + case TrainerType.ARCHIE_2: + trainerType = TrainerType.ARCHIE; + break; + case TrainerType.CYRUS_2: + trainerType = TrainerType.CYRUS; + break; + case TrainerType.GHETSIS_2: + trainerType = TrainerType.GHETSIS; + break; + case TrainerType.LYSANDRE_2: + trainerType = TrainerType.LYSANDRE; + break; + case TrainerType.LUSAMINE_2: + trainerType = TrainerType.LUSAMINE; + break; + case TrainerType.GUZMA_2: + trainerType = TrainerType.GUZMA; + break; + case TrainerType.ROSE_2: + trainerType = TrainerType.ROSE; + break; + case TrainerType.PENNY_2: + trainerType = TrainerType.PENNY; + break; + case TrainerType.MARNIE_ELITE: + trainerType = TrainerType.MARNIE; + break; + case TrainerType.NESSA_ELITE: + trainerType = TrainerType.NESSA; + break; + case TrainerType.BEA_ELITE: + trainerType = TrainerType.BEA; + break; + case TrainerType.ALLISTER_ELITE: + trainerType = TrainerType.ALLISTER; + break; + case TrainerType.RAIHAN_ELITE: + trainerType = TrainerType.RAIHAN; + break; } return trainerType; @@ -564,104 +564,104 @@ export class TrainerConfig { speciesPoolPerEvilTeamAdmin(team): TrainerTierPools { team = team.toLowerCase(); switch (team) { - case "rocket": { - return { - [TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ], - [TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ], - [TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ] - }; - } - 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.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, 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 ] - }; - } - case "galactic": { - return { - [TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ], - [TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ], - [TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ] - }; - } - case "plasma": { - return { - [TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ], - [TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ], - [TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ] - }; - } - case "flare": { - return { - [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.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] - }; - } - case "aether": { - return { - [TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ], - [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ], - [TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ] - }; - } - case "skull": { - return { - [TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ], - [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ], - [TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ] - }; - } - case "macro": { - return { - [TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ], - [TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ], - [TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ] - }; - } - case "star_1": { - return { - [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], - [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], - [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] - }; - } - case "star_2": { - return { - [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], - [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], - [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] - }; - } - case "star_3": { - return { - [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], - [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], - [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ] - }; - } - case "star_4": { - return { - [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], - [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], - [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ] - }; - } - case "star_5": { - return { - [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], - [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], - [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] - }; - } + case "rocket": { + return { + [TrainerPoolTier.COMMON]: [ Species.RATTATA, Species.KOFFING, Species.EKANS, Species.ZUBAT, Species.MAGIKARP, Species.HOUNDOUR, Species.ONIX, Species.CUBONE, Species.GROWLITHE, Species.MURKROW, Species.GASTLY, Species.EXEGGCUTE, Species.VOLTORB, Species.DROWZEE, Species.VILEPLUME ], + [TrainerPoolTier.UNCOMMON]: [ Species.PORYGON, Species.MANKEY, Species.MAGNEMITE, Species.ALOLA_SANDSHREW, Species.ALOLA_MEOWTH, Species.ALOLA_GRIMER, Species.ALOLA_GEODUDE, Species.PALDEA_TAUROS, Species.OMANYTE, Species.KABUTO, Species.MAGBY, Species.ELEKID ], + [TrainerPoolTier.RARE]: [ Species.DRATINI, Species.LARVITAR ] + }; + } + 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.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, 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 ] + }; + } + case "galactic": { + return { + [TrainerPoolTier.COMMON]: [ Species.BRONZOR, Species.SWINUB, Species.YANMA, Species.LICKITUNG, Species.TANGELA, Species.MAGBY, Species.ELEKID, Species.SKORUPI, Species.ZUBAT, Species.MURKROW, Species.MAGIKARP, Species.VOLTORB ], + [TrainerPoolTier.UNCOMMON]: [ Species.HISUI_GROWLITHE, Species.HISUI_QWILFISH, Species.SNEASEL, Species.DUSKULL, Species.ROTOM, Species.HISUI_VOLTORB, Species.GLIGAR, Species.ABRA ], + [TrainerPoolTier.RARE]: [ Species.URSALUNA, Species.HISUI_LILLIGANT, Species.SPIRITOMB, Species.HISUI_SNEASEL ] + }; + } + case "plasma": { + return { + [TrainerPoolTier.COMMON]: [ Species.YAMASK, Species.ROGGENROLA, Species.JOLTIK, Species.TYMPOLE, Species.FRILLISH, Species.FERROSEED, Species.SANDILE, Species.TIMBURR, Species.DARUMAKA, Species.FOONGUS, Species.CUBCHOO, Species.VANILLITE ], + [TrainerPoolTier.UNCOMMON]: [ Species.PAWNIARD, Species.VULLABY, Species.ZORUA, Species.DRILBUR, Species.KLINK, Species.TYNAMO, Species.GALAR_DARUMAKA, Species.GOLETT, Species.MIENFOO, Species.DURANT, Species.SIGILYPH ], + [TrainerPoolTier.RARE]: [ Species.HISUI_ZORUA, Species.AXEW, Species.DEINO, Species.HISUI_BRAVIARY ] + }; + } + case "flare": { + return { + [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.NOIBAT, Species.HISUI_AVALUGG, Species.HISUI_SLIGGOO ] + }; + } + case "aether": { + return { + [TrainerPoolTier.COMMON]: [ Species.BRUXISH, Species.SLOWPOKE, Species.BALTOY, Species.EXEGGCUTE, Species.ABRA, Species.ALOLA_RAICHU, Species.ELGYEM, Species.NATU, Species.BLIPBUG, Species.GIRAFARIG, Species.ORANGURU ], + [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.MEDITITE, Species.BELDUM, Species.HATENNA, Species.INKAY, Species.RALTS, Species.GALAR_MR_MIME ], + [TrainerPoolTier.RARE]: [ Species.ARMAROUGE, Species.HISUI_BRAVIARY, Species.PORYGON ] + }; + } + case "skull": { + return { + [TrainerPoolTier.COMMON]: [ Species.MAREANIE, Species.ALOLA_GRIMER, Species.GASTLY, Species.ZUBAT, Species.FOMANTIS, Species.VENIPEDE, Species.BUDEW, Species.KOFFING, Species.STUNKY, Species.CROAGUNK, Species.NIDORAN_F ], + [TrainerPoolTier.UNCOMMON]: [ Species.GALAR_SLOWPOKE, Species.SKORUPI, Species.PALDEA_WOOPER, Species.VULLABY, Species.HISUI_QWILFISH, Species.GLIMMET ], + [TrainerPoolTier.RARE]: [ Species.SKRELP, Species.HISUI_SNEASEL ] + }; + } + case "macro": { + return { + [TrainerPoolTier.COMMON]: [ Species.HATENNA, Species.FEEBAS, Species.BOUNSWEET, Species.SALANDIT, Species.GALAR_PONYTA, Species.GOTHITA, Species.FROSLASS, Species.VULPIX, Species.FRILLISH, Species.ODDISH, Species.SINISTEA ], + [TrainerPoolTier.UNCOMMON]: [ Species.VULLABY, Species.MAREANIE, Species.ALOLA_VULPIX, Species.TOGEPI, Species.GALAR_CORSOLA, Species.APPLIN ], + [TrainerPoolTier.RARE]: [ Species.TINKATINK, Species.HISUI_LILLIGANT ] + }; + } + case "star_1": { + return { + [TrainerPoolTier.COMMON]: [ Species.MURKROW, Species.SEEDOT, Species.CACNEA, Species.STUNKY, Species.SANDILE, Species.NYMBLE, Species.MASCHIFF, Species.GALAR_ZIGZAGOON ], + [TrainerPoolTier.UNCOMMON]: [ Species.UMBREON, Species.SNEASEL, Species.CORPHISH, Species.ZORUA, Species.INKAY, Species.BOMBIRDIER ], + [TrainerPoolTier.RARE]: [ Species.DEINO, Species.SPRIGATITO ] + }; + } + case "star_2": { + return { + [TrainerPoolTier.COMMON]: [ Species.GROWLITHE, Species.HOUNDOUR, Species.NUMEL, Species.LITWICK, Species.FLETCHLING, Species.LITLEO, Species.ROLYCOLY, Species.CAPSAKID ], + [TrainerPoolTier.UNCOMMON]: [ Species.PONYTA, Species.FLAREON, Species.MAGBY, Species.TORKOAL, Species.SALANDIT, Species.TURTONATOR ], + [TrainerPoolTier.RARE]: [ Species.LARVESTA, Species.FUECOCO ] + }; + } + case "star_3": { + return { + [TrainerPoolTier.COMMON]: [ Species.ZUBAT, Species.GRIMER, Species.STUNKY, Species.FOONGUS, Species.MAREANIE, Species.TOXEL, Species.SHROODLE, Species.PALDEA_WOOPER ], + [TrainerPoolTier.UNCOMMON]: [ Species.GASTLY, Species.SEVIPER, Species.SKRELP, Species.ALOLA_GRIMER, Species.GALAR_SLOWPOKE, Species.HISUI_QWILFISH ], + [TrainerPoolTier.RARE]: [ Species.GLIMMET, Species.BULBASAUR ] + }; + } + case "star_4": { + return { + [TrainerPoolTier.COMMON]: [ Species.CLEFFA, Species.IGGLYBUFF, Species.AZURILL, Species.COTTONEE, Species.FLABEBE, Species.HATENNA, Species.IMPIDIMP, Species.TINKATINK ], + [TrainerPoolTier.UNCOMMON]: [ Species.TOGEPI, Species.GARDEVOIR, Species.SYLVEON, Species.KLEFKI, Species.MIMIKYU, Species.ALOLA_VULPIX ], + [TrainerPoolTier.RARE]: [ Species.GALAR_PONYTA, Species.POPPLIO ] + }; + } + case "star_5": { + return { + [TrainerPoolTier.COMMON]: [ Species.SHROOMISH, Species.MAKUHITA, Species.MEDITITE, Species.CROAGUNK, Species.SCRAGGY, Species.MIENFOO, Species.PAWMI, Species.PALDEA_TAUROS ], + [TrainerPoolTier.UNCOMMON]: [ Species.RIOLU, Species.TIMBURR, Species.HAWLUCHA, Species.PASSIMIAN, Species.FALINKS, Species.FLAMIGO ], + [TrainerPoolTier.RARE]: [ Species.JANGMO_O, Species.QUAXLY ] + }; + } } console.warn(`Evil team admin for ${team} not found. Returning empty species pools.`); diff --git a/src/data/type.ts b/src/data/type.ts index 47bea8dd72b..483ec068d3c 100644 --- a/src/data/type.ts +++ b/src/data/type.ts @@ -29,260 +29,260 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa } switch (defType) { - case Type.NORMAL: - switch (attackType) { - case Type.FIGHTING: - return 2; - case Type.GHOST: - return 0; - default: - return 1; - } - case Type.FIGHTING: - switch (attackType) { - case Type.FLYING: - case Type.PSYCHIC: - case Type.FAIRY: - return 2; - case Type.ROCK: - case Type.BUG: - case Type.DARK: - return 0.5; - default: - return 1; - } - case Type.FLYING: - switch (attackType) { - case Type.ROCK: - case Type.ELECTRIC: - case Type.ICE: - return 2; - case Type.FIGHTING: - case Type.BUG: - case Type.GRASS: - return 0.5; - case Type.GROUND: - return 0; - default: - return 1; - } - case Type.POISON: - switch (attackType) { - case Type.GROUND: - case Type.PSYCHIC: - return 2; - case Type.FIGHTING: - case Type.POISON: - case Type.BUG: - case Type.GRASS: - case Type.FAIRY: - return 0.5; - default: - return 1; - } - case Type.GROUND: - switch (attackType) { - case Type.WATER: - case Type.GRASS: - case Type.ICE: - return 2; - case Type.POISON: - case Type.ROCK: - return 0.5; - case Type.ELECTRIC: - return 0; - default: - return 1; - } - case Type.ROCK: - switch (attackType) { - case Type.FIGHTING: - case Type.GROUND: - case Type.STEEL: - case Type.WATER: - case Type.GRASS: - return 2; case Type.NORMAL: - case Type.FLYING: - case Type.POISON: - case Type.FIRE: - return 0.5; - default: - return 1; - } - case Type.BUG: - switch (attackType) { - case Type.FLYING: - case Type.ROCK: - case Type.FIRE: - return 2; + switch (attackType) { + case Type.FIGHTING: + return 2; + case Type.GHOST: + return 0; + default: + return 1; + } case Type.FIGHTING: + switch (attackType) { + case Type.FLYING: + case Type.PSYCHIC: + case Type.FAIRY: + return 2; + case Type.ROCK: + case Type.BUG: + case Type.DARK: + return 0.5; + default: + return 1; + } + case Type.FLYING: + switch (attackType) { + case Type.ROCK: + case Type.ELECTRIC: + case Type.ICE: + return 2; + case Type.FIGHTING: + case Type.BUG: + case Type.GRASS: + return 0.5; + case Type.GROUND: + return 0; + default: + return 1; + } + case Type.POISON: + switch (attackType) { + case Type.GROUND: + case Type.PSYCHIC: + return 2; + case Type.FIGHTING: + case Type.POISON: + case Type.BUG: + case Type.GRASS: + case Type.FAIRY: + return 0.5; + default: + return 1; + } case Type.GROUND: - case Type.GRASS: - return 0.5; - default: - return 1; - } - case Type.GHOST: - switch (attackType) { + switch (attackType) { + case Type.WATER: + case Type.GRASS: + case Type.ICE: + return 2; + case Type.POISON: + case Type.ROCK: + return 0.5; + case Type.ELECTRIC: + return 0; + default: + return 1; + } + case Type.ROCK: + switch (attackType) { + case Type.FIGHTING: + case Type.GROUND: + case Type.STEEL: + case Type.WATER: + case Type.GRASS: + return 2; + case Type.NORMAL: + case Type.FLYING: + case Type.POISON: + case Type.FIRE: + return 0.5; + default: + return 1; + } + case Type.BUG: + switch (attackType) { + case Type.FLYING: + case Type.ROCK: + case Type.FIRE: + return 2; + case Type.FIGHTING: + case Type.GROUND: + case Type.GRASS: + return 0.5; + default: + return 1; + } case Type.GHOST: - case Type.DARK: - return 2; - case Type.POISON: - case Type.BUG: - return 0.5; - case Type.NORMAL: - case Type.FIGHTING: - return 0; - default: - return 1; - } - case Type.STEEL: - switch (attackType) { - case Type.FIGHTING: - case Type.GROUND: - case Type.FIRE: - return 2; - case Type.NORMAL: - case Type.FLYING: - case Type.ROCK: - case Type.BUG: + switch (attackType) { + case Type.GHOST: + case Type.DARK: + return 2; + case Type.POISON: + case Type.BUG: + return 0.5; + case Type.NORMAL: + case Type.FIGHTING: + return 0; + default: + return 1; + } case Type.STEEL: + switch (attackType) { + case Type.FIGHTING: + case Type.GROUND: + case Type.FIRE: + return 2; + case Type.NORMAL: + case Type.FLYING: + case Type.ROCK: + case Type.BUG: + case Type.STEEL: + case Type.GRASS: + case Type.PSYCHIC: + case Type.ICE: + case Type.DRAGON: + case Type.FAIRY: + return 0.5; + case Type.POISON: + return 0; + default: + return 1; + } + case Type.FIRE: + switch (attackType) { + case Type.GROUND: + case Type.ROCK: + case Type.WATER: + return 2; + case Type.BUG: + case Type.STEEL: + case Type.FIRE: + case Type.GRASS: + case Type.ICE: + case Type.FAIRY: + return 0.5; + default: + return 1; + } + case Type.WATER: + switch (attackType) { + case Type.GRASS: + case Type.ELECTRIC: + return 2; + case Type.STEEL: + case Type.FIRE: + case Type.WATER: + case Type.ICE: + return 0.5; + default: + return 1; + } case Type.GRASS: + switch (attackType) { + case Type.FLYING: + case Type.POISON: + case Type.BUG: + case Type.FIRE: + case Type.ICE: + return 2; + case Type.GROUND: + case Type.WATER: + case Type.GRASS: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } + case Type.ELECTRIC: + switch (attackType) { + case Type.GROUND: + return 2; + case Type.FLYING: + case Type.STEEL: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } case Type.PSYCHIC: + switch (attackType) { + case Type.BUG: + case Type.GHOST: + case Type.DARK: + return 2; + case Type.FIGHTING: + case Type.PSYCHIC: + return 0.5; + default: + return 1; + } case Type.ICE: + switch (attackType) { + case Type.FIGHTING: + case Type.ROCK: + case Type.STEEL: + case Type.FIRE: + return 2; + case Type.ICE: + return 0.5; + default: + return 1; + } case Type.DRAGON: - case Type.FAIRY: - return 0.5; - case Type.POISON: - return 0; - default: - return 1; - } - case Type.FIRE: - switch (attackType) { - case Type.GROUND: - case Type.ROCK: - case Type.WATER: - return 2; - case Type.BUG: - case Type.STEEL: - case Type.FIRE: - case Type.GRASS: - case Type.ICE: - case Type.FAIRY: - return 0.5; - default: - return 1; - } - case Type.WATER: - switch (attackType) { - case Type.GRASS: - case Type.ELECTRIC: - return 2; - case Type.STEEL: - case Type.FIRE: - case Type.WATER: - case Type.ICE: - return 0.5; - default: - return 1; - } - case Type.GRASS: - switch (attackType) { - case Type.FLYING: - case Type.POISON: - case Type.BUG: - case Type.FIRE: - case Type.ICE: - return 2; - case Type.GROUND: - case Type.WATER: - case Type.GRASS: - case Type.ELECTRIC: - return 0.5; - default: - return 1; - } - case Type.ELECTRIC: - switch (attackType) { - case Type.GROUND: - return 2; - case Type.FLYING: - case Type.STEEL: - case Type.ELECTRIC: - return 0.5; - default: - return 1; - } - case Type.PSYCHIC: - switch (attackType) { - case Type.BUG: - case Type.GHOST: + switch (attackType) { + case Type.ICE: + case Type.DRAGON: + case Type.FAIRY: + return 2; + case Type.FIRE: + case Type.WATER: + case Type.GRASS: + case Type.ELECTRIC: + return 0.5; + default: + return 1; + } case Type.DARK: - return 2; - case Type.FIGHTING: - case Type.PSYCHIC: - return 0.5; - default: - return 1; - } - case Type.ICE: - switch (attackType) { - case Type.FIGHTING: - case Type.ROCK: - case Type.STEEL: - case Type.FIRE: - return 2; - case Type.ICE: - return 0.5; - default: - return 1; - } - case Type.DRAGON: - switch (attackType) { - case Type.ICE: - case Type.DRAGON: + switch (attackType) { + case Type.FIGHTING: + case Type.BUG: + case Type.FAIRY: + return 2; + case Type.GHOST: + case Type.DARK: + return 0.5; + case Type.PSYCHIC: + return 0; + default: + return 1; + } case Type.FAIRY: - return 2; - case Type.FIRE: - case Type.WATER: - case Type.GRASS: - case Type.ELECTRIC: - return 0.5; - default: + switch (attackType) { + case Type.POISON: + case Type.STEEL: + return 2; + case Type.FIGHTING: + case Type.BUG: + case Type.DARK: + return 0.5; + case Type.DRAGON: + return 0; + default: + return 1; + } + case Type.STELLAR: return 1; - } - case Type.DARK: - switch (attackType) { - case Type.FIGHTING: - case Type.BUG: - case Type.FAIRY: - return 2; - case Type.GHOST: - case Type.DARK: - return 0.5; - case Type.PSYCHIC: - return 0; - default: - return 1; - } - case Type.FAIRY: - switch (attackType) { - case Type.POISON: - case Type.STEEL: - return 2; - case Type.FIGHTING: - case Type.BUG: - case Type.DARK: - return 0.5; - case Type.DRAGON: - return 0; - default: - return 1; - } - case Type.STELLAR: - return 1; } return 1; @@ -295,86 +295,86 @@ export function getTypeDamageMultiplier(attackType: Type, defType: Type): TypeDa export function getTypeDamageMultiplierColor(multiplier: TypeDamageMultiplier, side: "defense" | "offense"): string | undefined { if (side === "offense") { switch (multiplier) { - case 0: - return "#929292"; - case 0.125: - return "#FF5500"; - case 0.25: - return "#FF7400"; - case 0.5: - return "#FE8E00"; - case 1: - return undefined; - case 2: - return "#4AA500"; - case 4: - return "#4BB400"; - case 8: - return "#52C200"; + case 0: + return "#929292"; + case 0.125: + return "#FF5500"; + case 0.25: + return "#FF7400"; + case 0.5: + return "#FE8E00"; + case 1: + return undefined; + case 2: + return "#4AA500"; + case 4: + return "#4BB400"; + case 8: + return "#52C200"; } } else if (side === "defense") { switch (multiplier) { - case 0: - return "#B1B100"; - case 0.125: - return "#2DB4FF"; - case 0.25: - return "#00A4FF"; - case 0.5: - return "#0093FF"; - case 1: - return undefined; - case 2: - return "#FE8E00"; - case 4: - return "#FF7400"; - case 8: - return "#FF5500"; + case 0: + return "#B1B100"; + case 0.125: + return "#2DB4FF"; + case 0.25: + return "#00A4FF"; + case 0.5: + return "#0093FF"; + case 1: + return undefined; + case 2: + return "#FE8E00"; + case 4: + return "#FF7400"; + case 8: + return "#FF5500"; } } } export function getTypeRgb(type: Type): [ integer, integer, integer ] { switch (type) { - case Type.NORMAL: - return [ 168, 168, 120 ]; - case Type.FIGHTING: - return [ 192, 48, 40 ]; - case Type.FLYING: - return [ 168, 144, 240 ]; - case Type.POISON: - return [ 160, 64, 160 ]; - case Type.GROUND: - return [ 224, 192, 104 ]; - case Type.ROCK: - return [ 184, 160, 56 ]; - case Type.BUG: - return [ 168, 184, 32 ]; - case Type.GHOST: - return [ 112, 88, 152 ]; - case Type.STEEL: - return [ 184, 184, 208 ]; - case Type.FIRE: - return [ 240, 128, 48 ]; - case Type.WATER: - return [ 104, 144, 240 ]; - case Type.GRASS: - return [ 120, 200, 80 ]; - case Type.ELECTRIC: - return [ 248, 208, 48 ]; - case Type.PSYCHIC: - return [ 248, 88, 136 ]; - case Type.ICE: - return [ 152, 216, 216 ]; - case Type.DRAGON: - return [ 112, 56, 248 ]; - case Type.DARK: - return [ 112, 88, 72 ]; - case Type.FAIRY: - return [ 232, 136, 200 ]; - case Type.STELLAR: - return [ 255, 255, 255 ]; - default: - return [ 0, 0, 0 ]; + case Type.NORMAL: + return [ 168, 168, 120 ]; + case Type.FIGHTING: + return [ 192, 48, 40 ]; + case Type.FLYING: + return [ 168, 144, 240 ]; + case Type.POISON: + return [ 160, 64, 160 ]; + case Type.GROUND: + return [ 224, 192, 104 ]; + case Type.ROCK: + return [ 184, 160, 56 ]; + case Type.BUG: + return [ 168, 184, 32 ]; + case Type.GHOST: + return [ 112, 88, 152 ]; + case Type.STEEL: + return [ 184, 184, 208 ]; + case Type.FIRE: + return [ 240, 128, 48 ]; + case Type.WATER: + return [ 104, 144, 240 ]; + case Type.GRASS: + return [ 120, 200, 80 ]; + case Type.ELECTRIC: + return [ 248, 208, 48 ]; + case Type.PSYCHIC: + return [ 248, 88, 136 ]; + case Type.ICE: + return [ 152, 216, 216 ]; + case Type.DRAGON: + return [ 112, 56, 248 ]; + case Type.DARK: + return [ 112, 88, 72 ]; + case Type.FAIRY: + return [ 232, 136, 200 ]; + case Type.STELLAR: + return [ 255, 255, 255 ]; + default: + return [ 0, 0, 0 ]; } } diff --git a/src/data/variant.ts b/src/data/variant.ts index b7a01a4be89..13869635f1e 100644 --- a/src/data/variant.ts +++ b/src/data/variant.ts @@ -10,22 +10,22 @@ export const variantColorCache = {}; export function getVariantTint(variant: Variant): integer { switch (variant) { - case 0: - return 0xf8c020; - case 1: - return 0x20f8f0; - case 2: - return 0xe81048; + case 0: + return 0xf8c020; + case 1: + return 0x20f8f0; + case 2: + return 0xe81048; } } export function getVariantIcon(variant: Variant): integer { switch (variant) { - case 0: - return VariantTier.STANDARD; - case 1: - return VariantTier.RARE; - case 2: - return VariantTier.EPIC; + case 0: + return VariantTier.STANDARD; + case 1: + return VariantTier.RARE; + case 2: + return VariantTier.EPIC; } } diff --git a/src/data/weather.ts b/src/data/weather.ts index 8dfa17c4ef8..20c03af77c8 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -33,10 +33,10 @@ export class Weather { isImmutable(): boolean { switch (this.weatherType) { - case WeatherType.HEAVY_RAIN: - case WeatherType.HARSH_SUN: - case WeatherType.STRONG_WINDS: - return true; + case WeatherType.HEAVY_RAIN: + case WeatherType.HARSH_SUN: + case WeatherType.STRONG_WINDS: + return true; } return false; @@ -44,9 +44,9 @@ export class Weather { isDamaging(): boolean { switch (this.weatherType) { - case WeatherType.SANDSTORM: - case WeatherType.HAIL: - return true; + case WeatherType.SANDSTORM: + case WeatherType.HAIL: + return true; } return false; @@ -54,10 +54,10 @@ export class Weather { isTypeDamageImmune(type: Type): boolean { switch (this.weatherType) { - case WeatherType.SANDSTORM: - return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL; - case WeatherType.HAIL: - return type === Type.ICE; + case WeatherType.SANDSTORM: + return type === Type.GROUND || type === Type.ROCK || type === Type.STEEL; + case WeatherType.HAIL: + return type === Type.ICE; } return false; @@ -65,24 +65,24 @@ export class Weather { getAttackTypeMultiplier(attackType: Type): number { switch (this.weatherType) { - case WeatherType.SUNNY: - case WeatherType.HARSH_SUN: - if (attackType === Type.FIRE) { - return 1.5; - } - if (attackType === Type.WATER) { - return 0.5; - } - break; - case WeatherType.RAIN: - case WeatherType.HEAVY_RAIN: - if (attackType === Type.FIRE) { - return 0.5; - } - if (attackType === Type.WATER) { - return 1.5; - } - break; + case WeatherType.SUNNY: + case WeatherType.HARSH_SUN: + if (attackType === Type.FIRE) { + return 1.5; + } + if (attackType === Type.WATER) { + return 0.5; + } + break; + case WeatherType.RAIN: + case WeatherType.HEAVY_RAIN: + if (attackType === Type.FIRE) { + return 0.5; + } + if (attackType === Type.WATER) { + return 1.5; + } + break; } return 1; @@ -92,10 +92,10 @@ export class Weather { const moveType = user.getMoveType(move); switch (this.weatherType) { - case WeatherType.HARSH_SUN: - return move instanceof AttackMove && moveType === Type.WATER; - case WeatherType.HEAVY_RAIN: - return move instanceof AttackMove && moveType === Type.FIRE; + case WeatherType.HARSH_SUN: + return move instanceof AttackMove && moveType === Type.WATER; + case WeatherType.HEAVY_RAIN: + return move instanceof AttackMove && moveType === Type.FIRE; } return false; @@ -120,24 +120,24 @@ export class Weather { export function getWeatherStartMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyStartMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainStartMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormStartMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailStartMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowStartMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogStartMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainStartMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunStartMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsStartMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyStartMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainStartMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormStartMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailStartMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowStartMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogStartMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainStartMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunStartMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsStartMessage"); } return null; @@ -145,24 +145,24 @@ export function getWeatherStartMessage(weatherType: WeatherType): string | null export function getWeatherLapseMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyLapseMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainLapseMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormLapseMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailLapseMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowLapseMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogLapseMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainLapseMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunLapseMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsLapseMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyLapseMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainLapseMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormLapseMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailLapseMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowLapseMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogLapseMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainLapseMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunLapseMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsLapseMessage"); } return null; @@ -170,10 +170,10 @@ export function getWeatherLapseMessage(weatherType: WeatherType): string | null export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokemon): string | null { switch (weatherType) { - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); - case WeatherType.HAIL: - return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); + case WeatherType.HAIL: + return i18next.t("weather:hailDamageMessage", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }); } return null; @@ -181,24 +181,24 @@ export function getWeatherDamageMessage(weatherType: WeatherType, pokemon: Pokem export function getWeatherClearMessage(weatherType: WeatherType): string | null { switch (weatherType) { - case WeatherType.SUNNY: - return i18next.t("weather:sunnyClearMessage"); - case WeatherType.RAIN: - return i18next.t("weather:rainClearMessage"); - case WeatherType.SANDSTORM: - return i18next.t("weather:sandstormClearMessage"); - case WeatherType.HAIL: - return i18next.t("weather:hailClearMessage"); - case WeatherType.SNOW: - return i18next.t("weather:snowClearMessage"); - case WeatherType.FOG: - return i18next.t("weather:fogClearMessage"); - case WeatherType.HEAVY_RAIN: - return i18next.t("weather:heavyRainClearMessage"); - case WeatherType.HARSH_SUN: - return i18next.t("weather:harshSunClearMessage"); - case WeatherType.STRONG_WINDS: - return i18next.t("weather:strongWindsClearMessage"); + case WeatherType.SUNNY: + return i18next.t("weather:sunnyClearMessage"); + case WeatherType.RAIN: + return i18next.t("weather:rainClearMessage"); + case WeatherType.SANDSTORM: + return i18next.t("weather:sandstormClearMessage"); + case WeatherType.HAIL: + return i18next.t("weather:hailClearMessage"); + case WeatherType.SNOW: + return i18next.t("weather:snowClearMessage"); + case WeatherType.FOG: + return i18next.t("weather:fogClearMessage"); + case WeatherType.HEAVY_RAIN: + return i18next.t("weather:heavyRainClearMessage"); + case WeatherType.HARSH_SUN: + return i18next.t("weather:harshSunClearMessage"); + case WeatherType.STRONG_WINDS: + return i18next.t("weather:strongWindsClearMessage"); } return null; @@ -206,33 +206,33 @@ export function getWeatherClearMessage(weatherType: WeatherType): string | null export function getTerrainStartMessage(terrainType: TerrainType): string | null { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyStartMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricStartMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyStartMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicStartMessage"); - default: - console.warn("getTerrainStartMessage not defined. Using default null"); - return null; + case TerrainType.MISTY: + return i18next.t("terrain:mistyStartMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricStartMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyStartMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicStartMessage"); + default: + console.warn("getTerrainStartMessage not defined. Using default null"); + return null; } } export function getTerrainClearMessage(terrainType: TerrainType): string | null { switch (terrainType) { - case TerrainType.MISTY: - return i18next.t("terrain:mistyClearMessage"); - case TerrainType.ELECTRIC: - return i18next.t("terrain:electricClearMessage"); - case TerrainType.GRASSY: - return i18next.t("terrain:grassyClearMessage"); - case TerrainType.PSYCHIC: - return i18next.t("terrain:psychicClearMessage"); - default: - console.warn("getTerrainClearMessage not defined. Using default null"); - return null; + case TerrainType.MISTY: + return i18next.t("terrain:mistyClearMessage"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electricClearMessage"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassyClearMessage"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychicClearMessage"); + default: + console.warn("getTerrainClearMessage not defined. Using default null"); + return null; } } @@ -252,126 +252,126 @@ export function getRandomWeatherType(arena: any /* Importing from arena causes a let weatherPool: WeatherPoolEntry[] = []; const hasSun = arena.getTimeOfDay() < 2; switch (arena.biomeType) { - case Biome.GRASS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 7 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); - } - break; - case Biome.TALL_GRASS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 5 }, - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 }); - } - break; - case Biome.FOREST: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 5 } - ]; - break; - case Biome.SEA: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.RAIN, weight: 12 } - ]; - break; - case Biome.SWAMP: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.RAIN, weight: 4 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.BEACH: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 3 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); - } - break; - case Biome.LAKE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 10 }, - { weatherType: WeatherType.RAIN, weight: 5 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.SEABED: - weatherPool = [ - { weatherType: WeatherType.RAIN, weight: 1 } - ]; - break; - case Biome.BADLANDS: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.SANDSTORM, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); - } - break; - case Biome.DESERT: - weatherPool = [ - { weatherType: WeatherType.SANDSTORM, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - break; - case Biome.ICE_CAVE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.SNOW, weight: 4 }, - { weatherType: WeatherType.HAIL, weight: 1 } - ]; - break; - case Biome.MEADOW: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 2 } - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - case Biome.VOLCANO: - weatherPool = [ - { weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } - ]; - break; - case Biome.GRAVEYARD: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 3 }, - { weatherType: WeatherType.FOG, weight: 1 } - ]; - break; - case Biome.JUNGLE: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 8 }, - { weatherType: WeatherType.RAIN, weight: 2 } - ]; - break; - case Biome.SNOWY_FOREST: - weatherPool = [ - { weatherType: WeatherType.SNOW, weight: 7 }, - { weatherType: WeatherType.HAIL, weight: 1 } - ]; - break; - case Biome.ISLAND: - weatherPool = [ - { weatherType: WeatherType.NONE, weight: 5 }, - { weatherType: WeatherType.RAIN, weight: 1 }, - ]; - if (hasSun) { - weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); - } - break; + case Biome.GRASS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 7 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 3 }); + } + break; + case Biome.TALL_GRASS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 5 }, + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 8 }); + } + break; + case Biome.FOREST: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 5 } + ]; + break; + case Biome.SEA: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.RAIN, weight: 12 } + ]; + break; + case Biome.SWAMP: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.RAIN, weight: 4 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.BEACH: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 3 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); + } + break; + case Biome.LAKE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 10 }, + { weatherType: WeatherType.RAIN, weight: 5 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.SEABED: + weatherPool = [ + { weatherType: WeatherType.RAIN, weight: 1 } + ]; + break; + case Biome.BADLANDS: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.SANDSTORM, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 5 }); + } + break; + case Biome.DESERT: + weatherPool = [ + { weatherType: WeatherType.SANDSTORM, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + break; + case Biome.ICE_CAVE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.SNOW, weight: 4 }, + { weatherType: WeatherType.HAIL, weight: 1 } + ]; + break; + case Biome.MEADOW: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 2 } + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + case Biome.VOLCANO: + weatherPool = [ + { weatherType: hasSun ? WeatherType.SUNNY : WeatherType.NONE, weight: 1 } + ]; + break; + case Biome.GRAVEYARD: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 3 }, + { weatherType: WeatherType.FOG, weight: 1 } + ]; + break; + case Biome.JUNGLE: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 8 }, + { weatherType: WeatherType.RAIN, weight: 2 } + ]; + break; + case Biome.SNOWY_FOREST: + weatherPool = [ + { weatherType: WeatherType.SNOW, weight: 7 }, + { weatherType: WeatherType.HAIL, weight: 1 } + ]; + break; + case Biome.ISLAND: + weatherPool = [ + { weatherType: WeatherType.NONE, weight: 5 }, + { weatherType: WeatherType.RAIN, weight: 1 }, + ]; + if (hasSun) { + weatherPool.push({ weatherType: WeatherType.SUNNY, weight: 2 }); + } + break; } if (weatherPool.length > 1) { diff --git a/src/field/anims.ts b/src/field/anims.ts index 52a15aa4f20..c73c52027c5 100644 --- a/src/field/anims.ts +++ b/src/field/anims.ts @@ -4,21 +4,21 @@ import * as Utils from "../utils"; export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void { switch (pokeballType) { - case PokeballType.POKEBALL: - doDefaultPbOpenParticles(scene, x, y, 48); - break; - case PokeballType.GREAT_BALL: - doDefaultPbOpenParticles(scene, x, y, 96); - break; - case PokeballType.ULTRA_BALL: - doUbOpenParticles(scene, x, y, 8); - break; - case PokeballType.ROGUE_BALL: - doUbOpenParticles(scene, x, y, 10); - break; - case PokeballType.MASTER_BALL: - doMbOpenParticles(scene, x, y); - break; + case PokeballType.POKEBALL: + doDefaultPbOpenParticles(scene, x, y, 48); + break; + case PokeballType.GREAT_BALL: + doDefaultPbOpenParticles(scene, x, y, 96); + break; + case PokeballType.ULTRA_BALL: + doUbOpenParticles(scene, x, y, 8); + break; + case PokeballType.ROGUE_BALL: + doUbOpenParticles(scene, x, y, 10); + break; + case PokeballType.MASTER_BALL: + doMbOpenParticles(scene, x, y); + break; } } diff --git a/src/field/arena.ts b/src/field/arena.ts index ad1846884fc..7bfdf9a0000 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -129,18 +129,18 @@ export class Arena { if (ret.subLegendary || ret.legendary || ret.mythical) { switch (true) { - case (ret.baseTotal >= 720): - regen = level < 90; - break; - case (ret.baseTotal >= 670): - regen = level < 70; - break; - case (ret.baseTotal >= 580): - regen = level < 50; - break; - default: - regen = level < 30; - break; + case (ret.baseTotal >= 720): + regen = level < 90; + break; + case (ret.baseTotal >= 670): + regen = level < 70; + break; + case (ret.baseTotal >= 580): + regen = level < 50; + break; + default: + regen = level < 30; + break; } } } @@ -177,41 +177,41 @@ export class Arena { getSpeciesFormIndex(species: PokemonSpecies): integer { switch (species.speciesId) { - case Species.BURMY: - case Species.WORMADAM: - switch (this.biomeType) { - case Biome.BEACH: - return 1; - case Biome.SLUM: - return 2; - } - break; - case Species.ROTOM: - switch (this.biomeType) { - case Biome.VOLCANO: - return 1; - case Biome.SEA: - return 2; - case Biome.ICE_CAVE: - return 3; - case Biome.MOUNTAIN: - return 4; - case Biome.TALL_GRASS: - return 5; - } - break; - case Species.LYCANROC: - const timeOfDay = this.getTimeOfDay(); - switch (timeOfDay) { - case TimeOfDay.DAY: - case TimeOfDay.DAWN: - return 0; - case TimeOfDay.DUSK: - return 2; - case TimeOfDay.NIGHT: - return 1; - } - break; + case Species.BURMY: + case Species.WORMADAM: + switch (this.biomeType) { + case Biome.BEACH: + return 1; + case Biome.SLUM: + return 2; + } + break; + case Species.ROTOM: + switch (this.biomeType) { + case Biome.VOLCANO: + return 1; + case Biome.SEA: + return 2; + case Biome.ICE_CAVE: + return 3; + case Biome.MOUNTAIN: + return 4; + case Biome.TALL_GRASS: + return 5; + } + break; + case Species.LYCANROC: + const timeOfDay = this.getTimeOfDay(); + switch (timeOfDay) { + case TimeOfDay.DAY: + case TimeOfDay.DAWN: + return 0; + case TimeOfDay.DUSK: + return 2; + case TimeOfDay.NIGHT: + return 1; + } + break; } return 0; @@ -219,70 +219,70 @@ export class Arena { getTypeForBiome() { switch (this.biomeType) { - case Biome.TOWN: - case Biome.PLAINS: - case Biome.METROPOLIS: - return Type.NORMAL; - case Biome.GRASS: - case Biome.TALL_GRASS: - return Type.GRASS; - case Biome.FOREST: - case Biome.JUNGLE: - return Type.BUG; - case Biome.SLUM: - case Biome.SWAMP: - return Type.POISON; - case Biome.SEA: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - return Type.WATER; - case Biome.MOUNTAIN: - return Type.FLYING; - case Biome.BADLANDS: - return Type.GROUND; - case Biome.CAVE: - case Biome.DESERT: - return Type.ROCK; - case Biome.ICE_CAVE: - case Biome.SNOWY_FOREST: - return Type.ICE; - case Biome.MEADOW: - case Biome.FAIRY_CAVE: - case Biome.ISLAND: - return Type.FAIRY; - case Biome.POWER_PLANT: - return Type.ELECTRIC; - case Biome.VOLCANO: - return Type.FIRE; - case Biome.GRAVEYARD: - case Biome.TEMPLE: - return Type.GHOST; - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return Type.FIGHTING; - case Biome.FACTORY: - case Biome.LABORATORY: - return Type.STEEL; - case Biome.RUINS: - case Biome.SPACE: - return Type.PSYCHIC; - case Biome.WASTELAND: - case Biome.END: - return Type.DRAGON; - case Biome.ABYSS: - return Type.DARK; - default: - return Type.UNKNOWN; + case Biome.TOWN: + case Biome.PLAINS: + case Biome.METROPOLIS: + return Type.NORMAL; + case Biome.GRASS: + case Biome.TALL_GRASS: + return Type.GRASS; + case Biome.FOREST: + case Biome.JUNGLE: + return Type.BUG; + case Biome.SLUM: + case Biome.SWAMP: + return Type.POISON; + case Biome.SEA: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + return Type.WATER; + case Biome.MOUNTAIN: + return Type.FLYING; + case Biome.BADLANDS: + return Type.GROUND; + case Biome.CAVE: + case Biome.DESERT: + return Type.ROCK; + case Biome.ICE_CAVE: + case Biome.SNOWY_FOREST: + return Type.ICE; + case Biome.MEADOW: + case Biome.FAIRY_CAVE: + case Biome.ISLAND: + return Type.FAIRY; + case Biome.POWER_PLANT: + return Type.ELECTRIC; + case Biome.VOLCANO: + return Type.FIRE; + case Biome.GRAVEYARD: + case Biome.TEMPLE: + return Type.GHOST; + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return Type.FIGHTING; + case Biome.FACTORY: + case Biome.LABORATORY: + return Type.STEEL; + case Biome.RUINS: + case Biome.SPACE: + return Type.PSYCHIC; + case Biome.WASTELAND: + case Biome.END: + return Type.DRAGON; + case Biome.ABYSS: + return Type.DARK; + default: + return Type.UNKNOWN; } } getBgTerrainColorRatioForBiome(): number { switch (this.biomeType) { - case Biome.SPACE: - return 1; - case Biome.END: - return 0; + case Biome.SPACE: + return 1; + case Biome.END: + return 0; } return 131 / 180; @@ -424,52 +424,52 @@ export class Arena { */ getTrainerChance(): integer { switch (this.biomeType) { - case Biome.METROPOLIS: - return 2; - case Biome.SLUM: - case Biome.BEACH: - case Biome.DOJO: - case Biome.CONSTRUCTION_SITE: - return 4; - case Biome.PLAINS: - case Biome.GRASS: - case Biome.LAKE: - case Biome.CAVE: - return 6; - case Biome.TALL_GRASS: - case Biome.FOREST: - case Biome.SEA: - case Biome.SWAMP: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.DESERT: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.SNOWY_FOREST: - return 8; - case Biome.ICE_CAVE: - case Biome.VOLCANO: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - return 12; - case Biome.SEABED: - case Biome.ABYSS: - case Biome.SPACE: - case Biome.TEMPLE: - return 16; - default: - return 0; + case Biome.METROPOLIS: + return 2; + case Biome.SLUM: + case Biome.BEACH: + case Biome.DOJO: + case Biome.CONSTRUCTION_SITE: + return 4; + case Biome.PLAINS: + case Biome.GRASS: + case Biome.LAKE: + case Biome.CAVE: + return 6; + case Biome.TALL_GRASS: + case Biome.FOREST: + case Biome.SEA: + case Biome.SWAMP: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.DESERT: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.SNOWY_FOREST: + return 8; + case Biome.ICE_CAVE: + case Biome.VOLCANO: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + return 12; + case Biome.SEABED: + case Biome.ABYSS: + case Biome.SPACE: + case Biome.TEMPLE: + return 16; + default: + return 0; } } getTimeOfDay(): TimeOfDay { switch (this.biomeType) { - case Biome.ABYSS: - return TimeOfDay.NIGHT; + case Biome.ABYSS: + return TimeOfDay.NIGHT; } const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.waveCycleOffset) % 40; @@ -491,35 +491,35 @@ export class Arena { isOutside(): boolean { switch (this.biomeType) { - case Biome.SEABED: - case Biome.CAVE: - case Biome.ICE_CAVE: - case Biome.POWER_PLANT: - case Biome.DOJO: - case Biome.FACTORY: - case Biome.ABYSS: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.LABORATORY: - return false; - default: - return true; + case Biome.SEABED: + case Biome.CAVE: + case Biome.ICE_CAVE: + case Biome.POWER_PLANT: + case Biome.DOJO: + case Biome.FACTORY: + case Biome.ABYSS: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.LABORATORY: + return false; + default: + return true; } } overrideTint(): [integer, integer, integer] { switch (Overrides.ARENA_TINT_OVERRIDE) { - case TimeOfDay.DUSK: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; - break; - case (TimeOfDay.NIGHT): - return [ 64, 64, 64 ]; - break; - case TimeOfDay.DAWN: - case TimeOfDay.DAY: - default: - return [ 128, 128, 128 ]; - break; + case TimeOfDay.DUSK: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + break; + case (TimeOfDay.NIGHT): + return [ 64, 64, 64 ]; + break; + case TimeOfDay.DAWN: + case TimeOfDay.DAY: + default: + return [ 128, 128, 128 ]; + break; } } @@ -528,10 +528,10 @@ export class Arena { return this.overrideTint(); } switch (this.biomeType) { - case Biome.ABYSS: - return [ 64, 64, 64 ]; - default: - return [ 128, 128, 128 ]; + case Biome.ABYSS: + return [ 64, 64, 64 ]; + default: + return [ 128, 128, 128 ]; } } @@ -544,8 +544,8 @@ export class Arena { } switch (this.biomeType) { - default: - return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; + default: + return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer]; } } @@ -554,10 +554,10 @@ export class Arena { return this.overrideTint(); } switch (this.biomeType) { - case Biome.ABYSS: - case Biome.SPACE: - case Biome.END: - return this.getDayTint(); + case Biome.ABYSS: + case Biome.SPACE: + case Biome.END: + return this.getDayTint(); } if (!this.isOutside()) { @@ -565,8 +565,8 @@ export class Arena { } switch (this.biomeType) { - default: - return [ 48, 48, 98 ]; + default: + return [ 48, 48, 98 ]; } } @@ -747,77 +747,77 @@ export class Arena { getBgmLoopPoint(): number { switch (this.biomeType) { - case Biome.TOWN: - return 7.288; - case Biome.PLAINS: - return 17.485; - case Biome.GRASS: - return 1.995; - case Biome.TALL_GRASS: - return 9.608; - case Biome.METROPOLIS: - return 141.470; - case Biome.FOREST: - return 4.294; - case Biome.SEA: - return 0.024; - case Biome.SWAMP: - return 4.461; - case Biome.BEACH: - return 3.462; - case Biome.LAKE: - return 7.215; - case Biome.SEABED: - return 2.600; - case Biome.MOUNTAIN: - return 4.018; - case Biome.BADLANDS: - return 17.790; - case Biome.CAVE: - return 14.240; - case Biome.DESERT: - return 1.143; - case Biome.ICE_CAVE: - return 0.000; - case Biome.MEADOW: - return 3.891; - case Biome.POWER_PLANT: - return 9.447; - case Biome.VOLCANO: - return 17.637; - case Biome.GRAVEYARD: - return 3.232; - case Biome.DOJO: - return 6.205; - case Biome.FACTORY: - return 4.985; - case Biome.RUINS: - return 0.000; - case Biome.WASTELAND: - return 6.336; - case Biome.ABYSS: - return 5.130; - case Biome.SPACE: - return 20.036; - case Biome.CONSTRUCTION_SITE: - return 1.222; - case Biome.JUNGLE: - return 0.000; - case Biome.FAIRY_CAVE: - return 4.542; - case Biome.TEMPLE: - return 2.547; - case Biome.ISLAND: - return 2.751; - case Biome.LABORATORY: - return 114.862; - case Biome.SLUM: - return 0.000; - case Biome.SNOWY_FOREST: - return 3.047; - default: - console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); - return 0; + case Biome.TOWN: + return 7.288; + case Biome.PLAINS: + return 17.485; + case Biome.GRASS: + return 1.995; + case Biome.TALL_GRASS: + return 9.608; + case Biome.METROPOLIS: + return 141.470; + case Biome.FOREST: + return 4.294; + case Biome.SEA: + return 0.024; + case Biome.SWAMP: + return 4.461; + case Biome.BEACH: + return 3.462; + case Biome.LAKE: + return 7.215; + case Biome.SEABED: + return 2.600; + case Biome.MOUNTAIN: + return 4.018; + case Biome.BADLANDS: + return 17.790; + case Biome.CAVE: + return 14.240; + case Biome.DESERT: + return 1.143; + case Biome.ICE_CAVE: + return 0.000; + case Biome.MEADOW: + return 3.891; + case Biome.POWER_PLANT: + return 9.447; + case Biome.VOLCANO: + return 17.637; + case Biome.GRAVEYARD: + return 3.232; + case Biome.DOJO: + return 6.205; + case Biome.FACTORY: + return 4.985; + case Biome.RUINS: + return 0.000; + case Biome.WASTELAND: + return 6.336; + case Biome.ABYSS: + return 5.130; + case Biome.SPACE: + return 20.036; + case Biome.CONSTRUCTION_SITE: + return 1.222; + case Biome.JUNGLE: + return 0.000; + case Biome.FAIRY_CAVE: + return 4.542; + case Biome.TEMPLE: + return 2.547; + case Biome.ISLAND: + return 2.751; + case Biome.LABORATORY: + return 114.862; + case Biome.SLUM: + return 0.000; + case Biome.SNOWY_FOREST: + return 3.047; + default: + console.warn(`missing bgm loop-point for biome "${Biome[this.biomeType]}" (=${this.biomeType})`); + return 0; } } } @@ -828,32 +828,32 @@ export function getBiomeKey(biome: Biome): string { export function getBiomeHasProps(biomeType: Biome): boolean { switch (biomeType) { - case Biome.METROPOLIS: - case Biome.BEACH: - case Biome.LAKE: - case Biome.SEABED: - case Biome.MOUNTAIN: - case Biome.BADLANDS: - case Biome.CAVE: - case Biome.DESERT: - case Biome.ICE_CAVE: - case Biome.MEADOW: - case Biome.POWER_PLANT: - case Biome.VOLCANO: - case Biome.GRAVEYARD: - case Biome.FACTORY: - case Biome.RUINS: - case Biome.WASTELAND: - case Biome.ABYSS: - case Biome.CONSTRUCTION_SITE: - case Biome.JUNGLE: - case Biome.FAIRY_CAVE: - case Biome.TEMPLE: - case Biome.SNOWY_FOREST: - case Biome.ISLAND: - case Biome.LABORATORY: - case Biome.END: - return true; + case Biome.METROPOLIS: + case Biome.BEACH: + case Biome.LAKE: + case Biome.SEABED: + case Biome.MOUNTAIN: + case Biome.BADLANDS: + case Biome.CAVE: + case Biome.DESERT: + case Biome.ICE_CAVE: + case Biome.MEADOW: + case Biome.POWER_PLANT: + case Biome.VOLCANO: + case Biome.GRAVEYARD: + case Biome.FACTORY: + case Biome.RUINS: + case Biome.WASTELAND: + case Biome.ABYSS: + case Biome.CONSTRUCTION_SITE: + case Biome.JUNGLE: + case Biome.FAIRY_CAVE: + case Biome.TEMPLE: + case Biome.SNOWY_FOREST: + case Biome.ISLAND: + case Biome.LABORATORY: + case Biome.END: + return true; } return false; diff --git a/src/field/damage-number-handler.ts b/src/field/damage-number-handler.ts index ae0692da342..4ddcd2d3ee7 100644 --- a/src/field/damage-number-handler.ts +++ b/src/field/damage-number-handler.ts @@ -29,21 +29,21 @@ export default class DamageNumberHandler { let [ textColor, shadowColor ] : TextAndShadowArr = [ null, null ]; switch (result) { - case HitResult.SUPER_EFFECTIVE: - [ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ]; - break; - case HitResult.NOT_VERY_EFFECTIVE: - [ textColor, shadowColor ] = [ "#f08030", "#c03028" ]; - break; - case HitResult.ONE_HIT_KO: - [ textColor, shadowColor ] = [ "#a040a0", "#483850" ]; - break; - case HitResult.HEAL: - [ textColor, shadowColor ] = [ "#78c850", "#588040" ]; - break; - default: - [ textColor, shadowColor ] = [ "#ffffff", "#636363" ]; - break; + case HitResult.SUPER_EFFECTIVE: + [ textColor, shadowColor ] = [ "#f8d030", "#b8a038" ]; + break; + case HitResult.NOT_VERY_EFFECTIVE: + [ textColor, shadowColor ] = [ "#f08030", "#c03028" ]; + break; + case HitResult.ONE_HIT_KO: + [ textColor, shadowColor ] = [ "#a040a0", "#483850" ]; + break; + case HitResult.HEAL: + [ textColor, shadowColor ] = [ "#78c850", "#588040" ]; + break; + default: + [ textColor, shadowColor ] = [ "#ffffff", "#636363" ]; + break; } if (textColor) { diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 9aaadf29657..0afbbf105e2 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -680,12 +680,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getFieldPositionOffset(): [ number, number ] { switch (this.fieldPosition) { - case FieldPosition.CENTER: - return [ 0, 0 ]; - case FieldPosition.LEFT: - return [ -32, -8 ]; - case FieldPosition.RIGHT: - return [ 32, 0 ]; + case FieldPosition.CENTER: + return [ 0, 0 ]; + case FieldPosition.LEFT: + return [ -32, -8 ]; + case FieldPosition.RIGHT: + return [ 32, 0 ]; } } @@ -917,39 +917,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { let ret = statValue.value * this.getStatStageMultiplier(stat, opponent, move, ignoreOppAbility, isCritical, simulated); switch (stat) { - case Stat.ATK: - if (this.getTag(BattlerTagType.SLOW_START)) { - ret >>= 1; - } - break; - case Stat.DEF: - if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { - ret *= 1.5; - } - break; - case Stat.SPATK: - break; - case Stat.SPDEF: - if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { - ret *= 1.5; - } - break; - case Stat.SPD: - const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; - if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { - ret *= 2; - } - if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { - ret >>= 2; - } + case Stat.ATK: + if (this.getTag(BattlerTagType.SLOW_START)) { + ret >>= 1; + } + break; + case Stat.DEF: + if (this.isOfType(Type.ICE) && this.scene.arena.weather?.weatherType === WeatherType.SNOW) { + ret *= 1.5; + } + break; + case Stat.SPATK: + break; + case Stat.SPDEF: + if (this.isOfType(Type.ROCK) && this.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { + ret *= 1.5; + } + break; + case Stat.SPD: + const side = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + if (this.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, side)) { + ret *= 2; + } + if (this.scene.arena.getTagOnSide(ArenaTagType.GRASS_WATER_PLEDGE, side)) { + ret >>= 2; + } - if (this.getTag(BattlerTagType.SLOW_START)) { - ret >>= 1; - } - if (this.status && this.status.effect === StatusEffect.PARALYSIS) { - ret >>= 1; - } - break; + if (this.getTag(BattlerTagType.SLOW_START)) { + ret >>= 1; + } + if (this.status && this.status.effect === StatusEffect.PARALYSIS) { + ret >>= 1; + } + break; } const highestStatBoost = this.findTag(t => t instanceof HighestStatBoostTag && (t as HighestStatBoostTag).stat === stat) as HighestStatBoostTag; @@ -2338,14 +2338,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (opponent) { if (isCritical) { switch (stat) { - case Stat.ATK: - case Stat.SPATK: - statStage.value = Math.max(statStage.value, 0); - break; - case Stat.DEF: - case Stat.SPDEF: - statStage.value = Math.min(statStage.value, 0); - break; + case Stat.ATK: + case Stat.SPATK: + statStage.value = Math.max(statStage.value, 0); + break; + case Stat.DEF: + case Stat.SPDEF: + statStage.value = Math.min(statStage.value, 0); + break; } } if (!ignoreOppAbility) { @@ -2795,15 +2795,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { // want to include is.Fainted() in case multi hit move ends early, still want to render message if (source.turnData.hitsLeft === 1 || this.isFainted()) { switch (result) { - case HitResult.SUPER_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); - break; - case HitResult.ONE_HIT_KO: - this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); - break; + case HitResult.SUPER_EFFECTIVE: + this.scene.queueMessage(i18next.t("battle:hitResultSuperEffective")); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.queueMessage(i18next.t("battle:hitResultNotVeryEffective")); + break; + case HitResult.ONE_HIT_KO: + this.scene.queueMessage(i18next.t("battle:hitResultOneHitKO")); + break; } } @@ -3355,53 +3355,53 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } switch (effect) { - case StatusEffect.POISON: - case StatusEffect.TOXIC: + case StatusEffect.POISON: + case StatusEffect.TOXIC: // Check if the Pokemon is immune to Poison/Toxic or if the source pokemon is canceling the immunity - const poisonImmunity = types.map(defType => { + const poisonImmunity = types.map(defType => { // Check if the Pokemon is not immune to Poison/Toxic - if (defType !== Type.POISON && defType !== Type.STEEL) { - return false; - } + if (defType !== Type.POISON && defType !== Type.STEEL) { + return false; + } - // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity - const cancelImmunity = new Utils.BooleanHolder(false); - if (sourcePokemon) { - applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); - if (cancelImmunity.value) { + // Check if the source Pokemon has an ability that cancels the Poison/Toxic immunity + const cancelImmunity = new Utils.BooleanHolder(false); + if (sourcePokemon) { + applyAbAttrs(IgnoreTypeStatusEffectImmunityAbAttr, sourcePokemon, cancelImmunity, false, effect, defType); + if (cancelImmunity.value) { + return false; + } + } + + return true; + }); + + if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) { + if (poisonImmunity.includes(true)) { return false; } } - - return true; - }); - - if (this.isOfType(Type.POISON) || this.isOfType(Type.STEEL)) { - if (poisonImmunity.includes(true)) { + break; + case StatusEffect.PARALYSIS: + if (this.isOfType(Type.ELECTRIC)) { return false; } - } - break; - case StatusEffect.PARALYSIS: - if (this.isOfType(Type.ELECTRIC)) { - return false; - } - break; - case StatusEffect.SLEEP: - if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { - return false; - } - break; - case StatusEffect.FREEZE: - if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { - return false; - } - break; - case StatusEffect.BURN: - if (this.isOfType(Type.FIRE)) { - return false; - } - break; + break; + case StatusEffect.SLEEP: + if (this.isGrounded() && this.scene.arena.terrain?.terrainType === TerrainType.ELECTRIC) { + return false; + } + break; + case StatusEffect.FREEZE: + if (this.isOfType(Type.ICE) || (this.scene?.arena?.weather?.weatherType && [ WeatherType.SUNNY, WeatherType.HARSH_SUN ].includes(this.scene.arena.weather.weatherType))) { + return false; + } + break; + case StatusEffect.BURN: + if (this.isOfType(Type.FIRE)) { + return false; + } + break; } const cancelled = new Utils.BooleanHolder(false); @@ -4501,35 +4501,35 @@ export class EnemyPokemon extends Pokemon { generateAndPopulateMoveset(formIndex?: integer): void { switch (true) { - case (this.species.speciesId === Species.SMEARGLE): - this.moveset = [ - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH), - new PokemonMove(Moves.SKETCH) - ]; - break; - case (this.species.speciesId === Species.ETERNATUS): - this.moveset = (formIndex !== undefined ? formIndex : this.formIndex) - ? [ - new PokemonMove(Moves.DYNAMAX_CANNON), - new PokemonMove(Moves.CROSS_POISON), - new PokemonMove(Moves.FLAMETHROWER), - new PokemonMove(Moves.RECOVER, 0, -4) - ] - : [ - new PokemonMove(Moves.ETERNABEAM), - new PokemonMove(Moves.SLUDGE_BOMB), - new PokemonMove(Moves.FLAMETHROWER), - new PokemonMove(Moves.COSMIC_POWER) + case (this.species.speciesId === Species.SMEARGLE): + this.moveset = [ + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH), + new PokemonMove(Moves.SKETCH) ]; - if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { - this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); - } - break; - default: - super.generateAndPopulateMoveset(); - break; + break; + case (this.species.speciesId === Species.ETERNATUS): + this.moveset = (formIndex !== undefined ? formIndex : this.formIndex) + ? [ + new PokemonMove(Moves.DYNAMAX_CANNON), + new PokemonMove(Moves.CROSS_POISON), + new PokemonMove(Moves.FLAMETHROWER), + new PokemonMove(Moves.RECOVER, 0, -4) + ] + : [ + new PokemonMove(Moves.ETERNABEAM), + new PokemonMove(Moves.SLUDGE_BOMB), + new PokemonMove(Moves.FLAMETHROWER), + new PokemonMove(Moves.COSMIC_POWER) + ]; + if (this.scene.gameMode.hasChallenge(Challenges.INVERSE_BATTLE)) { + this.moveset[2] = new PokemonMove(Moves.THUNDERBOLT); + } + break; + default: + super.generateAndPopulateMoveset(); + break; } } @@ -4569,135 +4569,135 @@ export class EnemyPokemon extends Pokemon { } } switch (this.aiType) { - case AiType.RANDOM: // No enemy should spawn with this AI type in-game - const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? - return { move: moveId, targets: this.getNextTargets(moveId) }; - case AiType.SMART_RANDOM: - case AiType.SMART: + case AiType.RANDOM: // No enemy should spawn with this AI type in-game + const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)]!.moveId; // TODO: is the bang correct? + return { move: moveId, targets: this.getNextTargets(moveId) }; + case AiType.SMART_RANDOM: + case AiType.SMART: /** * Search this Pokemon's move pool for moves that will KO an opposing target. * If there are any moves that can KO an opponent (i.e. a player Pokemon), * those moves are the only ones considered for selection on this turn. */ - const koMoves = movePool.filter(pkmnMove => { - if (!pkmnMove) { - return false; - } + const koMoves = movePool.filter(pkmnMove => { + if (!pkmnMove) { + return false; + } - const move = pkmnMove.getMove()!; - if (move.moveTarget === MoveTarget.ATTACKER) { - return false; - } + const move = pkmnMove.getMove()!; + if (move.moveTarget === MoveTarget.ATTACKER) { + return false; + } - const fieldPokemon = this.scene.getField(); - const moveTargets = getMoveTargets(this, move.id).targets - .map(ind => fieldPokemon[ind]) - .filter(p => this.isPlayer() !== p.isPlayer()); - // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus - const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); + const fieldPokemon = this.scene.getField(); + const moveTargets = getMoveTargets(this, move.id).targets + .map(ind => fieldPokemon[ind]) + .filter(p => this.isPlayer() !== p.isPlayer()); + // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus + const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); - return move.category !== MoveCategory.STATUS + return move.category !== MoveCategory.STATUS && moveTargets.some(p => { const doesNotFail = move.applyConditions(this, p, move) || [ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id); return doesNotFail && p.getAttackDamage(this, move, !p.battleData.abilityRevealed, false, isCritical).damage >= p.hp; }); - }, this); + }, this); - if (koMoves.length > 0) { - movePool = koMoves; - } + if (koMoves.length > 0) { + movePool = koMoves; + } - /** + /** * Move selection is based on the move's calculated "benefit score" against the * best possible target(s) (as determined by {@linkcode getNextTargets}). * For more information on how benefit scores are calculated, see `docs/enemy-ai.md`. */ - const moveScores = movePool.map(() => 0); - const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct? - for (const m in movePool) { - const pokemonMove = movePool[m]!; // TODO: is the bang correct? - const move = pokemonMove.getMove(); + const moveScores = movePool.map(() => 0); + const moveTargets = Object.fromEntries(movePool.map(m => [ m!.moveId, this.getNextTargets(m!.moveId) ])); // TODO: are those bangs correct? + for (const m in movePool) { + const pokemonMove = movePool[m]!; // TODO: is the bang correct? + const move = pokemonMove.getMove(); - let moveScore = moveScores[m]; - const targetScores: integer[] = []; + let moveScore = moveScores[m]; + const targetScores: integer[] = []; - for (const mt of moveTargets[move.id]) { + for (const mt of moveTargets[move.id]) { // Prevent a target score from being calculated when the target is whoever attacks the user - if (mt === BattlerIndex.ATTACKER) { - break; - } + if (mt === BattlerIndex.ATTACKER) { + break; + } - const target = this.scene.getField()[mt]; - /** + const target = this.scene.getField()[mt]; + /** * The "target score" of a move is given by the move's user benefit score + the move's target benefit score. * If the target is an ally, the target benefit score is multiplied by -1. */ - let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); - if (Number.isNaN(targetScore)) { - console.error(`Move ${move.name} returned score of NaN`); - targetScore = 0; - } - /** + let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1); + if (Number.isNaN(targetScore)) { + console.error(`Move ${move.name} returned score of NaN`); + targetScore = 0; + } + /** * If this move is unimplemented, or the move is known to fail when used, set its * target score to -20 */ - if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) { - targetScore = -20; - } else if (move instanceof AttackMove) { + if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![ Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP ].includes(move.id)) { + targetScore = -20; + } else if (move instanceof AttackMove) { /** * Attack moves are given extra multipliers to their base benefit score based on * the move's type effectiveness against the target and whether the move is a STAB move. */ - const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed); - if (target.isPlayer() !== this.isPlayer()) { - targetScore *= effectiveness; - if (this.isOfType(move.type)) { - targetScore *= 1.5; + const effectiveness = target.getMoveEffectiveness(this, move, !target.battleData?.abilityRevealed); + if (target.isPlayer() !== this.isPlayer()) { + targetScore *= effectiveness; + if (this.isOfType(move.type)) { + targetScore *= 1.5; + } + } else if (effectiveness) { + targetScore /= effectiveness; + if (this.isOfType(move.type)) { + targetScore /= 1.5; + } } - } else if (effectiveness) { - targetScore /= effectiveness; - if (this.isOfType(move.type)) { - targetScore /= 1.5; + /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ + if (!targetScore) { + targetScore = -20; } } - /** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */ - if (!targetScore) { - targetScore = -20; - } + targetScores.push(targetScore); } - targetScores.push(targetScore); + // When a move has multiple targets, its score is equal to the maximum target score across all targets + moveScore += Math.max(...targetScores); + + // could make smarter by checking opponent def/spdef + moveScores[m] = moveScore; } - // When a move has multiple targets, its score is equal to the maximum target score across all targets - moveScore += Math.max(...targetScores); - // could make smarter by checking opponent def/spdef - moveScores[m] = moveScore; - } + console.log(moveScores); - console.log(moveScores); - - // Sort the move pool in decreasing order of move score - const sortedMovePool = movePool.slice(0); - sortedMovePool.sort((a, b) => { - const scoreA = moveScores[movePool.indexOf(a)]; - const scoreB = moveScores[movePool.indexOf(b)]; - return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; - }); - let r = 0; - if (this.aiType === AiType.SMART_RANDOM) { + // Sort the move pool in decreasing order of move score + const sortedMovePool = movePool.slice(0); + sortedMovePool.sort((a, b) => { + const scoreA = moveScores[movePool.indexOf(a)]; + const scoreB = moveScores[movePool.indexOf(b)]; + return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; + }); + let r = 0; + if (this.aiType === AiType.SMART_RANDOM) { // Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll) - while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { - r++; - } - } else if (this.aiType === AiType.SMART) { + while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) { + r++; + } + } else if (this.aiType === AiType.SMART) { // The chance to advance to the next best move increases when the compared moves' scores are closer to each other. - while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 + while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0 && this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) { - r++; + r++; + } } - } - console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct? - return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; + console.log(movePool.map(m => m!.getName()), moveScores, r, sortedMovePool.map(m => m!.getName())); // TODO: are those bangs correct? + return { move: sortedMovePool[r]!.moveId, targets: moveTargets[sortedMovePool[r]!.moveId] }; } } @@ -4845,10 +4845,10 @@ export class EnemyPokemon extends Pokemon { } switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.formIndex && this.bossSegmentIndex < 1) { - damage = Math.min(damage, this.hp - 1); - } + case BattleSpec.FINAL_BOSS: + if (!this.formIndex && this.bossSegmentIndex < 1) { + damage = Math.min(damage, this.hp - 1); + } } const ret = super.damage(damage, ignoreSegments, preventEndure, ignoreFaintPhase); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 5110787452f..b77a156f401 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -65,16 +65,16 @@ export default class Trainer extends Phaser.GameObjects.Container { } switch (this.variant) { - case TrainerVariant.FEMALE: - if (!this.config.hasGenders) { - variant = TrainerVariant.DEFAULT; - } - break; - case TrainerVariant.DOUBLE: - if (!this.config.hasDouble) { - variant = TrainerVariant.DEFAULT; - } - break; + case TrainerVariant.FEMALE: + if (!this.config.hasGenders) { + variant = TrainerVariant.DEFAULT; + } + break; + case TrainerVariant.DOUBLE: + if (!this.config.hasDouble) { + variant = TrainerVariant.DEFAULT; + } + break; } console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]); @@ -229,21 +229,21 @@ export default class Trainer extends Phaser.GameObjects.Container { const strength = partyTemplate.getStrength(i); switch (strength) { - case PartyMemberStrength.WEAKER: - multiplier = 0.95; - break; - case PartyMemberStrength.WEAK: - multiplier = 1.0; - break; - case PartyMemberStrength.AVERAGE: - multiplier = 1.1; - break; - case PartyMemberStrength.STRONG: - multiplier = 1.2; - break; - case PartyMemberStrength.STRONGER: - multiplier = 1.25; - break; + case PartyMemberStrength.WEAKER: + multiplier = 0.95; + break; + case PartyMemberStrength.WEAK: + multiplier = 1.0; + break; + case PartyMemberStrength.AVERAGE: + multiplier = 1.1; + break; + case PartyMemberStrength.STRONG: + multiplier = 1.2; + break; + case PartyMemberStrength.STRONGER: + multiplier = 1.25; + break; } let levelOffset = 0; @@ -515,19 +515,19 @@ export default class Trainer extends Phaser.GameObjects.Container { getPartyMemberModifierChanceMultiplier(index: integer): number { switch (this.getPartyTemplate().getStrength(index)) { - case PartyMemberStrength.WEAKER: - return 0.75; - case PartyMemberStrength.WEAK: - return 0.675; - case PartyMemberStrength.AVERAGE: - return 0.5625; - case PartyMemberStrength.STRONG: - return 0.45; - case PartyMemberStrength.STRONGER: - return 0.375; - default: - console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); - return 0; + case PartyMemberStrength.WEAKER: + return 0.75; + case PartyMemberStrength.WEAK: + return 0.675; + case PartyMemberStrength.AVERAGE: + return 0.5625; + case PartyMemberStrength.STRONG: + return 0.45; + case PartyMemberStrength.STRONGER: + return 0.375; + default: + console.warn("getPartyMemberModifierChanceMultiplier not defined. Using default 0"); + return 0; } } diff --git a/src/game-mode.ts b/src/game-mode.ts index 91e8933ea6e..8f1bb9356e6 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -92,10 +92,10 @@ export class GameMode implements GameModeConfig { return Overrides.STARTING_LEVEL_OVERRIDE; } switch (this.modeId) { - case GameModes.DAILY: - return 20; - default: - return 5; + case GameModes.DAILY: + return 20; + default: + return 5; } } @@ -117,19 +117,19 @@ export class GameMode implements GameModeConfig { */ getStartingBiome(scene: BattleScene): Biome { switch (this.modeId) { - case GameModes.DAILY: - return scene.generateRandomBiome(this.getWaveForDifficulty(1)); - default: - return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; + case GameModes.DAILY: + return scene.generateRandomBiome(this.getWaveForDifficulty(1)); + default: + return Overrides.STARTING_BIOME_OVERRIDE || Biome.TOWN; } } getWaveForDifficulty(waveIndex: integer, ignoreCurveChanges: boolean = false): integer { switch (this.modeId) { - case GameModes.DAILY: - return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); - default: - return waveIndex; + case GameModes.DAILY: + return waveIndex + 30 + (!ignoreCurveChanges ? Math.floor(waveIndex / 5) : 0); + default: + return waveIndex; } } @@ -186,10 +186,10 @@ export class GameMode implements GameModeConfig { isTrainerBoss(waveIndex: integer, biomeType: Biome, offsetGym: boolean): boolean { switch (this.modeId) { - case GameModes.DAILY: - return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); - default: - return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex)); + case GameModes.DAILY: + return waveIndex > 10 && waveIndex < 50 && !(waveIndex % 10); + default: + return (waveIndex % 30) === (offsetGym ? 0 : 20) && (biomeType !== Biome.END || this.isClassic || this.isWaveFinal(waveIndex)); } } @@ -211,14 +211,14 @@ export class GameMode implements GameModeConfig { */ isWaveFinal(waveIndex: integer, modeId: GameModes = this.modeId): boolean { switch (modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - return waveIndex === 200; - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - return !(waveIndex % 250); - case GameModes.DAILY: - return waveIndex === 50; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + return waveIndex === 200; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + return !(waveIndex % 250); + case GameModes.DAILY: + return waveIndex === 50; } } @@ -287,40 +287,40 @@ export class GameMode implements GameModeConfig { getClearScoreBonus(): integer { switch (this.modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - return 5000; - case GameModes.DAILY: - return 2500; - default: - return 0; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + return 5000; + case GameModes.DAILY: + return 2500; + default: + return 0; } } getEnemyModifierChance(isBoss: boolean): integer { switch (this.modeId) { - case GameModes.CLASSIC: - case GameModes.CHALLENGE: - case GameModes.DAILY: - return !isBoss ? 18 : 6; - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - return !isBoss ? 12 : 4; + case GameModes.CLASSIC: + case GameModes.CHALLENGE: + case GameModes.DAILY: + return !isBoss ? 18 : 6; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + return !isBoss ? 12 : 4; } } getName(): string { switch (this.modeId) { - case GameModes.CLASSIC: - return i18next.t("gameMode:classic"); - case GameModes.ENDLESS: - return i18next.t("gameMode:endless"); - case GameModes.SPLICED_ENDLESS: - return i18next.t("gameMode:endlessSpliced"); - case GameModes.DAILY: - return i18next.t("gameMode:dailyRun"); - case GameModes.CHALLENGE: - return i18next.t("gameMode:challenge"); + case GameModes.CLASSIC: + return i18next.t("gameMode:classic"); + case GameModes.ENDLESS: + return i18next.t("gameMode:endless"); + case GameModes.SPLICED_ENDLESS: + return i18next.t("gameMode:endlessSpliced"); + case GameModes.DAILY: + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); } } @@ -329,42 +329,42 @@ export class GameMode implements GameModeConfig { */ getMysteryEncounterLegalWaves(): [number, number] { switch (this.modeId) { - default: - return [ 0, 0 ]; - case GameModes.CLASSIC: - return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES; - case GameModes.CHALLENGE: - return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES; + default: + return [ 0, 0 ]; + case GameModes.CLASSIC: + return CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES; + case GameModes.CHALLENGE: + return CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES; } } static getModeName(modeId: GameModes): string { switch (modeId) { - case GameModes.CLASSIC: - return i18next.t("gameMode:classic"); - case GameModes.ENDLESS: - return i18next.t("gameMode:endless"); - case GameModes.SPLICED_ENDLESS: - return i18next.t("gameMode:endlessSpliced"); - case GameModes.DAILY: - return i18next.t("gameMode:dailyRun"); - case GameModes.CHALLENGE: - return i18next.t("gameMode:challenge"); + case GameModes.CLASSIC: + return i18next.t("gameMode:classic"); + case GameModes.ENDLESS: + return i18next.t("gameMode:endless"); + case GameModes.SPLICED_ENDLESS: + return i18next.t("gameMode:endlessSpliced"); + case GameModes.DAILY: + return i18next.t("gameMode:dailyRun"); + case GameModes.CHALLENGE: + return i18next.t("gameMode:challenge"); } } } export function getGameMode(gameMode: GameModes): GameMode { switch (gameMode) { - case GameModes.CLASSIC: - return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles); - case GameModes.ENDLESS: - return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); - case GameModes.SPLICED_ENDLESS: - return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); - case GameModes.DAILY: - return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); - case GameModes.CHALLENGE: - return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles); + case GameModes.CLASSIC: + return new GameMode(GameModes.CLASSIC, { isClassic: true, hasTrainers: true, hasMysteryEncounters: true }, classicFixedBattles); + case GameModes.ENDLESS: + return new GameMode(GameModes.ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true }); + case GameModes.SPLICED_ENDLESS: + return new GameMode(GameModes.SPLICED_ENDLESS, { isEndless: true, hasShortBiomes: true, hasRandomBosses: true, isSplicedOnly: true }); + case GameModes.DAILY: + return new GameMode(GameModes.DAILY, { isDaily: true, hasTrainers: true, hasNoShop: true }); + case GameModes.CHALLENGE: + return new GameMode(GameModes.CHALLENGE, { isClassic: true, hasTrainers: true, isChallenge: true, hasMysteryEncounters: true }, classicFixedBattles); } } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index d6512486d0e..4f673fd2cfc 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -477,18 +477,18 @@ export class LoadingScene extends SceneBase { this.load.on(this.LOAD_EVENTS.FILE_COMPLETE, (key: string) => { assetText.setText(i18next.t("menu:loadingAsset", { assetName: key })); switch (key) { - case "loading_bg": - bg.setTexture("loading_bg"); - if (mobile) { - bg.setVisible(true); - } - break; - case "logo": - logo.setTexture("logo"); - if (mobile) { - logo.setVisible(true); - } - break; + case "loading_bg": + bg.setTexture("loading_bg"); + if (mobile) { + bg.setVisible(true); + } + break; + case "logo": + logo.setTexture("logo"); + if (mobile) { + logo.setVisible(true); + } + break; } }); diff --git a/src/messages.ts b/src/messages.ts index 1cd6a5966b6..91f550918e5 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -13,21 +13,21 @@ export function getPokemonNameWithAffix(pokemon: Pokemon | undefined): string { } switch (pokemon.scene.currentBattle.battleSpec) { - case BattleSpec.DEFAULT: - return !pokemon.isPlayer() - ? pokemon.hasTrainer() - ? i18next.t("battle:foePokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), - }) - : i18next.t("battle:wildPokemonWithAffix", { - pokemonName: pokemon.getNameToRender(), - }) - : pokemon.getNameToRender(); - case BattleSpec.FINAL_BOSS: - return !pokemon.isPlayer() - ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) - : pokemon.getNameToRender(); - default: - return pokemon.getNameToRender(); + case BattleSpec.DEFAULT: + return !pokemon.isPlayer() + ? pokemon.hasTrainer() + ? i18next.t("battle:foePokemonWithAffix", { + pokemonName: pokemon.getNameToRender(), + }) + : i18next.t("battle:wildPokemonWithAffix", { + pokemonName: pokemon.getNameToRender(), + }) + : pokemon.getNameToRender(); + case BattleSpec.FINAL_BOSS: + return !pokemon.isPlayer() + ? i18next.t("battle:foePokemonWithAffix", { pokemonName: pokemon.getNameToRender() }) + : pokemon.getNameToRender(); + default: + return pokemon.getNameToRender(); } } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 32173a6fead..674c5f47c88 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -78,18 +78,18 @@ export class ModifierType { } let poolTypes: ModifierPoolType[]; switch (poolType) { - case ModifierPoolType.PLAYER: - poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ]; - break; - case ModifierPoolType.WILD: - poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ]; - break; - case ModifierPoolType.TRAINER: - poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ]; - break; - default: - poolTypes = [ poolType ]; - break; + case ModifierPoolType.PLAYER: + poolTypes = [ poolType, ModifierPoolType.TRAINER, ModifierPoolType.WILD ]; + break; + case ModifierPoolType.WILD: + poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.TRAINER ]; + break; + case ModifierPoolType.TRAINER: + poolTypes = [ poolType, ModifierPoolType.PLAYER, ModifierPoolType.WILD ]; + break; + default: + poolTypes = [ poolType ]; + break; } // Try multiple pool types in case of stolen items for (const type of poolTypes) { @@ -502,42 +502,42 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge function getAttackTypeBoosterItemName(type: Type) { switch (type) { - case Type.NORMAL: - return "Silk Scarf"; - case Type.FIGHTING: - return "Black Belt"; - case Type.FLYING: - return "Sharp Beak"; - case Type.POISON: - return "Poison Barb"; - case Type.GROUND: - return "Soft Sand"; - case Type.ROCK: - return "Hard Stone"; - case Type.BUG: - return "Silver Powder"; - case Type.GHOST: - return "Spell Tag"; - case Type.STEEL: - return "Metal Coat"; - case Type.FIRE: - return "Charcoal"; - case Type.WATER: - return "Mystic Water"; - case Type.GRASS: - return "Miracle Seed"; - case Type.ELECTRIC: - return "Magnet"; - case Type.PSYCHIC: - return "Twisted Spoon"; - case Type.ICE: - return "Never-Melt Ice"; - case Type.DRAGON: - return "Dragon Fang"; - case Type.DARK: - return "Black Glasses"; - case Type.FAIRY: - return "Fairy Feather"; + case Type.NORMAL: + return "Silk Scarf"; + case Type.FIGHTING: + return "Black Belt"; + case Type.FLYING: + return "Sharp Beak"; + case Type.POISON: + return "Poison Barb"; + case Type.GROUND: + return "Soft Sand"; + case Type.ROCK: + return "Hard Stone"; + case Type.BUG: + return "Silver Powder"; + case Type.GHOST: + return "Spell Tag"; + case Type.STEEL: + return "Metal Coat"; + case Type.FIRE: + return "Charcoal"; + case Type.WATER: + return "Mystic Water"; + case Type.GRASS: + return "Miracle Seed"; + case Type.ELECTRIC: + return "Magnet"; + case Type.PSYCHIC: + return "Twisted Spoon"; + case Type.ICE: + return "Never-Melt Ice"; + case Type.DRAGON: + return "Dragon Fang"; + case Type.DARK: + return "Black Glasses"; + case Type.FAIRY: + return "Fairy Feather"; } } @@ -1149,15 +1149,15 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { foundN_SOLAR = false; formChangeItemTriggers.forEach((fc, i) => { switch (fc.item) { - case FormChangeItem.ULTRANECROZIUM_Z: - foundULTRA_Z = true; - break; - case FormChangeItem.N_LUNARIZER: - foundN_LUNA = true; - break; - case FormChangeItem.N_SOLARIZER: - foundN_SOLAR = true; - break; + case FormChangeItem.ULTRANECROZIUM_Z: + foundULTRA_Z = true; + break; + case FormChangeItem.N_LUNARIZER: + foundN_LUNA = true; + break; + case FormChangeItem.N_SOLARIZER: + foundN_SOLAR = true; + break; } }); if (foundULTRA_Z && foundN_LUNA && foundN_SOLAR) { @@ -1975,21 +1975,21 @@ let enemyBuffIgnoredPoolIndexes = {}; // eslint-disable-line @typescript-eslint/ export function getModifierPoolForType(poolType: ModifierPoolType): ModifierPool { let pool: ModifierPool; switch (poolType) { - case ModifierPoolType.PLAYER: - pool = modifierPool; - break; - case ModifierPoolType.WILD: - pool = wildModifierPool; - break; - case ModifierPoolType.TRAINER: - pool = trainerModifierPool; - break; - case ModifierPoolType.ENEMY_BUFF: - pool = enemyBuffModifierPool; - break; - case ModifierPoolType.DAILY_STARTER: - pool = dailyStarterModifierPool; - break; + case ModifierPoolType.PLAYER: + pool = modifierPool; + break; + case ModifierPoolType.WILD: + pool = wildModifierPool; + break; + case ModifierPoolType.TRAINER: + pool = trainerModifierPool; + break; + case ModifierPoolType.ENEMY_BUFF: + pool = enemyBuffModifierPool; + break; + case ModifierPoolType.DAILY_STARTER: + pool = dailyStarterModifierPool; + break; } return pool; } @@ -2060,23 +2060,23 @@ export function regenerateModifierPoolThresholds(party: Pokemon[], poolType: Mod console.table(modifierTableData); } switch (poolType) { - case ModifierPoolType.PLAYER: - modifierPoolThresholds = thresholds; - ignoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.WILD: - case ModifierPoolType.TRAINER: - enemyModifierPoolThresholds = thresholds; - enemyIgnoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.ENEMY_BUFF: - enemyBuffModifierPoolThresholds = thresholds; - enemyBuffIgnoredPoolIndexes = ignoredIndexes; - break; - case ModifierPoolType.DAILY_STARTER: - dailyStarterModifierPoolThresholds = thresholds; - ignoredDailyStarterPoolIndexes = ignoredIndexes; - break; + case ModifierPoolType.PLAYER: + modifierPoolThresholds = thresholds; + ignoredPoolIndexes = ignoredIndexes; + break; + case ModifierPoolType.WILD: + case ModifierPoolType.TRAINER: + enemyModifierPoolThresholds = thresholds; + enemyIgnoredPoolIndexes = ignoredIndexes; + break; + case ModifierPoolType.ENEMY_BUFF: + enemyBuffModifierPoolThresholds = thresholds; + enemyBuffIgnoredPoolIndexes = ignoredIndexes; + break; + case ModifierPoolType.DAILY_STARTER: + dailyStarterModifierPoolThresholds = thresholds; + ignoredDailyStarterPoolIndexes = ignoredIndexes; + break; } } @@ -2247,15 +2247,15 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: PersistentModifier[], scene: BattleScene): EnemyPersistentModifier { let tierStackCount: number; switch (tier) { - case ModifierTier.ULTRA: - tierStackCount = 5; - break; - case ModifierTier.GREAT: - tierStackCount = 3; - break; - default: - tierStackCount = 1; - break; + case ModifierTier.ULTRA: + tierStackCount = 5; + break; + case ModifierTier.GREAT: + tierStackCount = 3; + break; + default: + tierStackCount = 1; + break; } const retryCount = 50; @@ -2321,21 +2321,21 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, const pool = getModifierPoolForType(poolType); let thresholds: object; switch (poolType) { - case ModifierPoolType.PLAYER: - thresholds = modifierPoolThresholds; - break; - case ModifierPoolType.WILD: - thresholds = enemyModifierPoolThresholds; - break; - case ModifierPoolType.TRAINER: - thresholds = enemyModifierPoolThresholds; - break; - case ModifierPoolType.ENEMY_BUFF: - thresholds = enemyBuffModifierPoolThresholds; - break; - case ModifierPoolType.DAILY_STARTER: - thresholds = dailyStarterModifierPoolThresholds; - break; + case ModifierPoolType.PLAYER: + thresholds = modifierPoolThresholds; + break; + case ModifierPoolType.WILD: + thresholds = enemyModifierPoolThresholds; + break; + case ModifierPoolType.TRAINER: + thresholds = enemyModifierPoolThresholds; + break; + case ModifierPoolType.ENEMY_BUFF: + thresholds = enemyBuffModifierPoolThresholds; + break; + case ModifierPoolType.DAILY_STARTER: + thresholds = dailyStarterModifierPoolThresholds; + break; } if (tier === undefined) { const tierValue = randSeedInt(1024); diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 35d1a304461..2cd96496f45 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1695,12 +1695,12 @@ export class TurnStatusEffectModifier extends PokemonHeldItemModifier { super(type, pokemonId, stackCount); switch (type.id) { - case "TOXIC_ORB": - this.effect = StatusEffect.TOXIC; - break; - case "FLAME_ORB": - this.effect = StatusEffect.BURN; - break; + case "TOXIC_ORB": + this.effect = StatusEffect.TOXIC; + break; + case "FLAME_ORB": + this.effect = StatusEffect.BURN; + break; } } @@ -2684,15 +2684,15 @@ export class PokemonMultiHitModifier extends PokemonHeldItemModifier { count.value *= (this.getStackCount() + 1); switch (this.getStackCount()) { - case 1: - power.value *= 0.4; - break; - case 2: - power.value *= 0.25; - break; - case 3: - power.value *= 0.175; - break; + case 1: + power.value *= 0.4; + break; + case 2: + power.value *= 0.25; + break; + case 3: + power.value *= 0.175; + break; } return true; diff --git a/src/phases/command-phase.ts b/src/phases/command-phase.ts index cf66631bd96..e6f2eb69ff3 100644 --- a/src/phases/command-phase.ts +++ b/src/phases/command-phase.ts @@ -84,170 +84,170 @@ export class CommandPhase extends FieldPhase { let success: boolean; switch (command) { - case Command.FIGHT: - let useStruggle = false; - if (cursor === -1 || + case Command.FIGHT: + let useStruggle = false; + if (cursor === -1 || playerPokemon.trySelectMove(cursor, args[0] as boolean) || (useStruggle = cursor > -1 && !playerPokemon.getMoveset().filter(m => m?.isUsable(playerPokemon)).length)) { - const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? - const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; - const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; - if (!moveId) { - turnCommand.targets = [ this.fieldIndex ]; - } - console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); - if (moveTargets.targets.length > 1 && moveTargets.multiple) { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - if (moveTargets.targets.length <= 1 || moveTargets.multiple) { + const moveId = !useStruggle ? cursor > -1 ? playerPokemon.getMoveset()[cursor]!.moveId : Moves.NONE : Moves.STRUGGLE; // TODO: is the bang correct? + const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; + const moveTargets: MoveTargetSet = args.length < 3 ? getMoveTargets(playerPokemon, moveId) : args[2]; + if (!moveId) { + turnCommand.targets = [ this.fieldIndex ]; + } + console.log(moveTargets, getPokemonNameWithAffix(playerPokemon)); + if (moveTargets.targets.length > 1 && moveTargets.multiple) { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + if (moveTargets.targets.length <= 1 || moveTargets.multiple) { turnCommand.move!.targets = moveTargets.targets; //TODO: is the bang correct here? - } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { + } else if (playerPokemon.getTag(BattlerTagType.CHARGING) && playerPokemon.getMoveQueue().length >= 1) { turnCommand.move!.targets = playerPokemon.getMoveQueue()[0].targets; //TODO: is the bang correct here? - } else { - this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); - } - this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; - success = true; - } else if (cursor < playerPokemon.getMoveset().length) { - const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? - this.scene.ui.setMode(Mode.MESSAGE); + } else { + this.scene.unshiftPhase(new SelectTargetPhase(this.scene, this.fieldIndex)); + } + this.scene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; + success = true; + } else if (cursor < playerPokemon.getMoveset().length) { + const move = playerPokemon.getMoveset()[cursor]!; //TODO: is this bang correct? + this.scene.ui.setMode(Mode.MESSAGE); - // Decides between a Disabled, Not Implemented, or No PP translation message - const errorMessage = + // Decides between a Disabled, Not Implemented, or No PP translation message + const errorMessage = playerPokemon.isMoveRestricted(move.moveId, playerPokemon) ? playerPokemon.getRestrictingTag(move.moveId, playerPokemon)!.selectionDeniedText(playerPokemon, move.moveId) : move.getName().endsWith(" (N)") ? "battle:moveNotImplemented" : "battle:moveNoPP"; - const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator + const moveName = move.getName().replace(" (N)", ""); // Trims off the indicator - this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); - }, null, true); - } - break; - case Command.BALL: - const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); - if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); - if (targets.length > 1) { + this.scene.ui.showText(i18next.t(errorMessage, { moveName: moveName }), null, () => { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.FIGHT, this.fieldIndex); + }, null, true); + } + break; + case Command.BALL: + const notInDex = (this.scene.getEnemyField().filter(p => p.isActive(true)).some(p => !p.scene.gameData.dexData[p.species.speciesId].caughtAttr) && this.scene.gameData.getStarterCount(d => !!d.caughtAttr) < Object.keys(speciesStarterCosts).length - 1); + if (this.scene.arena.biomeType === Biome.END && (!this.scene.gameMode.isClassic || this.scene.gameMode.isFreshStartChallenge() || notInDex )) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { + this.scene.ui.showText(i18next.t("battle:noPokeballForce"), null, () => { this.scene.ui.showText("", 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else if (cursor < 5) { - const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); - if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (this.scene.currentBattle.isBattleMysteryEncounter() && !this.scene.currentBattle.mysteryEncounter!.catchAllowed) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballMysteryEncounter"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + const targets = this.scene.getEnemyField().filter(p => p.isActive(true)).map(p => p.getBattlerIndex()); + if (targets.length > 1) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText(i18next.t("battle:noPokeballMulti"), null, () => { this.scene.ui.showText("", 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else { - this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; + } else if (cursor < 5) { + const targetPokemon = this.scene.getEnemyField().find(p => p.isActive(true)); + if (targetPokemon?.isBoss() && targetPokemon?.bossSegmentIndex >= 1 && !targetPokemon?.hasAbility(Abilities.WONDER_GUARD, false, true) && cursor < PokeballType.MASTER_BALL) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noPokeballStrong"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else { + this.scene.currentBattle.turnCommands[this.fieldIndex] = { command: Command.BALL, cursor: cursor }; this.scene.currentBattle.turnCommands[this.fieldIndex]!.targets = targets; if (this.fieldIndex) { this.scene.currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; } success = true; + } } } - } - break; - case Command.POKEMON: - case Command.RUN: - const isSwitch = command === Command.POKEMON; - const { currentBattle, arena } = this.scene; - const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; - if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { - this.scene.ui.showText("", 0); + break; + case Command.POKEMON: + case Command.RUN: + const isSwitch = command === Command.POKEMON; + const { currentBattle, arena } = this.scene; + const mysteryEncounterFleeAllowed = currentBattle.mysteryEncounter?.fleeAllowed; + if (!isSwitch && (arena.biomeType === Biome.END || (!isNullOrUndefined(mysteryEncounterFleeAllowed) && !mysteryEncounterFleeAllowed))) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { - this.scene.ui.showText("", 0); - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - }, null, true); - } else { - const batonPass = isSwitch && args[0] as boolean; - const trappedAbMessages: string[] = []; - if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) { - currentBattle.turnCommands[this.fieldIndex] = isSwitch - ? { command: Command.POKEMON, cursor: cursor, args: args } - : { command: Command.RUN }; - success = true; - if (!isSwitch && this.fieldIndex) { - currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; - } - } else if (trappedAbMessages.length > 0) { - if (!isSwitch) { - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText(trappedAbMessages[0], null, () => { + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeForce"), null, () => { this.scene.ui.showText("", 0); - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - } + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + }, null, true); + } else if (!isSwitch && (currentBattle.battleType === BattleType.TRAINER || currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { + this.scene.ui.showText("", 0); + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); } else { - const trapTag = playerPokemon.getTag(TrappedTag); - - // trapTag should be defined at this point, but just in case... - if (!trapTag) { + const batonPass = isSwitch && args[0] as boolean; + const trappedAbMessages: string[] = []; + if (batonPass || !playerPokemon.isTrapped(trappedAbMessages)) { currentBattle.turnCommands[this.fieldIndex] = isSwitch ? { command: Command.POKEMON, cursor: cursor, args: args } : { command: Command.RUN }; - break; - } - - if (!isSwitch) { - this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); - this.scene.ui.setMode(Mode.MESSAGE); - } - this.scene.ui.showText( - i18next.t("battle:noEscapePokemon", { - pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", - moveName: trapTag.getMoveName(), - escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") - }), - null, - () => { + success = true; + if (!isSwitch && this.fieldIndex) { + currentBattle.turnCommands[this.fieldIndex - 1]!.skip = true; + } + } else if (trappedAbMessages.length > 0) { + if (!isSwitch) { + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText(trappedAbMessages[0], null, () => { this.scene.ui.showText("", 0); if (!isSwitch) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); } }, null, true); + } else { + const trapTag = playerPokemon.getTag(TrappedTag); + + // trapTag should be defined at this point, but just in case... + if (!trapTag) { + currentBattle.turnCommands[this.fieldIndex] = isSwitch + ? { command: Command.POKEMON, cursor: cursor, args: args } + : { command: Command.RUN }; + break; + } + + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + this.scene.ui.setMode(Mode.MESSAGE); + } + this.scene.ui.showText( + i18next.t("battle:noEscapePokemon", { + pokemonName: trapTag.sourceId && this.scene.getPokemonById(trapTag.sourceId) ? getPokemonNameWithAffix(this.scene.getPokemonById(trapTag.sourceId)!) : "", + moveName: trapTag.getMoveName(), + escapeVerb: isSwitch ? i18next.t("battle:escapeVerbSwitch") : i18next.t("battle:escapeVerbFlee") + }), + null, + () => { + this.scene.ui.showText("", 0); + if (!isSwitch) { + this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); + } + }, null, true); + } } - } - break; + break; } if (success!) { // TODO: is the bang correct? diff --git a/src/phases/damage-phase.ts b/src/phases/damage-phase.ts index 66b11512729..44e3dfd4182 100644 --- a/src/phases/damage-phase.ts +++ b/src/phases/damage-phase.ts @@ -41,16 +41,16 @@ export class DamagePhase extends PokemonPhase { applyDamage() { switch (this.damageResult) { - case HitResult.EFFECTIVE: - this.scene.playSound("se/hit"); - break; - case HitResult.SUPER_EFFECTIVE: - case HitResult.ONE_HIT_KO: - this.scene.playSound("se/hit_strong"); - break; - case HitResult.NOT_VERY_EFFECTIVE: - this.scene.playSound("se/hit_weak"); - break; + case HitResult.EFFECTIVE: + this.scene.playSound("se/hit"); + break; + case HitResult.SUPER_EFFECTIVE: + case HitResult.ONE_HIT_KO: + this.scene.playSound("se/hit_strong"); + break; + case HitResult.NOT_VERY_EFFECTIVE: + this.scene.playSound("se/hit_weak"); + break; } if (this.amount) { diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 3610ffed386..84f07f3ee94 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -494,31 +494,31 @@ export class EncounterPhase extends BattlePhase { tryOverrideForBattleSpec(): boolean { switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - const enemy = this.scene.getEnemyPokemon(); - this.scene.ui.showText(this.getEncounterMessage(), null, () => { - const localizationKey = "battleSpecDialogue:encounter"; - if (this.scene.ui.shouldSkipDialogue(localizationKey)) { + case BattleSpec.FINAL_BOSS: + const enemy = this.scene.getEnemyPokemon(); + this.scene.ui.showText(this.getEncounterMessage(), null, () => { + const localizationKey = "battleSpecDialogue:encounter"; + if (this.scene.ui.shouldSkipDialogue(localizationKey)) { // Logging mirrors logging found in dialogue-ui-handler - console.log(`Dialogue ${localizationKey} skipped`); - this.doEncounterCommon(false); - } else { - const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; - // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not. - const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : ""; - const cycleCount = count.toLocaleString() + ordinalUsed; - const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; - const genderStr = PlayerGender[genderIndex].toLowerCase(); - const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount }); - if (!this.scene.gameData.getSeenDialogues()[localizationKey]) { - this.scene.gameData.saveSeenDialogue(localizationKey); - } - this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + console.log(`Dialogue ${localizationKey} skipped`); this.doEncounterCommon(false); - }); - } - }, 1500, true); - return true; + } else { + const count = 5643853 + this.scene.gameData.gameStats.classicSessionsPlayed; + // The line below checks if an English ordinal is necessary or not based on whether an entry for encounterLocalizationKey exists in the language or not. + const ordinalUsed = !i18next.exists(localizationKey, { fallbackLng: []}) || i18next.resolvedLanguage === "en" ? i18next.t("battleSpecDialogue:key", { count: count, ordinal: true }) : ""; + const cycleCount = count.toLocaleString() + ordinalUsed; + const genderIndex = this.scene.gameData.gender ?? PlayerGender.UNSET; + const genderStr = PlayerGender[genderIndex].toLowerCase(); + const encounterDialogue = i18next.t(localizationKey, { context: genderStr, cycleCount: cycleCount }); + if (!this.scene.gameData.getSeenDialogues()[localizationKey]) { + this.scene.gameData.saveSeenDialogue(localizationKey); + } + this.scene.ui.showDialogue(encounterDialogue, enemy?.species.name, null, () => { + this.doEncounterCommon(false); + }); + } + }, 1500, true); + return true; } return false; } diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 95105337f60..eee1fd52938 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -179,19 +179,19 @@ export class FaintPhase extends PokemonPhase { tryOverrideForBattleSpec(): boolean { switch (this.scene.currentBattle.battleSpec) { - case BattleSpec.FINAL_BOSS: - if (!this.player) { - const enemy = this.getPokemon(); - if (enemy.formIndex) { - this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); - } else { + case BattleSpec.FINAL_BOSS: + if (!this.player) { + const enemy = this.getPokemon(); + if (enemy.formIndex) { + this.scene.ui.showDialogue(battleSpecDialogue[BattleSpec.FINAL_BOSS].secondStageWin, enemy.species.name, null, () => this.doFaint()); + } else { // Final boss' HP threshold has been bypassed; cancel faint and force check for 2nd phase - enemy.hp++; - this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); - this.end(); + enemy.hp++; + this.scene.unshiftPhase(new DamagePhase(this.scene, enemy.getBattlerIndex(), 0, HitResult.OTHER)); + this.end(); + } + return true; } - return true; - } } return false; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index f50cfbd78ac..0af61918636 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -167,23 +167,23 @@ export class MovePhase extends BattlePhase { let healed = false; switch (this.pokemon.status.effect) { - case StatusEffect.PARALYSIS: - if (!this.pokemon.randSeedInt(4)) { - activated = true; - this.cancelled = true; - } - break; - case StatusEffect.SLEEP: - applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); - healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; - activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); - this.cancelled = activated; - break; - case StatusEffect.FREEZE: - healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); - activated = !healed; - this.cancelled = activated; - break; + case StatusEffect.PARALYSIS: + if (!this.pokemon.randSeedInt(4)) { + activated = true; + this.cancelled = true; + } + break; + case StatusEffect.SLEEP: + applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); + healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn; + activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP); + this.cancelled = activated; + break; + case StatusEffect.FREEZE: + healed = !!this.move.getMove().findAttr(attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE)) || !this.pokemon.randSeedInt(5); + activated = !healed; + this.cancelled = activated; + break; } if (activated) { diff --git a/src/phases/pokemon-anim-phase.ts b/src/phases/pokemon-anim-phase.ts index 6f1bfe8bc18..26ae11d1026 100644 --- a/src/phases/pokemon-anim-phase.ts +++ b/src/phases/pokemon-anim-phase.ts @@ -25,20 +25,20 @@ export class PokemonAnimPhase extends BattlePhase { super.start(); switch (this.key) { - case PokemonAnimType.SUBSTITUTE_ADD: - this.doSubstituteAddAnim(); - break; - case PokemonAnimType.SUBSTITUTE_PRE_MOVE: - this.doSubstitutePreMoveAnim(); - break; - case PokemonAnimType.SUBSTITUTE_POST_MOVE: - this.doSubstitutePostMoveAnim(); - break; - case PokemonAnimType.SUBSTITUTE_REMOVE: - this.doSubstituteRemoveAnim(); - break; - default: - this.end(); + case PokemonAnimType.SUBSTITUTE_ADD: + this.doSubstituteAddAnim(); + break; + case PokemonAnimType.SUBSTITUTE_PRE_MOVE: + this.doSubstitutePreMoveAnim(); + break; + case PokemonAnimType.SUBSTITUTE_POST_MOVE: + this.doSubstitutePostMoveAnim(); + break; + case PokemonAnimType.SUBSTITUTE_REMOVE: + this.doSubstituteRemoveAnim(); + break; + default: + this.end(); } } diff --git a/src/phases/post-turn-status-effect-phase.ts b/src/phases/post-turn-status-effect-phase.ts index 285bbddde88..06681b733f0 100644 --- a/src/phases/post-turn-status-effect-phase.ts +++ b/src/phases/post-turn-status-effect-phase.ts @@ -26,16 +26,16 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { this.scene.queueMessage(getStatusEffectActivationText(pokemon.status.effect, getPokemonNameWithAffix(pokemon))); const damage = new Utils.NumberHolder(0); switch (pokemon.status.effect) { - case StatusEffect.POISON: - damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); - break; - case StatusEffect.TOXIC: - damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); - break; - case StatusEffect.BURN: - damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); - applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); - break; + case StatusEffect.POISON: + damage.value = Math.max(pokemon.getMaxHp() >> 3, 1); + break; + case StatusEffect.TOXIC: + damage.value = Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1); + break; + case StatusEffect.BURN: + damage.value = Math.max(pokemon.getMaxHp() >> 4, 1); + applyAbAttrs(ReduceBurnDamageAbAttr, pokemon, null, false, damage); + break; } if (damage.value) { // Set preventEndure flag to avoid pokemon surviving thanks to focus band, sturdy, endure ... diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index f9b3e978923..38d5cfb4a10 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -77,78 +77,78 @@ export class SelectModifierPhase extends BattlePhase { let cost: integer; const rerollCost = this.getRerollCost(this.scene.lockModifierTiers); switch (rowCursor) { - case 0: - switch (cursor) { case 0: - if (rerollCost < 0 || this.scene.money < rerollCost) { - this.scene.ui.playError(); - return false; - } else { - this.scene.reroll = true; - 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) { - this.scene.money -= rerollCost; - this.scene.updateMoneyText(); - this.scene.animateMoneyChanged(false); - } - this.scene.playSound("se/buy"); - } - break; - case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { - if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { - const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + switch (cursor) { + case 0: + if (rerollCost < 0 || this.scene.money < rerollCost) { + this.scene.ui.playError(); + return false; + } else { + this.scene.reroll = true; + 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) { + this.scene.money -= rerollCost; + this.scene.updateMoneyText(); + this.scene.animateMoneyChanged(false); + } + this.scene.playSound("se/buy"); + } + break; + case 1: + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { + const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; - const itemModifier = itemModifiers[itemIndex]; - this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); - } else { - 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(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); - }); - break; - case 3: - if (rerollCost < 0) { - // Reroll lock button is also disabled when reroll is disabled - this.scene.ui.playError(); - return false; + const itemModifier = itemModifiers[itemIndex]; + this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); + } else { + 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(), this.typeOptions, modifierSelectCallback, this.getRerollCost(this.scene.lockModifierTiers)); + }); + break; + case 3: + if (rerollCost < 0) { + // Reroll lock button is also disabled when reroll is disabled + this.scene.ui.playError(); + return false; + } + this.scene.lockModifierTiers = !this.scene.lockModifierTiers; + const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; + uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); + uiHandler.updateLockRaritiesText(); + uiHandler.updateRerollCostText(); + return false; } - this.scene.lockModifierTiers = !this.scene.lockModifierTiers; - const uiHandler = this.scene.ui.getHandler() as ModifierSelectUiHandler; - uiHandler.setRerollCost(this.getRerollCost(this.scene.lockModifierTiers)); - uiHandler.updateLockRaritiesText(); - uiHandler.updateRerollCostText(); - return false; - } - return true; - case 1: - if (this.typeOptions.length === 0) { - this.scene.ui.clearText(); - this.scene.ui.setMode(Mode.MESSAGE); - super.end(); return true; - } - if (this.typeOptions[cursor].type) { - modifierType = this.typeOptions[cursor].type; - } - break; - default: - const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); - const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; - if (shopOption.type) { - modifierType = shopOption.type; - } - // Apply Black Sludge to healing item cost - const healingItemCost = new NumberHolder(shopOption.cost); - this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); - cost = healingItemCost.value; - break; + case 1: + if (this.typeOptions.length === 0) { + this.scene.ui.clearText(); + this.scene.ui.setMode(Mode.MESSAGE); + super.end(); + return true; + } + if (this.typeOptions[cursor].type) { + modifierType = this.typeOptions[cursor].type; + } + break; + default: + const shopOptions = getPlayerShopModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex, this.scene.getWaveMoneyAmount(1)); + const shopOption = shopOptions[rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT]; + if (shopOption.type) { + modifierType = shopOption.type; + } + // Apply Black Sludge to healing item cost + const healingItemCost = new NumberHolder(shopOption.cost); + this.scene.applyModifier(HealShopCostModifier, true, healingItemCost); + cost = healingItemCost.value; + break; } if (cost! && (this.scene.money < cost) && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) { // TODO: is the bang on cost correct? diff --git a/src/phases/turn-start-phase.ts b/src/phases/turn-start-phase.ts index 25c079007fd..497d449912f 100644 --- a/src/phases/turn-start-phase.ts +++ b/src/phases/turn-start-phase.ts @@ -152,55 +152,55 @@ export class TurnStartPhase extends FieldPhase { } switch (turnCommand?.command) { - case Command.FIGHT: - const queuedMove = turnCommand.move; - pokemon.turnData.order = orderIndex++; - if (!queuedMove) { - continue; - } - const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); - if (move.getMove().hasAttr(MoveHeaderAttr)) { - this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); - } - if (pokemon.isPlayer()) { - if (turnCommand.cursor === -1) { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? - } else { - const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? - this.scene.pushPhase(playerPhase); + case Command.FIGHT: + const queuedMove = turnCommand.move; + pokemon.turnData.order = orderIndex++; + if (!queuedMove) { + continue; } - } else { - this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? - } - break; - case Command.BALL: - this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? - break; - case Command.POKEMON: - const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); - break; - case Command.RUN: - let runningPokemon = pokemon; - if (this.scene.currentBattle.double) { - const playerActivePokemon = field.filter(pokemon => { - if (!!pokemon) { - return pokemon.isPlayer() && pokemon.isActive(); + const move = pokemon.getMoveset().find(m => m?.moveId === queuedMove.move && m?.ppUsed < m?.getMovePp()) || new PokemonMove(queuedMove.move); + if (move.getMove().hasAttr(MoveHeaderAttr)) { + this.scene.unshiftPhase(new MoveHeaderPhase(this.scene, pokemon, move)); + } + if (pokemon.isPlayer()) { + if (turnCommand.cursor === -1) { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move));//TODO: is the bang correct here? } else { - return; + const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP);//TODO: is the bang correct here? + this.scene.pushPhase(playerPhase); } - }); - // if only one pokemon is alive, use that one - if (playerActivePokemon.length > 1) { - // find which active pokemon has faster speed - const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; - // check if either active pokemon has the ability "Run Away" - const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); - runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } else { + this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move!.targets, move, false, queuedMove.ignorePP));//TODO: is the bang correct here? } - } - this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); - break; + break; + case Command.BALL: + this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here? + break; + case Command.POKEMON: + const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer())); + break; + case Command.RUN: + let runningPokemon = pokemon; + if (this.scene.currentBattle.double) { + const playerActivePokemon = field.filter(pokemon => { + if (!!pokemon) { + return pokemon.isPlayer() && pokemon.isActive(); + } else { + return; + } + }); + // if only one pokemon is alive, use that one + if (playerActivePokemon.length > 1) { + // find which active pokemon has faster speed + const fasterPokemon = playerActivePokemon[0].getStat(Stat.SPD) > playerActivePokemon[1].getStat(Stat.SPD) ? playerActivePokemon[0] : playerActivePokemon[1]; + // check if either active pokemon has the ability "Run Away" + const hasRunAway = playerActivePokemon.find(p => p.hasAbility(Abilities.RUN_AWAY)); + runningPokemon = hasRunAway !== undefined ? hasRunAway : fasterPokemon; + } + } + this.scene.unshiftPhase(new AttemptRunPhase(this.scene, runningPokemon.getFieldIndex())); + break; } } diff --git a/src/system/achv.ts b/src/system/achv.ts index 7329dd41fd5..366813328e2 100644 --- a/src/system/achv.ts +++ b/src/system/achv.ts @@ -156,133 +156,133 @@ export function getAchievementDescription(localizationKey: string): string { const genderStr = PlayerGender[genderIndex].toLowerCase(); switch (localizationKey) { - case "10K_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") }); - case "100K_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") }); - case "1M_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") }); - case "10M_MONEY": - return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") }); - case "250_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") }); - case "1000_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") }); - case "2500_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") }); - case "10000_DMG": - return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") }); - case "250_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "1000_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "2500_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "10000_HEAL": - return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); - case "LV_100": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level }); - case "LV_250": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level }); - case "LV_1000": - return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level }); - case "10_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "25_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "50_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "75_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "100_RIBBONS": - return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") }); - case "TRANSFER_MAX_STAT_STAGE": - return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr }); - case "MAX_FRIENDSHIP": - return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr }); - case "MEGA_EVOLVE": - return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr }); - case "GIGANTAMAX": - return i18next.t("achv:GIGANTAMAX.description", { context: genderStr }); - case "TERASTALLIZE": - return i18next.t("achv:TERASTALLIZE.description", { context: genderStr }); - case "STELLAR_TERASTALLIZE": - return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr }); - case "SPLICE": - return i18next.t("achv:SPLICE.description", { context: genderStr }); - case "MINI_BLACK_HOLE": - return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr }); - case "CATCH_MYTHICAL": - return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr }); - case "CATCH_SUB_LEGENDARY": - return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr }); - case "CATCH_LEGENDARY": - return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr }); - case "SEE_SHINY": - return i18next.t("achv:SEE_SHINY.description", { context: genderStr }); - case "SHINY_PARTY": - return i18next.t("achv:SHINY_PARTY.description", { context: genderStr }); - case "HATCH_MYTHICAL": - return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr }); - case "HATCH_SUB_LEGENDARY": - return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr }); - case "HATCH_LEGENDARY": - return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr }); - case "HATCH_SHINY": - return i18next.t("achv:HATCH_SHINY.description", { context: genderStr }); - case "HIDDEN_ABILITY": - return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr }); - case "PERFECT_IVS": - return i18next.t("achv:PERFECT_IVS.description", { context: genderStr }); - case "CLASSIC_VICTORY": - return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr }); - case "UNEVOLVED_CLASSIC_VICTORY": - return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr }); - case "MONO_GEN_ONE": - return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr }); - case "MONO_GEN_TWO": - return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr }); - case "MONO_GEN_THREE": - return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr }); - case "MONO_GEN_FOUR": - return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr }); - case "MONO_GEN_FIVE": - return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr }); - case "MONO_GEN_SIX": - return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr }); - case "MONO_GEN_SEVEN": - return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr }); - case "MONO_GEN_EIGHT": - return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr }); - case "MONO_GEN_NINE": - return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr }); - case "MONO_NORMAL": - case "MONO_FIGHTING": - case "MONO_FLYING": - case "MONO_POISON": - case "MONO_GROUND": - case "MONO_ROCK": - case "MONO_BUG": - case "MONO_GHOST": - case "MONO_STEEL": - case "MONO_FIRE": - case "MONO_WATER": - case "MONO_GRASS": - case "MONO_ELECTRIC": - case "MONO_PSYCHIC": - case "MONO_ICE": - case "MONO_DRAGON": - case "MONO_DARK": - case "MONO_FAIRY": - return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); - case "FRESH_START": - return i18next.t("achv:FRESH_START.description", { context: genderStr }); - case "INVERSE_BATTLE": - return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); - case "BREEDERS_IN_SPACE": - return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); - default: - return ""; + case "10K_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10K_MONEY.moneyAmount.toLocaleString("en-US") }); + case "100K_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._100K_MONEY.moneyAmount.toLocaleString("en-US") }); + case "1M_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._1M_MONEY.moneyAmount.toLocaleString("en-US") }); + case "10M_MONEY": + return i18next.t("achv:MoneyAchv.description", { context: genderStr, "moneyAmount": achvs._10M_MONEY.moneyAmount.toLocaleString("en-US") }); + case "250_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._250_DMG.damageAmount.toLocaleString("en-US") }); + case "1000_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._1000_DMG.damageAmount.toLocaleString("en-US") }); + case "2500_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._2500_DMG.damageAmount.toLocaleString("en-US") }); + case "10000_DMG": + return i18next.t("achv:DamageAchv.description", { context: genderStr, "damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US") }); + case "250_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "1000_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "2500_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "10000_HEAL": + return i18next.t("achv:HealAchv.description", { context: genderStr, "healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) }); + case "LV_100": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_100.level }); + case "LV_250": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_250.level }); + case "LV_1000": + return i18next.t("achv:LevelAchv.description", { context: genderStr, "level": achvs.LV_1000.level }); + case "10_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._10_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "25_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._25_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "50_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._50_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "75_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "100_RIBBONS": + return i18next.t("achv:RibbonAchv.description", { context: genderStr, "ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US") }); + case "TRANSFER_MAX_STAT_STAGE": + return i18next.t("achv:TRANSFER_MAX_STAT_STAGE.description", { context: genderStr }); + case "MAX_FRIENDSHIP": + return i18next.t("achv:MAX_FRIENDSHIP.description", { context: genderStr }); + case "MEGA_EVOLVE": + return i18next.t("achv:MEGA_EVOLVE.description", { context: genderStr }); + case "GIGANTAMAX": + return i18next.t("achv:GIGANTAMAX.description", { context: genderStr }); + case "TERASTALLIZE": + return i18next.t("achv:TERASTALLIZE.description", { context: genderStr }); + case "STELLAR_TERASTALLIZE": + return i18next.t("achv:STELLAR_TERASTALLIZE.description", { context: genderStr }); + case "SPLICE": + return i18next.t("achv:SPLICE.description", { context: genderStr }); + case "MINI_BLACK_HOLE": + return i18next.t("achv:MINI_BLACK_HOLE.description", { context: genderStr }); + case "CATCH_MYTHICAL": + return i18next.t("achv:CATCH_MYTHICAL.description", { context: genderStr }); + case "CATCH_SUB_LEGENDARY": + return i18next.t("achv:CATCH_SUB_LEGENDARY.description", { context: genderStr }); + case "CATCH_LEGENDARY": + return i18next.t("achv:CATCH_LEGENDARY.description", { context: genderStr }); + case "SEE_SHINY": + return i18next.t("achv:SEE_SHINY.description", { context: genderStr }); + case "SHINY_PARTY": + return i18next.t("achv:SHINY_PARTY.description", { context: genderStr }); + case "HATCH_MYTHICAL": + return i18next.t("achv:HATCH_MYTHICAL.description", { context: genderStr }); + case "HATCH_SUB_LEGENDARY": + return i18next.t("achv:HATCH_SUB_LEGENDARY.description", { context: genderStr }); + case "HATCH_LEGENDARY": + return i18next.t("achv:HATCH_LEGENDARY.description", { context: genderStr }); + case "HATCH_SHINY": + return i18next.t("achv:HATCH_SHINY.description", { context: genderStr }); + case "HIDDEN_ABILITY": + return i18next.t("achv:HIDDEN_ABILITY.description", { context: genderStr }); + case "PERFECT_IVS": + return i18next.t("achv:PERFECT_IVS.description", { context: genderStr }); + case "CLASSIC_VICTORY": + return i18next.t("achv:CLASSIC_VICTORY.description", { context: genderStr }); + case "UNEVOLVED_CLASSIC_VICTORY": + return i18next.t("achv:UNEVOLVED_CLASSIC_VICTORY.description", { context: genderStr }); + case "MONO_GEN_ONE": + return i18next.t("achv:MONO_GEN_ONE.description", { context: genderStr }); + case "MONO_GEN_TWO": + return i18next.t("achv:MONO_GEN_TWO.description", { context: genderStr }); + case "MONO_GEN_THREE": + return i18next.t("achv:MONO_GEN_THREE.description", { context: genderStr }); + case "MONO_GEN_FOUR": + return i18next.t("achv:MONO_GEN_FOUR.description", { context: genderStr }); + case "MONO_GEN_FIVE": + return i18next.t("achv:MONO_GEN_FIVE.description", { context: genderStr }); + case "MONO_GEN_SIX": + return i18next.t("achv:MONO_GEN_SIX.description", { context: genderStr }); + case "MONO_GEN_SEVEN": + return i18next.t("achv:MONO_GEN_SEVEN.description", { context: genderStr }); + case "MONO_GEN_EIGHT": + return i18next.t("achv:MONO_GEN_EIGHT.description", { context: genderStr }); + case "MONO_GEN_NINE": + return i18next.t("achv:MONO_GEN_NINE.description", { context: genderStr }); + case "MONO_NORMAL": + case "MONO_FIGHTING": + case "MONO_FLYING": + case "MONO_POISON": + case "MONO_GROUND": + case "MONO_ROCK": + case "MONO_BUG": + case "MONO_GHOST": + case "MONO_STEEL": + case "MONO_FIRE": + case "MONO_WATER": + case "MONO_GRASS": + case "MONO_ELECTRIC": + case "MONO_PSYCHIC": + case "MONO_ICE": + case "MONO_DRAGON": + case "MONO_DARK": + case "MONO_FAIRY": + return i18next.t("achv:MonoType.description", { context: genderStr, "type": i18next.t(`pokemonInfo:Type.${localizationKey.slice(5)}`) }); + case "FRESH_START": + return i18next.t("achv:FRESH_START.description", { context: genderStr }); + case "INVERSE_BATTLE": + return i18next.t("achv:INVERSE_BATTLE.description", { context: genderStr }); + case "BREEDERS_IN_SPACE": + return i18next.t("achv:BREEDERS_IN_SPACE.description", { context: genderStr }); + default: + return ""; } } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 41746957d49..5f9aad63408 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -67,22 +67,22 @@ const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet n export function getDataTypeKey(dataType: GameDataType, slotId: integer = 0): string { switch (dataType) { - case GameDataType.SYSTEM: - return "data"; - case GameDataType.SESSION: - let ret = "sessionData"; - if (slotId) { - ret += slotId; - } - return ret; - case GameDataType.SETTINGS: - return "settings"; - case GameDataType.TUTORIALS: - return "tutorials"; - case GameDataType.SEEN_DIALOGUES: - return "seenDialogues"; - case GameDataType.RUN_HISTORY: - return "runHistoryData"; + case GameDataType.SYSTEM: + return "data"; + case GameDataType.SESSION: + let ret = "sessionData"; + if (slotId) { + ret += slotId; + } + return ret; + case GameDataType.SETTINGS: + return "settings"; + case GameDataType.TUTORIALS: + return "tutorials"; + case GameDataType.SEEN_DIALOGUES: + return "seenDialogues"; + case GameDataType.RUN_HISTORY: + return "runHistoryData"; } } @@ -1374,9 +1374,9 @@ export class GameData { const dataKey: string = `${getDataTypeKey(dataType, slotId)}_${loggedInUser?.username}`; const handleData = (dataStr: string) => { switch (dataType) { - case GameDataType.SYSTEM: - dataStr = this.convertSystemDataStr(dataStr, true); - break; + case GameDataType.SYSTEM: + dataStr = this.convertSystemDataStr(dataStr, true); + break; } const encryptedData = AES.encrypt(dataStr, saveKey); const blob = new Blob([ encryptedData.toString() ], { type: "text/json" }); @@ -1434,28 +1434,28 @@ export class GameData { try { dataName = GameDataType[dataType].toLowerCase(); switch (dataType) { - case GameDataType.SYSTEM: - dataStr = this.convertSystemDataStr(dataStr); - const systemData = this.parseSystemData(dataStr); - valid = !!systemData.dexData && !!systemData.timestamp; - break; - case GameDataType.SESSION: - const sessionData = this.parseSessionData(dataStr); - valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp; - break; - case GameDataType.RUN_HISTORY: - const data = JSON.parse(dataStr); - const keys = Object.keys(data); - dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); - keys.forEach((key) => { - const entryKeys = Object.keys(data[key]); - valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3; - }); - break; - case GameDataType.SETTINGS: - case GameDataType.TUTORIALS: - valid = true; - break; + case GameDataType.SYSTEM: + dataStr = this.convertSystemDataStr(dataStr); + const systemData = this.parseSystemData(dataStr); + valid = !!systemData.dexData && !!systemData.timestamp; + break; + case GameDataType.SESSION: + const sessionData = this.parseSessionData(dataStr); + valid = !!sessionData.party && !!sessionData.enemyParty && !!sessionData.timestamp; + break; + case GameDataType.RUN_HISTORY: + const data = JSON.parse(dataStr); + const keys = Object.keys(data); + dataName = i18next.t("menuUiHandler:RUN_HISTORY").toLowerCase(); + keys.forEach((key) => { + const entryKeys = Object.keys(data[key]); + valid = [ "isFavorite", "isVictory", "entry" ].every(v => entryKeys.includes(v)) && entryKeys.length === 3; + }); + break; + case GameDataType.SETTINGS: + case GameDataType.TUTORIALS: + valid = true; + break; } } catch (ex) { console.error(ex); diff --git a/src/system/settings/settings-gamepad.ts b/src/system/settings/settings-gamepad.ts index c96ec36f9a5..322b2baac9e 100644 --- a/src/system/settings/settings-gamepad.ts +++ b/src/system/settings/settings-gamepad.ts @@ -82,66 +82,66 @@ export const settingGamepadBlackList = [ export function setSettingGamepad(scene: BattleScene, setting: SettingGamepad, value: integer): boolean { switch (setting) { - case SettingGamepad.Gamepad_Support: + case SettingGamepad.Gamepad_Support: // if we change the value of the gamepad support, we call a method in the inputController to // activate or deactivate the controller listener - scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); - break; - case SettingGamepad.Button_Action: - case SettingGamepad.Button_Cancel: - case SettingGamepad.Button_Menu: - case SettingGamepad.Button_Stats: - case SettingGamepad.Button_Cycle_Shiny: - case SettingGamepad.Button_Cycle_Form: - case SettingGamepad.Button_Cycle_Gender: - case SettingGamepad.Button_Cycle_Ability: - case SettingGamepad.Button_Cycle_Nature: - case SettingGamepad.Button_Cycle_Variant: - case SettingGamepad.Button_Speed_Up: - case SettingGamepad.Button_Slow_Down: - case SettingGamepad.Button_Submit: - if (value) { - if (scene.ui) { - const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); - return success; - }; - scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { - target: setting, - cancelHandler: cancelHandler, - }); + scene.inputController.setGamepadSupport(settingGamepadOptions[setting][value] !== "Disabled"); + break; + case SettingGamepad.Button_Action: + case SettingGamepad.Button_Cancel: + case SettingGamepad.Button_Menu: + case SettingGamepad.Button_Stats: + case SettingGamepad.Button_Cycle_Shiny: + case SettingGamepad.Button_Cycle_Form: + case SettingGamepad.Button_Cycle_Gender: + case SettingGamepad.Button_Cycle_Ability: + case SettingGamepad.Button_Cycle_Nature: + case SettingGamepad.Button_Cycle_Variant: + case SettingGamepad.Button_Speed_Up: + case SettingGamepad.Button_Slow_Down: + case SettingGamepad.Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.GAMEPAD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } } - } - break; - case SettingGamepad.Controller: - if (value) { - const gp = scene.inputController.getGamepadsName(); - if (scene.ui && gp) { - const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); - (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + break; + case SettingGamepad.Controller: + if (value) { + const gp = scene.inputController.getGamepadsName(); + if (scene.ui && gp) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsGamepadUiHandler).setOptionCursor(Object.values(SettingGamepad).indexOf(SettingGamepad.Controller), 0, true); + (scene.ui.getHandler() as SettingsGamepadUiHandler).updateBindings(); + return false; + }; + const changeGamepadHandler = (gamepad: string) => { + scene.inputController.setChosenGamepad(gamepad); + cancelHandler(); + return true; + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ ...gp.map((g: string) => ({ + label: truncateString(g, 30), // Truncate the gamepad name for display + handler: () => changeGamepadHandler(g) + })), { + label: "Cancel", + handler: cancelHandler, + }] + }); return false; - }; - const changeGamepadHandler = (gamepad: string) => { - scene.inputController.setChosenGamepad(gamepad); - cancelHandler(); - return true; - }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - options: [ ...gp.map((g: string) => ({ - label: truncateString(g, 30), // Truncate the gamepad name for display - handler: () => changeGamepadHandler(g) - })), { - label: "Cancel", - handler: cancelHandler, - }] - }); - return false; + } } - } - break; + break; } return true; diff --git a/src/system/settings/settings-keyboard.ts b/src/system/settings/settings-keyboard.ts index d7cb994aca8..97990f61c86 100644 --- a/src/system/settings/settings-keyboard.ts +++ b/src/system/settings/settings-keyboard.ts @@ -135,53 +135,53 @@ export const settingKeyboardBlackList = [ export function setSettingKeyboard(scene: BattleScene, setting: SettingKeyboard, value: integer): boolean { switch (setting) { - case SettingKeyboard.Button_Up: - case SettingKeyboard.Button_Down: - case SettingKeyboard.Button_Left: - case SettingKeyboard.Button_Right: - case SettingKeyboard.Button_Action: - case SettingKeyboard.Button_Cancel: - case SettingKeyboard.Button_Menu: - case SettingKeyboard.Button_Stats: - case SettingKeyboard.Button_Cycle_Shiny: - case SettingKeyboard.Button_Cycle_Form: - case SettingKeyboard.Button_Cycle_Gender: - case SettingKeyboard.Button_Cycle_Ability: - case SettingKeyboard.Button_Cycle_Nature: - case SettingKeyboard.Button_Cycle_Variant: - case SettingKeyboard.Button_Speed_Up: - case SettingKeyboard.Button_Slow_Down: - case SettingKeyboard.Alt_Button_Up: - case SettingKeyboard.Alt_Button_Down: - case SettingKeyboard.Alt_Button_Left: - case SettingKeyboard.Alt_Button_Right: - case SettingKeyboard.Alt_Button_Action: - case SettingKeyboard.Alt_Button_Cancel: - case SettingKeyboard.Alt_Button_Menu: - case SettingKeyboard.Alt_Button_Stats: - case SettingKeyboard.Alt_Button_Cycle_Shiny: - case SettingKeyboard.Alt_Button_Cycle_Form: - case SettingKeyboard.Alt_Button_Cycle_Gender: - case SettingKeyboard.Alt_Button_Cycle_Ability: - case SettingKeyboard.Alt_Button_Cycle_Nature: - case SettingKeyboard.Alt_Button_Cycle_Variant: - case SettingKeyboard.Alt_Button_Speed_Up: - case SettingKeyboard.Alt_Button_Slow_Down: - case SettingKeyboard.Alt_Button_Submit: - if (value) { - if (scene.ui) { - const cancelHandler = (success: boolean = false) : boolean => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); - return success; - }; - scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { - target: setting, - cancelHandler: cancelHandler, - }); + case SettingKeyboard.Button_Up: + case SettingKeyboard.Button_Down: + case SettingKeyboard.Button_Left: + case SettingKeyboard.Button_Right: + case SettingKeyboard.Button_Action: + case SettingKeyboard.Button_Cancel: + case SettingKeyboard.Button_Menu: + case SettingKeyboard.Button_Stats: + case SettingKeyboard.Button_Cycle_Shiny: + case SettingKeyboard.Button_Cycle_Form: + case SettingKeyboard.Button_Cycle_Gender: + case SettingKeyboard.Button_Cycle_Ability: + case SettingKeyboard.Button_Cycle_Nature: + case SettingKeyboard.Button_Cycle_Variant: + case SettingKeyboard.Button_Speed_Up: + case SettingKeyboard.Button_Slow_Down: + case SettingKeyboard.Alt_Button_Up: + case SettingKeyboard.Alt_Button_Down: + case SettingKeyboard.Alt_Button_Left: + case SettingKeyboard.Alt_Button_Right: + case SettingKeyboard.Alt_Button_Action: + case SettingKeyboard.Alt_Button_Cancel: + case SettingKeyboard.Alt_Button_Menu: + case SettingKeyboard.Alt_Button_Stats: + case SettingKeyboard.Alt_Button_Cycle_Shiny: + case SettingKeyboard.Alt_Button_Cycle_Form: + case SettingKeyboard.Alt_Button_Cycle_Gender: + case SettingKeyboard.Alt_Button_Cycle_Ability: + case SettingKeyboard.Alt_Button_Cycle_Nature: + case SettingKeyboard.Alt_Button_Cycle_Variant: + case SettingKeyboard.Alt_Button_Speed_Up: + case SettingKeyboard.Alt_Button_Slow_Down: + case SettingKeyboard.Alt_Button_Submit: + if (value) { + if (scene.ui) { + const cancelHandler = (success: boolean = false) : boolean => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsKeyboardUiHandler).updateBindings(); + return success; + }; + scene.ui.setOverlayMode(Mode.KEYBOARD_BINDING, { + target: setting, + cancelHandler: cancelHandler, + }); + } } - } - break; + break; // case SettingKeyboard.Default_Layout: // if (value && scene.ui) { // const cancelHandler = () => { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 66021845c29..be440d5d93e 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -76,16 +76,16 @@ const SHOP_CURSOR_TARGET_OPTIONS: SettingOption[] = [ const shopCursorTargetIndexMap = SHOP_CURSOR_TARGET_OPTIONS.map(option => { switch (option.value) { - case "Rewards": - return ShopCursorTarget.REWARDS; - case "Shop": - return ShopCursorTarget.SHOP; - case "Reroll": - return ShopCursorTarget.REROLL; - case "Check Team": - return ShopCursorTarget.CHECK_TEAM; - default: - throw new Error(`Unknown value: ${option.value}`); + case "Rewards": + return ShopCursorTarget.REWARDS; + case "Shop": + return ShopCursorTarget.SHOP; + case "Reroll": + return ShopCursorTarget.REROLL; + case "Check Team": + return ShopCursorTarget.CHECK_TEAM; + default: + throw new Error(`Unknown value: ${option.value}`); } }); @@ -699,226 +699,226 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): return false; } switch (Setting[index].key) { - case SettingKeys.Game_Speed: - scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); - break; - case SettingKeys.Master_Volume: - scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.BGM_Volume: - scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.Field_Volume: - scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.SE_Volume: - scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - scene.updateSoundVolume(); - break; - case SettingKeys.UI_Volume: - scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; - break; - case SettingKeys.Music_Preference: - scene.musicPreference = value; - break; - case SettingKeys.Damage_Numbers: - scene.damageNumbersMode = value; - break; - case SettingKeys.UI_Theme: - scene.uiTheme = value; - break; - case SettingKeys.Window_Type: - updateWindowType(scene, parseInt(Setting[index].options[value].value)); - break; - case SettingKeys.Tutorials: - scene.enableTutorials = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Move_Info: - scene.enableMoveInfo = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Enable_Retries: - scene.enableRetries = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Hide_IVs: - scene.hideIvs = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Skip_Seen_Dialogues: - scene.skipSeenDialogues = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Egg_Skip: - scene.eggSkipPreference = value; - break; - case SettingKeys.Battle_Style: - scene.battleStyle = value; - break; - case SettingKeys.Show_BGM_Bar: - scene.showBgmBar = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Candy_Upgrade_Notification: - if (scene.candyUpgradeNotification === value) { + case SettingKeys.Game_Speed: + scene.gameSpeed = parseFloat(Setting[index].options[value].value.replace("x", "")); break; - } - scene.candyUpgradeNotification = value; - scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); - break; - case SettingKeys.Candy_Upgrade_Display: - scene.candyUpgradeDisplay = value; - case SettingKeys.Money_Format: - switch (Setting[index].options[value].value) { - case "Normal": - scene.moneyFormat = MoneyFormat.NORMAL; + case SettingKeys.Master_Volume: + scene.masterVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); break; - case "Abbreviated": - scene.moneyFormat = MoneyFormat.ABBREVIATED; + case SettingKeys.BGM_Volume: + scene.bgmVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); break; - } - scene.updateMoneyText(false); - break; - case SettingKeys.Sprite_Set: - scene.experimentalSprites = !!value; - if (value) { - scene.initExpSprites(); - } - break; - case SettingKeys.Move_Animations: - scene.moveAnimations = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Moveset_Flyout: - scene.showMovesetFlyout = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Arena_Flyout: - scene.showArenaFlyout = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Show_Time_Of_Day_Widget: - scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Time_Of_Day_Animation: - scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; - break; - case SettingKeys.Show_Stats_on_Level_Up: - scene.showLevelUpStats = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Shop_Cursor_Target: - const selectedValue = shopCursorTargetIndexMap[value]; - scene.shopCursorTarget = selectedValue; - break; - case SettingKeys.EXP_Gains_Speed: - scene.expGainsSpeed = value; - break; - case SettingKeys.EXP_Party_Display: - scene.expParty = value; - break; - case SettingKeys.HP_Bar_Speed: - scene.hpBarSpeed = value; - break; - case SettingKeys.Fusion_Palette_Swaps: - scene.fusionPaletteSwaps = !!value; - break; - case SettingKeys.Player_Gender: - if (scene.gameData) { - const female = Setting[index].options[value].value === "Girl"; - scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; - scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); - } else { - return false; - } - break; - case SettingKeys.Touch_Controls: - scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); - const touchControls = document.getElementById("touchControls"); - if (touchControls) { - touchControls.classList.toggle("visible", scene.enableTouchControls); - } - break; - case SettingKeys.Vibration: - scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); - break; - case SettingKeys.Type_Hints: - scene.typeHints = Setting[index].options[value].value === "On"; - break; - case SettingKeys.Language: - if (value) { - if (scene.ui) { - const cancelHandler = () => { - scene.ui.revertMode(); - (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); - }; - const changeLocaleHandler = (locale: string): boolean => { - try { - i18next.changeLanguage(locale); - localStorage.setItem("prLang", locale); - cancelHandler(); - // Reload the whole game to apply the new locale since also some constants are translated - window.location.reload(); - return true; - } catch (error) { - console.error("Error changing locale:", error); - return false; - } - }; - scene.ui.setOverlayMode(Mode.OPTION_SELECT, { - options: [ - { - label: "English", - handler: () => changeLocaleHandler("en") - }, - { - label: "Español", - handler: () => changeLocaleHandler("es") - }, - { - label: "Italiano", - handler: () => changeLocaleHandler("it") - }, - { - label: "Français", - handler: () => changeLocaleHandler("fr") - }, - { - label: "Deutsch", - handler: () => changeLocaleHandler("de") - }, - { - label: "Português (BR)", - handler: () => changeLocaleHandler("pt-BR") - }, - { - label: "简体中文", - handler: () => changeLocaleHandler("zh-CN") - }, - { - label: "繁體中文", - handler: () => changeLocaleHandler("zh-TW") - }, - { - label: "한국어", - handler: () => changeLocaleHandler("ko") - }, - { - label: "日本語", - handler: () => changeLocaleHandler("ja") - }, - // { - // label: "Català", - // handler: () => changeLocaleHandler("ca-ES") - // }, - { - label: i18next.t("settings:back"), - handler: () => cancelHandler() - } - ], - maxOptions: 7 - }); + case SettingKeys.Field_Volume: + scene.fieldVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.SE_Volume: + scene.seVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + scene.updateSoundVolume(); + break; + case SettingKeys.UI_Volume: + scene.uiVolume = value ? parseInt(Setting[index].options[value].value) * 0.01 : 0; + break; + case SettingKeys.Music_Preference: + scene.musicPreference = value; + break; + case SettingKeys.Damage_Numbers: + scene.damageNumbersMode = value; + break; + case SettingKeys.UI_Theme: + scene.uiTheme = value; + break; + case SettingKeys.Window_Type: + updateWindowType(scene, parseInt(Setting[index].options[value].value)); + break; + case SettingKeys.Tutorials: + scene.enableTutorials = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Move_Info: + scene.enableMoveInfo = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Enable_Retries: + scene.enableRetries = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Hide_IVs: + scene.hideIvs = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Skip_Seen_Dialogues: + scene.skipSeenDialogues = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Egg_Skip: + scene.eggSkipPreference = value; + break; + case SettingKeys.Battle_Style: + scene.battleStyle = value; + break; + case SettingKeys.Show_BGM_Bar: + scene.showBgmBar = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Candy_Upgrade_Notification: + if (scene.candyUpgradeNotification === value) { + break; + } + scene.candyUpgradeNotification = value; + scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); + break; + case SettingKeys.Candy_Upgrade_Display: + scene.candyUpgradeDisplay = value; + case SettingKeys.Money_Format: + switch (Setting[index].options[value].value) { + case "Normal": + scene.moneyFormat = MoneyFormat.NORMAL; + break; + case "Abbreviated": + scene.moneyFormat = MoneyFormat.ABBREVIATED; + break; + } + scene.updateMoneyText(false); + break; + case SettingKeys.Sprite_Set: + scene.experimentalSprites = !!value; + if (value) { + scene.initExpSprites(); + } + break; + case SettingKeys.Move_Animations: + scene.moveAnimations = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Moveset_Flyout: + scene.showMovesetFlyout = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Arena_Flyout: + scene.showArenaFlyout = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Show_Time_Of_Day_Widget: + scene.showTimeOfDayWidget = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Time_Of_Day_Animation: + scene.timeOfDayAnimation = Setting[index].options[value].value === "Bounce" ? EaseType.BOUNCE : EaseType.BACK; + break; + case SettingKeys.Show_Stats_on_Level_Up: + scene.showLevelUpStats = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Shop_Cursor_Target: + const selectedValue = shopCursorTargetIndexMap[value]; + scene.shopCursorTarget = selectedValue; + break; + case SettingKeys.EXP_Gains_Speed: + scene.expGainsSpeed = value; + break; + case SettingKeys.EXP_Party_Display: + scene.expParty = value; + break; + case SettingKeys.HP_Bar_Speed: + scene.hpBarSpeed = value; + break; + case SettingKeys.Fusion_Palette_Swaps: + scene.fusionPaletteSwaps = !!value; + break; + case SettingKeys.Player_Gender: + if (scene.gameData) { + const female = Setting[index].options[value].value === "Girl"; + scene.gameData.gender = female ? PlayerGender.FEMALE : PlayerGender.MALE; + scene.trainer.setTexture(scene.trainer.texture.key.replace(female ? "m" : "f", female ? "f" : "m")); + } else { return false; } - } - break; - case SettingKeys.Shop_Overlay_Opacity: - scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); - break; + break; + case SettingKeys.Touch_Controls: + scene.enableTouchControls = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + const touchControls = document.getElementById("touchControls"); + if (touchControls) { + touchControls.classList.toggle("visible", scene.enableTouchControls); + } + break; + case SettingKeys.Vibration: + scene.enableVibration = Setting[index].options[value].value !== "Disabled" && hasTouchscreen(); + break; + case SettingKeys.Type_Hints: + scene.typeHints = Setting[index].options[value].value === "On"; + break; + case SettingKeys.Language: + if (value) { + if (scene.ui) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(0, 0, true); + }; + const changeLocaleHandler = (locale: string): boolean => { + try { + i18next.changeLanguage(locale); + localStorage.setItem("prLang", locale); + cancelHandler(); + // Reload the whole game to apply the new locale since also some constants are translated + window.location.reload(); + return true; + } catch (error) { + console.error("Error changing locale:", error); + return false; + } + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ + { + label: "English", + handler: () => changeLocaleHandler("en") + }, + { + label: "Español", + handler: () => changeLocaleHandler("es") + }, + { + label: "Italiano", + handler: () => changeLocaleHandler("it") + }, + { + label: "Français", + handler: () => changeLocaleHandler("fr") + }, + { + label: "Deutsch", + handler: () => changeLocaleHandler("de") + }, + { + label: "Português (BR)", + handler: () => changeLocaleHandler("pt-BR") + }, + { + label: "简体中文", + handler: () => changeLocaleHandler("zh-CN") + }, + { + label: "繁體中文", + handler: () => changeLocaleHandler("zh-TW") + }, + { + label: "한국어", + handler: () => changeLocaleHandler("ko") + }, + { + label: "日本語", + handler: () => changeLocaleHandler("ja") + }, + // { + // label: "Català", + // handler: () => changeLocaleHandler("ca-ES") + // }, + { + label: i18next.t("settings:back"), + handler: () => cancelHandler() + } + ], + maxOptions: 7 + }); + return false; + } + } + break; + case SettingKeys.Shop_Overlay_Opacity: + scene.updateShopOverlayOpacity(parseInt(Setting[index].options[value].value) * .01); + break; } return true; diff --git a/src/system/unlockables.ts b/src/system/unlockables.ts index 983909373fd..0a666e2c755 100644 --- a/src/system/unlockables.ts +++ b/src/system/unlockables.ts @@ -10,13 +10,13 @@ export enum Unlockables { export function getUnlockableName(unlockable: Unlockables) { switch (unlockable) { - case Unlockables.ENDLESS_MODE: - return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`; - case Unlockables.MINI_BLACK_HOLE: - return i18next.t("modifierType:ModifierType.MINI_BLACK_HOLE.name"); - case Unlockables.SPLICED_ENDLESS_MODE: - return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`; - case Unlockables.EVIOLITE: - return i18next.t("modifierType:ModifierType.EVIOLITE.name"); + case Unlockables.ENDLESS_MODE: + return `${GameMode.getModeName(GameModes.ENDLESS)} Mode`; + case Unlockables.MINI_BLACK_HOLE: + return i18next.t("modifierType:ModifierType.MINI_BLACK_HOLE.name"); + case Unlockables.SPLICED_ENDLESS_MODE: + return `${GameMode.getModeName(GameModes.SPLICED_ENDLESS)} Mode`; + case Unlockables.EVIOLITE: + return i18next.t("modifierType:ModifierType.EVIOLITE.name"); } } diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index c20e2a281e7..f16b6bcb6bb 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -108,15 +108,15 @@ export const sessionMigrators = [ } 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; + case "MAX_LURE": + maxBattles = 30; + break; + case "SUPER_LURE": + maxBattles = 15; + break; + default: + maxBattles = 10; + break; } // From [ battlesLeft ] to [ maxBattles, battleCount ] diff --git a/src/system/voucher.ts b/src/system/voucher.ts index aca7b9fc2f2..b38fd528c9f 100644 --- a/src/system/voucher.ts +++ b/src/system/voucher.ts @@ -45,41 +45,41 @@ export class Voucher { getTier(): AchvTier { switch (this.voucherType) { - case VoucherType.REGULAR: - return AchvTier.COMMON; - case VoucherType.PLUS: - return AchvTier.GREAT; - case VoucherType.PREMIUM: - return AchvTier.ULTRA; - case VoucherType.GOLDEN: - return AchvTier.ROGUE; + case VoucherType.REGULAR: + return AchvTier.COMMON; + case VoucherType.PLUS: + return AchvTier.GREAT; + case VoucherType.PREMIUM: + return AchvTier.ULTRA; + case VoucherType.GOLDEN: + return AchvTier.ROGUE; } } } export function getVoucherTypeName(voucherType: VoucherType): string { switch (voucherType) { - case VoucherType.REGULAR: - return i18next.t("voucher:eggVoucher"); - case VoucherType.PLUS: - return i18next.t("voucher:eggVoucherPlus"); - case VoucherType.PREMIUM: - return i18next.t("voucher:eggVoucherPremium"); - case VoucherType.GOLDEN: - return i18next.t("voucher:eggVoucherGold"); + case VoucherType.REGULAR: + return i18next.t("voucher:eggVoucher"); + case VoucherType.PLUS: + return i18next.t("voucher:eggVoucherPlus"); + case VoucherType.PREMIUM: + return i18next.t("voucher:eggVoucherPremium"); + case VoucherType.GOLDEN: + return i18next.t("voucher:eggVoucherGold"); } } export function getVoucherTypeIcon(voucherType: VoucherType): string { switch (voucherType) { - case VoucherType.REGULAR: - return "coupon"; - case VoucherType.PLUS: - return "pair_of_tickets"; - case VoucherType.PREMIUM: - return "mystic_ticket"; - case VoucherType.GOLDEN: - return "golden_mystic_ticket"; + case VoucherType.REGULAR: + return "coupon"; + case VoucherType.PLUS: + return "pair_of_tickets"; + case VoucherType.PREMIUM: + return "mystic_ticket"; + case VoucherType.GOLDEN: + return "golden_mystic_ticket"; } } diff --git a/src/test/mystery-encounter/encounter-test-utils.ts b/src/test/mystery-encounter/encounter-test-utils.ts index 9fb2504c02b..f95a442d4c2 100644 --- a/src/test/mystery-encounter/encounter-test-utils.ts +++ b/src/test/mystery-encounter/encounter-test-utils.ts @@ -97,20 +97,20 @@ export async function runSelectMysteryEncounterOption(game: GameManager, optionN uiHandler.unblockInput(); // input are blocked by 1s to prevent accidental input. Tests need to handle that switch (optionNo) { - default: - case 1: + default: + case 1: // no movement needed. Default cursor position - break; - case 2: - uiHandler.processInput(Button.RIGHT); - break; - case 3: - uiHandler.processInput(Button.DOWN); - break; - case 4: - uiHandler.processInput(Button.RIGHT); - uiHandler.processInput(Button.DOWN); - break; + break; + case 2: + uiHandler.processInput(Button.RIGHT); + break; + case 3: + uiHandler.processInput(Button.DOWN); + break; + case 4: + uiHandler.processInput(Button.RIGHT); + uiHandler.processInput(Button.DOWN); + break; } if (!isNullOrUndefined(secondaryOptionSelect?.pokemonNo)) { diff --git a/src/touch-controls.ts b/src/touch-controls.ts index 786d1203d12..93032ce59fe 100644 --- a/src/touch-controls.ts +++ b/src/touch-controls.ts @@ -122,20 +122,20 @@ export default class TouchControl { const button = Button[key]; switch (eventType) { - case "keydown": - this.events.emit("input_down", { - controller_type: "keyboard", - button: button, - isTouch: true - }); - break; - case "keyup": - this.events.emit("input_up", { - controller_type: "keyboard", - button: button, - isTouch: true - }); - break; + case "keydown": + this.events.emit("input_down", { + controller_type: "keyboard", + button: button, + isTouch: true + }); + break; + case "keyup": + this.events.emit("input_up", { + controller_type: "keyboard", + button: button, + isTouch: true + }); + break; } return true; } diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 9ea1276352a..92b1653df3d 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -168,26 +168,26 @@ export class UiInputs { return; } switch (this.scene.ui?.getMode()) { - case Mode.MESSAGE: - const messageHandler = this.scene.ui.getHandler(); - if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { + case Mode.MESSAGE: + const messageHandler = this.scene.ui.getHandler(); + if (!messageHandler.pendingPrompt || messageHandler.isTextAnimationInProgress()) { + return; + } + case Mode.TITLE: + case Mode.COMMAND: + case Mode.MODIFIER_SELECT: + case Mode.MYSTERY_ENCOUNTER: + this.scene.ui.setOverlayMode(Mode.MENU); + break; + case Mode.STARTER_SELECT: + this.buttonTouch(); + break; + case Mode.MENU: + this.scene.ui.revertMode(); + this.scene.playSound("ui/select"); + break; + default: return; - } - case Mode.TITLE: - case Mode.COMMAND: - case Mode.MODIFIER_SELECT: - case Mode.MYSTERY_ENCOUNTER: - this.scene.ui.setOverlayMode(Mode.MENU); - break; - case Mode.STARTER_SELECT: - this.buttonTouch(); - break; - case Mode.MENU: - this.scene.ui.revertMode(); - this.scene.playSound("ui/select"); - break; - default: - return; } } diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index a12ffbc46bd..01fc5b00014 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -222,20 +222,20 @@ export default abstract class AbstractOptionSelectUiHandler extends UiHandler { } } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.cursor === 0) { - success = this.setCursor(options.length - 1); - } - break; - case Button.DOWN: - if (this.cursor < options.length - 1) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(0); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.cursor === 0) { + success = this.setCursor(options.length - 1); + } + break; + case Button.DOWN: + if (this.cursor < options.length - 1) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(0); + } + break; } if (this.config?.supportHover) { // handle hover code if the element supports hover-handlers and the option has the optional hover-handler set. diff --git a/src/ui/achvs-ui-handler.ts b/src/ui/achvs-ui-handler.ts index 4e0e2feea81..a1ced6145eb 100644 --- a/src/ui/achvs-ui-handler.ts +++ b/src/ui/achvs-ui-handler.ts @@ -245,51 +245,51 @@ export default class AchvsUiHandler extends MessageUiHandler { const rowIndex = Math.floor(this.cursor / this.COLS); const itemOffset = (this.scrollCursor * this.COLS); switch (button) { - case Button.UP: - if (this.cursor < this.COLS) { - if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { + case Button.UP: + if (this.cursor < this.COLS) { + if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else { // Wrap around to the last row - success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); - let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; - if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { - newCursorIndex -= this.COLS; + success = this.setScrollCursor(Math.ceil(this.currentTotal / this.COLS) - this.ROWS); + let newCursorIndex = this.cursor + (this.ROWS - 1) * this.COLS; + if (newCursorIndex > this.currentTotal - this.scrollCursor * this.COLS - 1) { + newCursorIndex -= this.COLS; + } + success = success && this.setCursor(newCursorIndex); } - success = success && this.setCursor(newCursorIndex); - } - } else { - success = this.setCursor(this.cursor - this.COLS); - } - break; - case Button.DOWN: - const canMoveDown = itemOffset + 1 < this.currentTotal; - if (rowIndex >= this.ROWS - 1) { - if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { - // scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); } else { - // wrap back to the first row - success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + success = this.setCursor(this.cursor - this.COLS); } - } else if (canMoveDown) { - success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); - } - break; - case Button.LEFT: - if (this.cursor % this.COLS === 0) { - success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); - } else { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) { - success = this.setCursor(this.cursor - this.cursor % this.COLS); - } else { - success = this.setCursor(this.cursor + 1); - } - break; + break; + case Button.DOWN: + const canMoveDown = itemOffset + 1 < this.currentTotal; + if (rowIndex >= this.ROWS - 1) { + if (this.scrollCursor < Math.ceil(this.currentTotal / this.COLS) - this.ROWS && canMoveDown) { + // scroll down one row + success = this.setScrollCursor(this.scrollCursor + 1); + } else { + // wrap back to the first row + success = this.setScrollCursor(0) && this.setCursor(this.cursor % this.COLS); + } + } else if (canMoveDown) { + success = this.setCursor(Math.min(this.cursor + this.COLS, this.currentTotal - itemOffset - 1)); + } + break; + case Button.LEFT: + if (this.cursor % this.COLS === 0) { + success = this.setCursor(Math.min(this.cursor + this.COLS - 1, this.currentTotal - itemOffset - 1)); + } else { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if ((this.cursor + 1) % this.COLS === 0 || (this.cursor + itemOffset) === (this.currentTotal - 1)) { + success = this.setCursor(this.cursor - this.cursor % this.COLS); + } else { + success = this.setCursor(this.cursor + 1); + } + break; } } @@ -316,22 +316,22 @@ export default class AchvsUiHandler extends MessageUiHandler { if (update || pageChange) { switch (this.currentPage) { - case Page.ACHIEVEMENTS: - if (pageChange) { - this.titleBg.width = 174; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(true); - } - this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - if (pageChange) { - this.titleBg.width = 220; - this.titleText.x = this.titleBg.width / 2; - this.scoreContainer.setVisible(false); - } - this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); - break; + case Page.ACHIEVEMENTS: + if (pageChange) { + this.titleBg.width = 174; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(true); + } + this.showAchv(achvs[Object.keys(achvs)[cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + if (pageChange) { + this.titleBg.width = 220; + this.titleText.x = this.titleBg.width / 2; + this.scoreContainer.setVisible(false); + } + this.showVoucher(vouchers[Object.keys(vouchers)[cursor + this.scrollCursor * this.COLS]]); + break; } } return ret; @@ -358,14 +358,14 @@ export default class AchvsUiHandler extends MessageUiHandler { } switch (this.currentPage) { - case Page.ACHIEVEMENTS: - this.updateAchvIcons(); - this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]); - break; - case Page.VOUCHERS: - this.updateVoucherIcons(); - this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]); - break; + case Page.ACHIEVEMENTS: + this.updateAchvIcons(); + this.showAchv(achvs[Object.keys(achvs)[this.cursor + this.scrollCursor * this.COLS]]); + break; + case Page.VOUCHERS: + this.updateVoucherIcons(); + this.showVoucher(vouchers[Object.keys(vouchers)[this.cursor + this.scrollCursor * this.COLS]]); + break; } return true; } diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 0c00d3403b6..a82f97244cd 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -215,20 +215,20 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { // Creates a proxy object to decide which text object needs to be updated let textObject: Phaser.GameObjects.Text; switch (fieldEffectInfo.effecType) { - case ArenaEffectType.PLAYER: - textObject = this.flyoutTextPlayer; - break; + case ArenaEffectType.PLAYER: + textObject = this.flyoutTextPlayer; + break; - case ArenaEffectType.WEATHER: - case ArenaEffectType.TERRAIN: - case ArenaEffectType.FIELD: - textObject = this.flyoutTextField; + case ArenaEffectType.WEATHER: + case ArenaEffectType.TERRAIN: + case ArenaEffectType.FIELD: + textObject = this.flyoutTextField; - break; + break; - case ArenaEffectType.ENEMY: - textObject = this.flyoutTextEnemy; - break; + case ArenaEffectType.ENEMY: + textObject = this.flyoutTextEnemy; + break; } textObject.text += fieldEffectInfo.name; @@ -253,81 +253,81 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { let foundIndex: number; switch (arenaEffectChangedEvent.constructor) { - case TagAddedEvent: - const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; - const isArenaTrapTag = this.battleScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; - let arenaEffectType: ArenaEffectType; + case TagAddedEvent: + const tagAddedEvent = arenaEffectChangedEvent as TagAddedEvent; + const isArenaTrapTag = this.battleScene.arena.getTag(tagAddedEvent.arenaTagType) instanceof ArenaTrapTag; + let arenaEffectType: ArenaEffectType; - if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { - arenaEffectType = ArenaEffectType.FIELD; - } else if (tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER) { - arenaEffectType = ArenaEffectType.PLAYER; - } else { - arenaEffectType = ArenaEffectType.ENEMY; - } - - const existingTrapTagIndex = isArenaTrapTag ? this.fieldEffectInfo.findIndex(e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effecType) : -1; - let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]); - - if (isArenaTrapTag) { - if (existingTrapTagIndex !== -1) { - const layers = tagAddedEvent.arenaTagMaxLayers > 1 ? ` (${tagAddedEvent.arenaTagLayers})` : ""; - this.fieldEffectInfo[existingTrapTagIndex].name = `${name}${layers}`; - break; - } else if (tagAddedEvent.arenaTagMaxLayers > 1) { - name = `${name} (${tagAddedEvent.arenaTagLayers})`; + if (tagAddedEvent.arenaTagSide === ArenaTagSide.BOTH) { + arenaEffectType = ArenaEffectType.FIELD; + } else if (tagAddedEvent.arenaTagSide === ArenaTagSide.PLAYER) { + arenaEffectType = ArenaEffectType.PLAYER; + } else { + arenaEffectType = ArenaEffectType.ENEMY; } - } - this.fieldEffectInfo.push({ - name, - effecType: arenaEffectType, - maxDuration: tagAddedEvent.duration, - duration: tagAddedEvent.duration, - tagType: tagAddedEvent.arenaTagType - }); - break; - case TagRemovedEvent: - const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; - foundIndex = this.fieldEffectInfo.findIndex(info => info.tagType === tagRemovedEvent.arenaTagType); + const existingTrapTagIndex = isArenaTrapTag ? this.fieldEffectInfo.findIndex(e => tagAddedEvent.arenaTagType === e.tagType && arenaEffectType === e.effecType) : -1; + let name: string = getFieldEffectText(ArenaTagType[tagAddedEvent.arenaTagType]); - if (foundIndex !== -1) { // If the tag was being tracked, remove it - this.fieldEffectInfo.splice(foundIndex, 1); - } - break; + if (isArenaTrapTag) { + if (existingTrapTagIndex !== -1) { + const layers = tagAddedEvent.arenaTagMaxLayers > 1 ? ` (${tagAddedEvent.arenaTagLayers})` : ""; + this.fieldEffectInfo[existingTrapTagIndex].name = `${name}${layers}`; + break; + } else if (tagAddedEvent.arenaTagMaxLayers > 1) { + name = `${name} (${tagAddedEvent.arenaTagLayers})`; + } + } - case WeatherChangedEvent: - case TerrainChangedEvent: - const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + this.fieldEffectInfo.push({ + name, + effecType: arenaEffectType, + maxDuration: tagAddedEvent.duration, + duration: tagAddedEvent.duration, + tagType: tagAddedEvent.arenaTagType + }); + break; + case TagRemovedEvent: + const tagRemovedEvent = arenaEffectChangedEvent as TagRemovedEvent; + foundIndex = this.fieldEffectInfo.findIndex(info => info.tagType === tagRemovedEvent.arenaTagType); - // Stores the old Weather/Terrain name in case it's in the array already - const oldName = + if (foundIndex !== -1) { // If the tag was being tracked, remove it + this.fieldEffectInfo.splice(foundIndex, 1); + } + break; + + case WeatherChangedEvent: + case TerrainChangedEvent: + const fieldEffectChangedEvent = arenaEffectChangedEvent as WeatherChangedEvent | TerrainChangedEvent; + + // Stores the old Weather/Terrain name in case it's in the array already + const oldName = getFieldEffectText(fieldEffectChangedEvent instanceof WeatherChangedEvent ? WeatherType[fieldEffectChangedEvent.oldWeatherType] : TerrainType[fieldEffectChangedEvent.oldTerrainType]); - // Stores the new Weather/Terrain info - const newInfo = { - name: + // Stores the new Weather/Terrain info + const newInfo = { + name: getFieldEffectText(fieldEffectChangedEvent instanceof WeatherChangedEvent ? WeatherType[fieldEffectChangedEvent.newWeatherType] : TerrainType[fieldEffectChangedEvent.newTerrainType]), - effecType: fieldEffectChangedEvent instanceof WeatherChangedEvent - ? ArenaEffectType.WEATHER - : ArenaEffectType.TERRAIN, - maxDuration: fieldEffectChangedEvent.duration, - duration: fieldEffectChangedEvent.duration }; + effecType: fieldEffectChangedEvent instanceof WeatherChangedEvent + ? ArenaEffectType.WEATHER + : ArenaEffectType.TERRAIN, + maxDuration: fieldEffectChangedEvent.duration, + duration: fieldEffectChangedEvent.duration }; - foundIndex = this.fieldEffectInfo.findIndex(info => [ newInfo.name, oldName ].includes(info.name)); - if (foundIndex === -1) { - if (newInfo.name !== undefined) { - this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + foundIndex = this.fieldEffectInfo.findIndex(info => [ newInfo.name, oldName ].includes(info.name)); + if (foundIndex === -1) { + if (newInfo.name !== undefined) { + this.fieldEffectInfo.push(newInfo); // Adds the info to the array if it doesn't already exist and is defined + } + } else if (!newInfo.name) { + this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined + } else { + this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info } - } else if (!newInfo.name) { - this.fieldEffectInfo.splice(foundIndex, 1); // Removes the old info if the new one is undefined - } else { - this.fieldEffectInfo[foundIndex] = newInfo; // Otherwise, replace the old info - } - break; + break; } this.updateFieldText(); diff --git a/src/ui/ball-ui-handler.ts b/src/ui/ball-ui-handler.ts index 0d97a79943d..9df6da36055 100644 --- a/src/ui/ball-ui-handler.ts +++ b/src/ui/ball-ui-handler.ts @@ -90,12 +90,12 @@ export default class BallUiHandler extends UiHandler { } } else { switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? this.cursor - 1 : pokeballTypeCount); - break; - case Button.DOWN: - success = this.setCursor(this.cursor < pokeballTypeCount ? this.cursor + 1 : 0); - break; + case Button.UP: + success = this.setCursor(this.cursor ? this.cursor - 1 : pokeballTypeCount); + break; + case Button.DOWN: + success = this.setCursor(this.cursor < pokeballTypeCount ? this.cursor + 1 : 0); + break; } } diff --git a/src/ui/challenges-select-ui-handler.ts b/src/ui/challenges-select-ui-handler.ts index b2c0f1a1746..e2547a626de 100644 --- a/src/ui/challenges-select-ui-handler.ts +++ b/src/ui/challenges-select-ui-handler.ts @@ -376,66 +376,66 @@ export default class GameChallengesUiHandler extends UiHandler { } else { if (this.cursorObj?.visible && !this.startCursor.visible) { switch (button) { - case Button.UP: - if (this.cursor === 0) { - if (this.scrollCursor === 0) { + case Button.UP: + if (this.cursor === 0) { + if (this.scrollCursor === 0) { // When at the top of the menu and pressing UP, move to the bottommost item. - if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom + if (this.scene.gameMode.challenges.length > rowsToDisplay) { // If there are more than 9 challenges, scroll to the bottom // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } else { // If there are 9 or less challenges, just move to the bottom one - success = this.setCursor(this.scene.gameMode.challenges.length - 1); + const successA = this.setCursor(rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.scene.gameMode.challenges.length - rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect + } else { // If there are 9 or less challenges, just move to the bottom one + success = this.setCursor(this.scene.gameMode.challenges.length - 1); + } + } else { + success = this.setScrollCursor(this.scrollCursor - 1); } } else { - success = this.setScrollCursor(this.scrollCursor - 1); + success = this.setCursor(this.cursor - 1); } - } else { - success = this.setCursor(this.cursor - 1); - } - if (success) { - this.updateText(); - } - break; - case Button.DOWN: - if (this.cursor === rowsToDisplay - 1) { - if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { + if (success) { + this.updateText(); + } + break; + case Button.DOWN: + if (this.cursor === rowsToDisplay - 1) { + if (this.scrollCursor < this.scene.gameMode.challenges.length - rowsToDisplay) { // When at the bottom and pressing DOWN, scroll if possible. - success = this.setScrollCursor(this.scrollCursor + 1); - } else { + success = this.setScrollCursor(this.scrollCursor + 1); + } else { // When at the bottom of a scrolling menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, preparing for the scroll to the top. - const successA = this.setCursor(0); - // Then, adjust the scroll to display the topmost elements of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // success is just there to play the little validation sound effect - } - } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { + const successA = this.setCursor(0); + // Then, adjust the scroll to display the topmost elements of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // success is just there to play the little validation sound effect + } + } else if (this.scene.gameMode.challenges.length < rowsToDisplay && this.cursor === this.scene.gameMode.challenges.length - 1) { // When at the bottom of a non-scrolling menu and pressing DOWN, move to the topmost item. - success = this.setCursor(0); - } else { - success = this.setCursor(this.cursor + 1); - } - if (success) { - this.updateText(); - } - break; - case Button.LEFT: + success = this.setCursor(0); + } else { + success = this.setCursor(this.cursor + 1); + } + if (success) { + this.updateText(); + } + break; + case Button.LEFT: // Moves the option cursor left, if possible. - success = this.getActiveChallenge().decreaseValue(); - if (success) { - this.updateText(); - } - break; - case Button.RIGHT: + success = this.getActiveChallenge().decreaseValue(); + if (success) { + this.updateText(); + } + break; + case Button.RIGHT: // Moves the option cursor right, if possible. - success = this.getActiveChallenge().increaseValue(); - if (success) { - this.updateText(); - } - break; + success = this.getActiveChallenge().increaseValue(); + if (success) { + this.updateText(); + } + break; } } } diff --git a/src/ui/command-ui-handler.ts b/src/ui/command-ui-handler.ts index 2f65fa0b457..0f5edc28675 100644 --- a/src/ui/command-ui-handler.ts +++ b/src/ui/command-ui-handler.ts @@ -89,54 +89,54 @@ export default class CommandUiHandler extends UiHandler { if (button === Button.ACTION) { switch (cursor) { // Fight - case Command.FIGHT: - if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) { - return true; - } - ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); - success = true; - break; + case Command.FIGHT: + if ((this.scene.getCurrentPhase() as CommandPhase).checkFightOverride()) { + return true; + } + ui.setMode(Mode.FIGHT, (this.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); + success = true; + break; // Ball - case Command.BALL: - ui.setModeWithoutClear(Mode.BALL); - success = true; - break; + case Command.BALL: + ui.setModeWithoutClear(Mode.BALL); + success = true; + break; // Pokemon - case Command.POKEMON: - ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); - success = true; - break; + case Command.POKEMON: + ui.setMode(Mode.PARTY, PartyUiMode.SWITCH, (this.scene.getCurrentPhase() as CommandPhase).getPokemon().getFieldIndex(), null, PartyUiHandler.FilterNonFainted); + success = true; + break; // Run - case Command.RUN: - (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); - success = true; - break; + case Command.RUN: + (this.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.RUN, 0); + success = true; + break; } } else { (this.scene.getCurrentPhase() as CommandPhase).cancel(); } } else { switch (button) { - case Button.UP: - if (cursor >= 2) { - success = this.setCursor(cursor - 2); - } - break; - case Button.DOWN: - if (cursor < 2) { - success = this.setCursor(cursor + 2); - } - break; - case Button.LEFT: - if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor % 2 === 0) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2) { + success = this.setCursor(cursor - 2); + } + break; + case Button.DOWN: + if (cursor < 2) { + success = this.setCursor(cursor + 2); + } + break; + case Button.LEFT: + if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor % 2 === 0) { + success = this.setCursor(cursor + 1); + } + break; } } diff --git a/src/ui/daily-run-scoreboard.ts b/src/ui/daily-run-scoreboard.ts index b535a94d35c..b9c1c6ea49a 100644 --- a/src/ui/daily-run-scoreboard.ts +++ b/src/ui/daily-run-scoreboard.ts @@ -142,13 +142,13 @@ export class DailyRunScoreboard extends Phaser.GameObjects.Container { entryContainer.add(scoreLabel); switch (this.category) { - case ScoreboardCategory.DAILY: - const waveLabel = addTextObject(this.scene, 68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); - entryContainer.add(waveLabel); - break; - case ScoreboardCategory.WEEKLY: - scoreLabel.x -= 16; - break; + case ScoreboardCategory.DAILY: + const waveLabel = addTextObject(this.scene, 68, 0, wave, TextStyle.WINDOW, { fontSize: "54px" }); + entryContainer.add(waveLabel); + break; + case ScoreboardCategory.WEEKLY: + scoreLabel.x -= 16; + break; } return entryContainer; diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index eec44480c89..e16efe17036 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -115,18 +115,18 @@ export class DropDownOption extends Phaser.GameObjects.Container { */ private updateToggleIconColor(): void { switch (this.state) { - case DropDownState.ON: - this.toggle.setTint(this.onColor); - break; - case DropDownState.OFF: - this.toggle.setTint(this.offColor); - break; - case DropDownState.EXCLUDE: - this.toggle.setTint(this.excludeColor); - break; - case DropDownState.UNLOCKABLE: - this.toggle.setTint(this.unlockableColor); - break; + case DropDownState.ON: + this.toggle.setTint(this.onColor); + break; + case DropDownState.OFF: + this.toggle.setTint(this.offColor); + break; + case DropDownState.EXCLUDE: + this.toggle.setTint(this.excludeColor); + break; + case DropDownState.UNLOCKABLE: + this.toggle.setTint(this.unlockableColor); + break; } } @@ -500,18 +500,18 @@ export class DropDown extends Phaser.GameObjects.Container { }; switch (this.dropDownType) { - case DropDownType.MULTI: - case DropDownType.RADIAL: - return compareValues([ "val", "state" ]); + case DropDownType.MULTI: + case DropDownType.RADIAL: + return compareValues([ "val", "state" ]); - case DropDownType.HYBRID: - return compareValues([ "val", "state", "cursor" ]); + case DropDownType.HYBRID: + return compareValues([ "val", "state", "cursor" ]); - case DropDownType.SINGLE: - return compareValues([ "val", "state", "dir" ]); + case DropDownType.SINGLE: + return compareValues([ "val", "state", "dir" ]); - default: - return false; + default: + return false; } } diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 3aa009b1b31..8f977ba2ac0 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -127,47 +127,47 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaInfoContainer.add(gachaUpLabel); switch (gachaType as GachaType) { - case GachaType.LEGENDARY: - if ([ "de", "es" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } - if ([ "pt-BR" ].includes(currentLanguage)) { - gachaUpLabel.setX(legendaryLabelX - 2); - } else { - gachaUpLabel.setX(legendaryLabelX); - } - gachaUpLabel.setY(legendaryLabelY); + case GachaType.LEGENDARY: + if ([ "de", "es" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } + if ([ "pt-BR" ].includes(currentLanguage)) { + gachaUpLabel.setX(legendaryLabelX - 2); + } else { + gachaUpLabel.setX(legendaryLabelX); + } + gachaUpLabel.setY(legendaryLabelY); - const pokemonIcon = this.scene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); - if ([ "pt-BR" ].includes(currentLanguage)) { - pokemonIcon.setX(pokemonIconX - 2); - } - pokemonIcon.setScale(0.5); - pokemonIcon.setOrigin(0, 0.5); + const pokemonIcon = this.scene.add.sprite(pokemonIconX, pokemonIconY, "pokemon_icons_0"); + if ([ "pt-BR" ].includes(currentLanguage)) { + pokemonIcon.setX(pokemonIconX - 2); + } + pokemonIcon.setScale(0.5); + pokemonIcon.setOrigin(0, 0.5); - gachaInfoContainer.add(pokemonIcon); - break; - case GachaType.MOVE: - if ([ "de", "es", "fr", "pt-BR" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } + gachaInfoContainer.add(pokemonIcon); + break; + case GachaType.MOVE: + if ([ "de", "es", "fr", "pt-BR" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } - gachaUpLabel.setText(i18next.t("egg:moveUPGacha")); - gachaUpLabel.setX(0); - gachaUpLabel.setOrigin(0.5, 0); - break; - case GachaType.SHINY: - if ([ "de", "fr", "ko" ].includes(currentLanguage)) { - gachaUpLabel.setAlign("center"); - gachaUpLabel.setY(0); - } + gachaUpLabel.setText(i18next.t("egg:moveUPGacha")); + gachaUpLabel.setX(0); + gachaUpLabel.setOrigin(0.5, 0); + break; + case GachaType.SHINY: + if ([ "de", "fr", "ko" ].includes(currentLanguage)) { + gachaUpLabel.setAlign("center"); + gachaUpLabel.setY(0); + } - gachaUpLabel.setText(i18next.t("egg:shinyUPGacha")); - gachaUpLabel.setX(0); - gachaUpLabel.setOrigin(0.5, 0); - break; + gachaUpLabel.setText(i18next.t("egg:shinyUPGacha")); + gachaUpLabel.setX(0); + gachaUpLabel.setOrigin(0.5, 0); + break; } const gachaKnob = this.scene.add.sprite(191, 89, "gacha_knob"); @@ -470,12 +470,12 @@ export default class EggGachaUiHandler extends MessageUiHandler { getGuaranteedEggTierFromPullCount(pullCount: number): EggTier { switch (pullCount) { - case 10: - return EggTier.RARE; - case 25: - return EggTier.EPIC; - default: - return EggTier.COMMON; + case 10: + return EggTier.RARE; + case 25: + return EggTier.EPIC; + default: + return EggTier.COMMON; } } @@ -567,11 +567,11 @@ export default class EggGachaUiHandler extends MessageUiHandler { updateGachaInfo(gachaType: GachaType): void { const infoContainer = this.gachaInfoContainers[gachaType]; switch (gachaType as GachaType) { - case GachaType.LEGENDARY: - const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime())); - const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; - pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); - break; + case GachaType.LEGENDARY: + const species = getPokemonSpecies(getLegendaryGachaSpeciesForTimestamp(this.scene, new Date().getTime())); + const pokemonIcon = infoContainer.getAt(1) as Phaser.GameObjects.Sprite; + pokemonIcon.setTexture(species.getIconAtlasKey(), species.getIconId(false)); + break; } } @@ -638,106 +638,106 @@ export default class EggGachaUiHandler extends MessageUiHandler { } } else { switch (button) { - case Button.ACTION: - switch (this.cursor) { - case 0: - if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.REGULAR, 1); - } - this.pull(); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 2: - if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.PLUS, 1); - } - this.pull(5); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 1: - case 3: - if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) + case Button.ACTION: + switch (this.cursor) { + case 0: + if (!this.scene.gameData.voucherCounts[VoucherType.REGULAR] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 99 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.REGULAR, 1); + } + this.pull(); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); + } + break; + case 2: + if (!this.scene.gameData.voucherCounts[VoucherType.PLUS] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 95 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.PLUS, 1); + } + this.pull(5); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); + } + break; + case 1: + case 3: + if ((this.cursor === 1 && this.scene.gameData.voucherCounts[VoucherType.REGULAR] < 10 && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) || (this.cursor === 3 && !this.scene.gameData.voucherCounts[VoucherType.PREMIUM] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE)) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (this.cursor === 3) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.PREMIUM, 1); + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 90 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (this.cursor === 3) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.PREMIUM, 1); + } + } else { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.REGULAR, 10); + } + } + this.pull(10); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); } - } else { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.REGULAR, 10); + break; + case 4: + if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + error = true; + this.showError(i18next.t("egg:notEnoughVouchers")); + } else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { + if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { + this.consumeVouchers(VoucherType.GOLDEN, 1); + } + this.pull(25); + success = true; + } else { + error = true; + this.showError(i18next.t("egg:tooManyEggs")); } - } - this.pull(10); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); + break; + case 5: + ui.revertMode(); + success = true; + break; } break; - case 4: - if (!this.scene.gameData.voucherCounts[VoucherType.GOLDEN] && !Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - error = true; - this.showError(i18next.t("egg:notEnoughVouchers")); - } else if (this.scene.gameData.eggs.length < 75 || Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { - if (!Overrides.EGG_FREE_GACHA_PULLS_OVERRIDE) { - this.consumeVouchers(VoucherType.GOLDEN, 1); - } - this.pull(25); - success = true; - } else { - error = true; - this.showError(i18next.t("egg:tooManyEggs")); - } - break; - case 5: - ui.revertMode(); + case Button.CANCEL: + this.getUi().revertMode(); success = true; break; - } - break; - case Button.CANCEL: - this.getUi().revertMode(); - success = true; - break; - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.DOWN: - if (this.cursor < 5) { - success = this.setCursor(this.cursor + 1); - } - break; - case Button.LEFT: - if (this.gachaCursor) { - success = this.setGachaCursor(this.gachaCursor - 1); - } - break; - case Button.RIGHT: - if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1) { - success = this.setGachaCursor(this.gachaCursor + 1); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < 5) { + success = this.setCursor(this.cursor + 1); + } + break; + case Button.LEFT: + if (this.gachaCursor) { + success = this.setGachaCursor(this.gachaCursor - 1); + } + break; + case Button.RIGHT: + if (this.gachaCursor < Utils.getEnumKeys(GachaType).length - 1) { + success = this.setGachaCursor(this.gachaCursor + 1); + } + break; } } } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index 78894a7bf00..ee6641a1a27 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -152,26 +152,26 @@ export default class FightUiHandler extends UiHandler implements InfoToggle { } } else { switch (button) { - case Button.UP: - if (cursor >= 2) { - success = this.setCursor(cursor - 2); - } - break; - case Button.DOWN: - if (cursor < 2) { - success = this.setCursor(cursor + 2); - } - break; - case Button.LEFT: - if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor % 2 === 0) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2) { + success = this.setCursor(cursor - 2); + } + break; + case Button.DOWN: + if (cursor < 2) { + success = this.setCursor(cursor + 2); + } + break; + case Button.LEFT: + if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor % 2 === 0) { + success = this.setCursor(cursor + 1); + } + break; } } diff --git a/src/ui/game-stats-ui-handler.ts b/src/ui/game-stats-ui-handler.ts index 4d7b0855092..671bed29036 100644 --- a/src/ui/game-stats-ui-handler.ts +++ b/src/ui/game-stats-ui-handler.ts @@ -351,16 +351,16 @@ export default class GameStatsUiHandler extends UiHandler { this.scene.ui.revertMode(); } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.DOWN: - if (this.cursor < Math.ceil((Object.keys(displayStats).length - 18) / 2)) { - success = this.setCursor(this.cursor + 1); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < Math.ceil((Object.keys(displayStats).length - 18) / 2)) { + success = this.setCursor(this.cursor + 1); + } + break; } } diff --git a/src/ui/login-form-ui-handler.ts b/src/ui/login-form-ui-handler.ts index 192a6cefa7a..8be432ad6c1 100644 --- a/src/ui/login-form-ui-handler.ts +++ b/src/ui/login-form-ui-handler.ts @@ -97,18 +97,18 @@ export default class LoginFormUiHandler extends FormModalUiHandler { error = error.slice(0, colonIndex); } switch (error) { - case this.ERR_USERNAME: - return i18next.t("menu:invalidLoginUsername"); - case this.ERR_PASSWORD: - return i18next.t("menu:invalidLoginPassword"); - case this.ERR_ACCOUNT_EXIST: - return i18next.t("menu:accountNonExistent"); - case this.ERR_PASSWORD_MATCH: - return i18next.t("menu:unmatchingPassword"); - case this.ERR_NO_SAVES: - return i18next.t("menu:noSaves"); - case this.ERR_TOO_MANY_SAVES: - return i18next.t("menu:tooManySaves"); + case this.ERR_USERNAME: + return i18next.t("menu:invalidLoginUsername"); + case this.ERR_PASSWORD: + return i18next.t("menu:invalidLoginPassword"); + case this.ERR_ACCOUNT_EXIST: + return i18next.t("menu:accountNonExistent"); + case this.ERR_PASSWORD_MATCH: + return i18next.t("menu:unmatchingPassword"); + case this.ERR_NO_SAVES: + return i18next.t("menu:noSaves"); + case this.ERR_TOO_MANY_SAVES: + return i18next.t("menu:tooManySaves"); } return super.getReadableErrorMessage(error); diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index adebf9fb912..0f4c2d2f53e 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -468,148 +468,148 @@ export default class MenuUiHandler extends MessageUiHandler { } this.showText("", 0); switch (adjustedCursor) { - case MenuOptions.GAME_SETTINGS: - ui.setOverlayMode(Mode.SETTINGS); - success = true; - break; - case MenuOptions.ACHIEVEMENTS: - ui.setOverlayMode(Mode.ACHIEVEMENTS); - success = true; - break; - case MenuOptions.STATS: - ui.setOverlayMode(Mode.GAME_STATS); - success = true; - break; - case MenuOptions.RUN_HISTORY: - ui.setOverlayMode(Mode.RUN_HISTORY); - success = true; - break; - case MenuOptions.EGG_LIST: - if (this.scene.gameData.eggs.length) { + case MenuOptions.GAME_SETTINGS: + ui.setOverlayMode(Mode.SETTINGS); + success = true; + break; + case MenuOptions.ACHIEVEMENTS: + ui.setOverlayMode(Mode.ACHIEVEMENTS); + success = true; + break; + case MenuOptions.STATS: + ui.setOverlayMode(Mode.GAME_STATS); + success = true; + break; + case MenuOptions.RUN_HISTORY: + ui.setOverlayMode(Mode.RUN_HISTORY); + success = true; + break; + case MenuOptions.EGG_LIST: + if (this.scene.gameData.eggs.length) { + ui.revertMode(); + ui.setOverlayMode(Mode.EGG_LIST); + success = true; + } else { + ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500)); + error = true; + } + break; + case MenuOptions.EGG_GACHA: ui.revertMode(); - ui.setOverlayMode(Mode.EGG_LIST); + ui.setOverlayMode(Mode.EGG_GACHA); success = true; - } else { - ui.showText(i18next.t("menuUiHandler:noEggs"), null, () => ui.showText(""), Utils.fixedInt(1500)); - error = true; - } - break; - case MenuOptions.EGG_GACHA: - ui.revertMode(); - ui.setOverlayMode(Mode.EGG_GACHA); - success = true; - break; - case MenuOptions.MANAGE_DATA: - if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { - this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0, - { - label: loggedInUser?.discordId === "" ? i18next.t("menuUiHandler:linkDiscord") : i18next.t("menuUiHandler:unlinkDiscord"), - handler: () => { - if (loggedInUser?.discordId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); - const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; - const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`; - window.open(discordUrl, "_self"); - return true; - } else { - Utils.apiPost("/auth/discord/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } - updateUserInfo().then(() => this.scene.reset(true, true)); - }); - return true; + break; + case MenuOptions.MANAGE_DATA: + if (!bypassLogin && !this.manageDataConfig.options.some(o => o.label === i18next.t("menuUiHandler:linkDiscord") || o.label === i18next.t("menuUiHandler:unlinkDiscord"))) { + this.manageDataConfig.options.splice(this.manageDataConfig.options.length - 1, 0, + { + label: loggedInUser?.discordId === "" ? i18next.t("menuUiHandler:linkDiscord") : i18next.t("menuUiHandler:unlinkDiscord"), + handler: () => { + if (loggedInUser?.discordId === "") { + const token = Utils.getCookie(Utils.sessionIdKey); + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/discord/callback`); + const discordId = import.meta.env.VITE_DISCORD_CLIENT_ID; + const discordUrl = `https://discord.com/api/oauth2/authorize?client_id=${discordId}&redirect_uri=${redirectUri}&response_type=code&scope=identify&state=${token}&prompt=none`; + window.open(discordUrl, "_self"); + return true; + } else { + Utils.apiPost("/auth/discord/logout", undefined, undefined, true).then(res => { + if (!res.ok) { + console.error(`Unlink failed (${res.status}: ${res.statusText})`); + } + updateUserInfo().then(() => this.scene.reset(true, true)); + }); + return true; + } } - } - }, - { - label: loggedInUser?.googleId === "" ? i18next.t("menuUiHandler:linkGoogle") : i18next.t("menuUiHandler:unlinkGoogle"), - handler: () => { - if (loggedInUser?.googleId === "") { - const token = Utils.getCookie(Utils.sessionIdKey); - const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); - const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; - const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`; - window.open(googleUrl, "_self"); - return true; - } else { - Utils.apiPost("/auth/google/logout", undefined, undefined, true).then(res => { - if (!res.ok) { - console.error(`Unlink failed (${res.status}: ${res.statusText})`); - } - updateUserInfo().then(() => this.scene.reset(true, true)); - }); - return true; + }, + { + label: loggedInUser?.googleId === "" ? i18next.t("menuUiHandler:linkGoogle") : i18next.t("menuUiHandler:unlinkGoogle"), + handler: () => { + if (loggedInUser?.googleId === "") { + const token = Utils.getCookie(Utils.sessionIdKey); + const redirectUri = encodeURIComponent(`${import.meta.env.VITE_SERVER_URL}/auth/google/callback`); + const googleId = import.meta.env.VITE_GOOGLE_CLIENT_ID; + const googleUrl = `https://accounts.google.com/o/oauth2/auth?client_id=${googleId}&response_type=code&redirect_uri=${redirectUri}&scope=openid&state=${token}`; + window.open(googleUrl, "_self"); + return true; + } else { + Utils.apiPost("/auth/google/logout", undefined, undefined, true).then(res => { + if (!res.ok) { + console.error(`Unlink failed (${res.status}: ${res.statusText})`); + } + updateUserInfo().then(() => this.scene.reset(true, true)); + }); + return true; + } } - } - }); - } - ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.manageDataConfig); - success = true; - break; - case MenuOptions.COMMUNITY: - ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.communityConfig); - success = true; - break; - case MenuOptions.SAVE_AND_QUIT: - if (this.scene.currentBattle) { + }); + } + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.manageDataConfig); success = true; - const doSaveQuit = () => { - ui.setMode(Mode.LOADING, { - buttonActions: [], fadeOut: () => - this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => { + break; + case MenuOptions.COMMUNITY: + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, this.communityConfig); + success = true; + break; + case MenuOptions.SAVE_AND_QUIT: + if (this.scene.currentBattle) { + success = true; + const doSaveQuit = () => { + ui.setMode(Mode.LOADING, { + buttonActions: [], fadeOut: () => + this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => { - this.scene.reset(true); - }) + this.scene.reset(true); + }) + }); + }; + if (this.scene.currentBattle.turn > 1) { + ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { + if (!this.active) { + this.showText("", 0); + return; + } + ui.setOverlayMode(Mode.CONFIRM, doSaveQuit, () => { + ui.revertMode(); + this.showText("", 0); + }, false, -98); + }); + } else { + doSaveQuit(); + } + } else { + error = true; + } + break; + case MenuOptions.LOG_OUT: + success = true; + const doLogout = () => { + ui.setMode(Mode.LOADING, { + buttonActions: [], fadeOut: () => Utils.apiFetch("account/logout", true).then(res => { + if (!res.ok) { + console.error(`Log out failed (${res.status}: ${res.statusText})`); + } + Utils.removeCookie(Utils.sessionIdKey); + updateUserInfo().then(() => this.scene.reset(true, true)); + }) }); }; - if (this.scene.currentBattle.turn > 1) { + if (this.scene.currentBattle) { ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { if (!this.active) { this.showText("", 0); return; } - ui.setOverlayMode(Mode.CONFIRM, doSaveQuit, () => { + ui.setOverlayMode(Mode.CONFIRM, doLogout, () => { ui.revertMode(); this.showText("", 0); }, false, -98); }); } else { - doSaveQuit(); + doLogout(); } - } else { - error = true; - } - break; - case MenuOptions.LOG_OUT: - success = true; - const doLogout = () => { - ui.setMode(Mode.LOADING, { - buttonActions: [], fadeOut: () => Utils.apiFetch("account/logout", true).then(res => { - if (!res.ok) { - console.error(`Log out failed (${res.status}: ${res.statusText})`); - } - Utils.removeCookie(Utils.sessionIdKey); - updateUserInfo().then(() => this.scene.reset(true, true)); - }) - }); - }; - if (this.scene.currentBattle) { - ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { - if (!this.active) { - this.showText("", 0); - return; - } - ui.setOverlayMode(Mode.CONFIRM, doLogout, () => { - ui.revertMode(); - this.showText("", 0); - }, false, -98); - }); - } else { - doLogout(); - } - break; + break; } } else if (button === Button.CANCEL) { success = true; @@ -620,20 +620,20 @@ export default class MenuUiHandler extends MessageUiHandler { }); } else { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else { - success = this.setCursor(this.menuOptions.length - 1); - } - break; - case Button.DOWN: - if (this.cursor + 1 < this.menuOptions.length) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(0); - } - break; + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else { + success = this.setCursor(this.menuOptions.length - 1); + } + break; + case Button.DOWN: + if (this.cursor + 1 < this.menuOptions.length) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(0); + } + break; } } diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index f1b8ed981ee..5ae4707e329 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -56,18 +56,18 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { let actionMatch: RegExpExecArray | null; while ((actionMatch = actionPattern.exec(text))) { switch (actionMatch[1]) { - case "c": - charVarMap.set(actionMatch.index, actionMatch[2]); - break; - case "d": - delayMap.set(actionMatch.index, parseInt(actionMatch[2])); - break; - case "s": - soundMap.set(actionMatch.index, actionMatch[2]); - break; - case "f": - fadeMap.set(actionMatch.index, parseInt(actionMatch[2])); - break; + case "c": + charVarMap.set(actionMatch.index, actionMatch[2]); + break; + case "d": + delayMap.set(actionMatch.index, parseInt(actionMatch[2])); + break; + case "s": + soundMap.set(actionMatch.index, actionMatch[2]); + break; + case "f": + fadeMap.set(actionMatch.index, parseInt(actionMatch[2])); + break; } text = text.slice(0, actionMatch.index) + text.slice(actionMatch.index + actionMatch[2].length + 4); } diff --git a/src/ui/modifier-select-ui-handler.ts b/src/ui/modifier-select-ui-handler.ts index 1948d75ac4c..3f89ebe415f 100644 --- a/src/ui/modifier-select-ui-handler.ts +++ b/src/ui/modifier-select-ui-handler.ts @@ -357,79 +357,79 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { } } else { switch (button) { - case Button.UP: - if (this.rowCursor === 0 && this.cursor === 3) { - success = this.setCursor(0); - } else if (this.rowCursor < this.shopOptionsRows.length + 1) { - success = this.setRowCursor(this.rowCursor + 1); - } - break; - case Button.DOWN: - if (this.rowCursor) { - success = this.setRowCursor(this.rowCursor - 1); - } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { - success = this.setCursor(3); - } - break; - case Button.LEFT: - if (!this.rowCursor) { - switch (this.cursor) { - case 0: - success = false; - break; - case 1: - if (this.lockRarityButtonContainer.visible) { - success = this.setCursor(3); - } else { - success = this.rerollButtonContainer.visible && this.setCursor(0); - } - break; - case 2: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else if (this.rerollButtonContainer.visible) { - success = this.setCursor(0); - } else { - success = false; - } - break; + case Button.UP: + if (this.rowCursor === 0 && this.cursor === 3) { + success = this.setCursor(0); + } else if (this.rowCursor < this.shopOptionsRows.length + 1) { + success = this.setRowCursor(this.rowCursor + 1); } - } else if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { - success = this.setRowCursor(0); - } - break; - case Button.RIGHT: - if (!this.rowCursor) { - switch (this.cursor) { - case 0: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; - case 1: - success = this.setCursor(2); - break; - case 2: - success = false; - break; - case 3: - if (this.transferButtonContainer.visible) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; + break; + case Button.DOWN: + if (this.rowCursor) { + success = this.setRowCursor(this.rowCursor - 1); + } else if (this.lockRarityButtonContainer.visible && this.cursor === 0) { + success = this.setCursor(3); } - } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { - success = this.setCursor(this.cursor + 1); - } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { - success = this.setRowCursor(0); - } - break; + break; + case Button.LEFT: + if (!this.rowCursor) { + switch (this.cursor) { + case 0: + success = false; + break; + case 1: + if (this.lockRarityButtonContainer.visible) { + success = this.setCursor(3); + } else { + success = this.rerollButtonContainer.visible && this.setCursor(0); + } + break; + case 2: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else if (this.rerollButtonContainer.visible) { + success = this.setCursor(0); + } else { + success = false; + } + break; + } + } else if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.rowCursor === 1 && this.rerollButtonContainer.visible) { + success = this.setRowCursor(0); + } + break; + case Button.RIGHT: + if (!this.rowCursor) { + switch (this.cursor) { + case 0: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + case 1: + success = this.setCursor(2); + break; + case 2: + success = false; + break; + case 3: + if (this.transferButtonContainer.visible) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + } + } else if (this.cursor < this.getRowItems(this.rowCursor) - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.rowCursor === 1 && this.transferButtonContainer.visible) { + success = this.setRowCursor(0); + } + break; } } @@ -527,12 +527,12 @@ export default class ModifierSelectUiHandler extends AwaitableUiHandler { private getRowItems(rowCursor: integer): integer { switch (rowCursor) { - case 0: - return 3; - case 1: - return this.options.length; - default: - return this.shopOptionsRows[this.shopOptionsRows.length - (rowCursor - 1)].length; + case 0: + return 3; + case 1: + return this.options.length; + default: + return this.shopOptionsRows[this.shopOptionsRows.length - (rowCursor - 1)].length; } } diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index 0a838707432..b568f8b71de 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -159,16 +159,16 @@ export default class MysteryEncounterUiHandler extends UiHandler { } } else { switch (this.optionsContainer.getAll()?.length) { - default: - case 3: - success = this.handleTwoOptionMoveInput(button); - break; - case 4: - success = this.handleThreeOptionMoveInput(button); - break; - case 5: - success = this.handleFourOptionMoveInput(button); - break; + default: + case 3: + success = this.handleTwoOptionMoveInput(button); + break; + case 4: + success = this.handleThreeOptionMoveInput(button); + break; + case 5: + success = this.handleFourOptionMoveInput(button); + break; } this.displayOptionTooltip(); @@ -185,26 +185,26 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor < this.viewPartyIndex) { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } - break; - case Button.LEFT: - if (cursor > 0) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor < this.viewPartyIndex) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor < this.viewPartyIndex) { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } + break; + case Button.LEFT: + if (cursor > 0) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor < this.viewPartyIndex) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -214,34 +214,34 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor === 2) { - success = this.setCursor(cursor - 2); - } else { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else { - success = this.setCursor(2); - } - break; - case Button.LEFT: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else if (cursor === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor === 1) { - success = this.setCursor(this.viewPartyIndex); - } else if (cursor < 1) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor === 2) { + success = this.setCursor(cursor - 2); + } else { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else { + success = this.setCursor(2); + } + break; + case Button.LEFT: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else if (cursor === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor === 1) { + success = this.setCursor(this.viewPartyIndex); + } else if (cursor < 1) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -251,34 +251,34 @@ export default class MysteryEncounterUiHandler extends UiHandler { let success = false; const cursor = this.getCursor(); switch (button) { - case Button.UP: - if (cursor >= 2 && cursor !== this.viewPartyIndex) { - success = this.setCursor(cursor - 2); - } else { - success = this.setCursor(this.viewPartyIndex); - } - break; - case Button.DOWN: - if (cursor <= 1) { - success = this.setCursor(cursor + 2); - } else if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } - break; - case Button.LEFT: - if (cursor === this.viewPartyIndex) { - success = this.setCursor(1); - } else if (cursor % 2 === 1) { - success = this.setCursor(cursor - 1); - } - break; - case Button.RIGHT: - if (cursor === 1) { - success = this.setCursor(this.viewPartyIndex); - } else if (cursor % 2 === 0 && cursor !== this.viewPartyIndex) { - success = this.setCursor(cursor + 1); - } - break; + case Button.UP: + if (cursor >= 2 && cursor !== this.viewPartyIndex) { + success = this.setCursor(cursor - 2); + } else { + success = this.setCursor(this.viewPartyIndex); + } + break; + case Button.DOWN: + if (cursor <= 1) { + success = this.setCursor(cursor + 2); + } else if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } + break; + case Button.LEFT: + if (cursor === this.viewPartyIndex) { + success = this.setCursor(1); + } else if (cursor % 2 === 1) { + success = this.setCursor(cursor - 1); + } + break; + case Button.RIGHT: + if (cursor === 1) { + success = this.setCursor(this.viewPartyIndex); + } else if (cursor % 2 === 0 && cursor !== this.viewPartyIndex) { + success = this.setCursor(cursor + 1); + } + break; } return success; @@ -351,16 +351,16 @@ export default class MysteryEncounterUiHandler extends UiHandler { let optionText: BBCodeText; switch (this.encounterOptions.length) { - default: - case 2: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; - case 3: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; - case 4: - optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); - break; + default: + case 2: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; + case 3: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; + case 4: + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { fontSize: "80px", lineSpacing: -8 }); + break; } this.optionsMeetsReqs.push(option.meetsRequirements(this.scene)); diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index cfc5e146f08..e96fde8d54f 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -539,42 +539,42 @@ export default class PartyUiHandler extends MessageUiHandler { return true; } else { switch (button) { - case Button.LEFT: + case Button.LEFT: /** Decrease quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] : this.transferQuantities[option] - 1; - this.updateOptions(); - success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.RIGHT: + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === 1 ? this.transferQuantitiesMax[option] : this.transferQuantities[option] - 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ + } + break; + case Button.RIGHT: /** Increase quantity for the current item and update UI */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 : this.transferQuantities[option] + 1; - this.updateOptions(); - success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ - } - break; - case Button.UP: - /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + this.transferQuantities[option] = this.transferQuantities[option] === this.transferQuantitiesMax[option] ? 1 : this.transferQuantities[option] + 1; + this.updateOptions(); + success = this.setCursor(this.optionsCursor); /** Place again the cursor at the same position. Necessary, otherwise the cursor disappears */ } - this.updateOptions(); - } - success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); /** Move cursor */ - break; - case Button.DOWN: + break; + case Button.UP: /** If currently selecting items to transfer, reset quantity selection */ - if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { - if (option !== PartyOption.ALL) { - this.transferQuantities[option] = this.transferQuantitiesMax[option]; + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); } - this.updateOptions(); - } - success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ - break; + success = this.setCursor(this.optionsCursor ? this.optionsCursor - 1 : this.options.length - 1); /** Move cursor */ + break; + case Button.DOWN: + /** If currently selecting items to transfer, reset quantity selection */ + if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER) { + if (option !== PartyOption.ALL) { + this.transferQuantities[option] = this.transferQuantitiesMax[option]; + } + this.updateOptions(); + } + success = this.setCursor(this.optionsCursor < this.options.length - 1 ? this.optionsCursor + 1 : 0); /** Move cursor */ + break; } // show move description @@ -631,28 +631,28 @@ export default class PartyUiHandler extends MessageUiHandler { const battlerCount = this.scene.currentBattle.getBattlerCount(); switch (button) { - case Button.UP: - success = this.setCursor(this.cursor ? this.cursor < 6 ? this.cursor - 1 : slotCount - 1 : 6); - break; - case Button.DOWN: - success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0); - break; - case Button.LEFT: - if (this.cursor >= battlerCount && this.cursor <= 6) { - success = this.setCursor(0); - } - break; - case Button.RIGHT: - if (slotCount === battlerCount) { - success = this.setCursor(6); + case Button.UP: + success = this.setCursor(this.cursor ? this.cursor < 6 ? this.cursor - 1 : slotCount - 1 : 6); break; - } else if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { - success = this.setCursor(2); + case Button.DOWN: + success = this.setCursor(this.cursor < 6 ? this.cursor < slotCount - 1 ? this.cursor + 1 : 6 : 0); break; - } else if (slotCount > battlerCount && this.cursor < battlerCount) { - success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); + case Button.LEFT: + if (this.cursor >= battlerCount && this.cursor <= 6) { + success = this.setCursor(0); + } break; - } + case Button.RIGHT: + if (slotCount === battlerCount) { + success = this.setCursor(6); + break; + } else if (battlerCount >= 2 && slotCount > battlerCount && this.getCursor() === 0 && this.lastCursor === 1) { + success = this.setCursor(2); + break; + } else if (slotCount > battlerCount && this.cursor < battlerCount) { + success = this.setCursor(this.lastCursor < 6 ? this.lastCursor || battlerCount : battlerCount); + break; + } } } @@ -773,19 +773,19 @@ export default class PartyUiHandler extends MessageUiHandler { let optionsMessage = i18next.t("partyUiHandler:doWhatWithThisPokemon"); switch (this.partyUiMode) { - case PartyUiMode.MOVE_MODIFIER: - optionsMessage = i18next.t("partyUiHandler:selectAMove"); - break; - case PartyUiMode.MODIFIER_TRANSFER: - if (!this.transferMode) { - optionsMessage = i18next.t("partyUiHandler:changeQuantity"); - } - break; - case PartyUiMode.SPLICE: - if (!this.transferMode) { - optionsMessage = i18next.t("partyUiHandler:selectAnotherPokemonToSplice"); - } - break; + case PartyUiMode.MOVE_MODIFIER: + optionsMessage = i18next.t("partyUiHandler:selectAMove"); + break; + case PartyUiMode.MODIFIER_TRANSFER: + if (!this.transferMode) { + optionsMessage = i18next.t("partyUiHandler:changeQuantity"); + } + break; + case PartyUiMode.SPLICE: + if (!this.transferMode) { + optionsMessage = i18next.t("partyUiHandler:selectAnotherPokemonToSplice"); + } + break; } this.showText(optionsMessage, 0); @@ -829,64 +829,64 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.partyUiMode !== PartyUiMode.MOVE_MODIFIER && this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && (this.transferMode || this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER)) { switch (this.partyUiMode) { - case PartyUiMode.SWITCH: - case PartyUiMode.FAINT_SWITCH: - case PartyUiMode.POST_BATTLE_SWITCH: - if (this.cursor >= this.scene.currentBattle.getBattlerCount()) { - const allowBatonModifierSwitch = + case PartyUiMode.SWITCH: + case PartyUiMode.FAINT_SWITCH: + case PartyUiMode.POST_BATTLE_SWITCH: + if (this.cursor >= this.scene.currentBattle.getBattlerCount()) { + const allowBatonModifierSwitch = this.partyUiMode !== PartyUiMode.FAINT_SWITCH && this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.scene.getPlayerField()[this.fieldIndex].id); - const moveHistory = this.scene.getPlayerField()[this.fieldIndex].getMoveHistory(); - const isBatonPassMove = this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; + const moveHistory = this.scene.getPlayerField()[this.fieldIndex].getMoveHistory(); + const isBatonPassMove = this.partyUiMode === PartyUiMode.FAINT_SWITCH && moveHistory.length && allMoves[moveHistory[moveHistory.length - 1].move].getAttrs(ForceSwitchOutAttr)[0]?.isBatonPass() && moveHistory[moveHistory.length - 1].result === MoveResult.SUCCESS; - // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true - // at the same time, because they both explicitly check for a mutually - // exclusive partyUiMode. But better safe than sorry. - this.options.push(isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT); - if (allowBatonModifierSwitch && !isBatonPassMove) { + // isBatonPassMove and allowBatonModifierSwitch shouldn't ever be true + // at the same time, because they both explicitly check for a mutually + // exclusive partyUiMode. But better safe than sorry. + this.options.push(isBatonPassMove && !allowBatonModifierSwitch ? PartyOption.PASS_BATON : PartyOption.SEND_OUT); + if (allowBatonModifierSwitch && !isBatonPassMove) { // the BATON modifier gives an extra switch option for // pokemon-command switches, allowing buffs to be optionally passed - this.options.push(PartyOption.PASS_BATON); + this.options.push(PartyOption.PASS_BATON); + } } - } - break; - case PartyUiMode.REVIVAL_BLESSING: - this.options.push(PartyOption.REVIVE); - break; - case PartyUiMode.MODIFIER: - this.options.push(PartyOption.APPLY); - break; - case PartyUiMode.TM_MODIFIER: - this.options.push(PartyOption.TEACH); - break; - case PartyUiMode.MODIFIER_TRANSFER: - this.options.push(PartyOption.TRANSFER); - break; - case PartyUiMode.SPLICE: - if (this.transferMode) { - if (this.cursor !== this.transferCursor) { - this.options.push(PartyOption.SPLICE); - } - } else { + break; + case PartyUiMode.REVIVAL_BLESSING: + this.options.push(PartyOption.REVIVE); + break; + case PartyUiMode.MODIFIER: this.options.push(PartyOption.APPLY); - } - break; - case PartyUiMode.RELEASE: - this.options.push(PartyOption.RELEASE); - break; - case PartyUiMode.CHECK: - if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { - formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); - for (let i = 0; i < formChangeItemModifiers.length; i++) { - this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + break; + case PartyUiMode.TM_MODIFIER: + this.options.push(PartyOption.TEACH); + break; + case PartyUiMode.MODIFIER_TRANSFER: + this.options.push(PartyOption.TRANSFER); + break; + case PartyUiMode.SPLICE: + if (this.transferMode) { + if (this.cursor !== this.transferCursor) { + this.options.push(PartyOption.SPLICE); + } + } else { + this.options.push(PartyOption.APPLY); } - } - break; - case PartyUiMode.SELECT: - this.options.push(PartyOption.SELECT); - break; + break; + case PartyUiMode.RELEASE: + this.options.push(PartyOption.RELEASE); + break; + case PartyUiMode.CHECK: + if (this.scene.getCurrentPhase() instanceof SelectModifierPhase) { + formChangeItemModifiers = this.getFormChangeItemsModifiers(pokemon); + for (let i = 0; i < formChangeItemModifiers.length; i++) { + this.options.push(PartyOption.FORM_CHANGE_ITEM + i); + } + } + break; + case PartyUiMode.SELECT: + this.options.push(PartyOption.SELECT); + break; } this.options.push(PartyOption.SUMMARY); @@ -962,33 +962,33 @@ export default class PartyUiHandler extends MessageUiHandler { optionName = "↓"; } else if ((this.partyUiMode !== PartyUiMode.REMEMBER_MOVE_MODIFIER && (this.partyUiMode !== PartyUiMode.MODIFIER_TRANSFER || this.transferMode)) || option === PartyOption.CANCEL) { switch (option) { - case PartyOption.MOVE_1: - case PartyOption.MOVE_2: - case PartyOption.MOVE_3: - case PartyOption.MOVE_4: - const move = pokemon.moveset[option - PartyOption.MOVE_1]!; // TODO: is the bang correct? - if (this.showMovePp) { - const maxPP = move.getMovePp(); - const currPP = maxPP - move.ppUsed; - optionName = `${move.getName()} ${currPP}/${maxPP}`; - } else { - optionName = move.getName(); - } - break; - default: - if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { - const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; - optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; - } else if (option === PartyOption.UNPAUSE_EVOLUTION) { - optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; - } else { - if (this.localizedOptions.includes(option)) { - optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); + case PartyOption.MOVE_1: + case PartyOption.MOVE_2: + case PartyOption.MOVE_3: + case PartyOption.MOVE_4: + const move = pokemon.moveset[option - PartyOption.MOVE_1]!; // TODO: is the bang correct? + if (this.showMovePp) { + const maxPP = move.getMovePp(); + const currPP = maxPP - move.ppUsed; + optionName = `${move.getName()} ${currPP}/${maxPP}`; } else { - optionName = Utils.toReadableString(PartyOption[option]); + optionName = move.getName(); } - } - break; + break; + default: + if (formChangeItemModifiers && option >= PartyOption.FORM_CHANGE_ITEM) { + const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM]; + optionName = `${modifier.active ? i18next.t("partyUiHandler:DEACTIVATE") : i18next.t("partyUiHandler:ACTIVATE")} ${modifier.type.name}`; + } else if (option === PartyOption.UNPAUSE_EVOLUTION) { + optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:UNPAUSE_EVOLUTION") : i18next.t("partyUiHandler:PAUSE_EVOLUTION")}`; + } else { + if (this.localizedOptions.includes(option)) { + optionName = i18next.t(`partyUiHandler:${PartyOption[option]}`); + } else { + optionName = Utils.toReadableString(PartyOption[option]); + } + } + break; } } else if (this.partyUiMode === PartyUiMode.REMEMBER_MOVE_MODIFIER) { const move = learnableLevelMoves[option]; diff --git a/src/ui/pokemon-icon-anim-handler.ts b/src/ui/pokemon-icon-anim-handler.ts index d6796d5cb5d..c7a24f69200 100644 --- a/src/ui/pokemon-icon-anim-handler.ts +++ b/src/ui/pokemon-icon-anim-handler.ts @@ -39,12 +39,12 @@ export default class PokemonIconAnimHandler { getModeYDelta(mode: PokemonIconAnimMode): number { switch (mode) { - case PokemonIconAnimMode.NONE: - return 0; - case PokemonIconAnimMode.PASSIVE: - return -1; - case PokemonIconAnimMode.ACTIVE: - return -2; + case PokemonIconAnimMode.NONE: + return 0; + case PokemonIconAnimMode.PASSIVE: + return -1; + case PokemonIconAnimMode.ACTIVE: + return -2; } } diff --git a/src/ui/registration-form-ui-handler.ts b/src/ui/registration-form-ui-handler.ts index c94d060c0b7..2f8486bcdb3 100644 --- a/src/ui/registration-form-ui-handler.ts +++ b/src/ui/registration-form-ui-handler.ts @@ -50,12 +50,12 @@ export default class RegistrationFormUiHandler extends FormModalUiHandler { error = error.slice(0, colonIndex); } switch (error) { - case "invalid username": - return i18next.t("menu:invalidRegisterUsername"); - case "invalid password": - return i18next.t("menu:invalidRegisterPassword"); - case "failed to add account record": - return i18next.t("menu:usernameAlreadyUsed"); + case "invalid username": + return i18next.t("menu:invalidRegisterUsername"); + case "invalid password": + return i18next.t("menu:invalidRegisterPassword"); + case "failed to add account record": + return i18next.t("menu:usernameAlreadyUsed"); } return super.getReadableErrorMessage(error); diff --git a/src/ui/run-history-ui-handler.ts b/src/ui/run-history-ui-handler.ts index 20de7fd832c..061f15d0956 100644 --- a/src/ui/run-history-ui-handler.ts +++ b/src/ui/run-history-ui-handler.ts @@ -118,28 +118,28 @@ export default class RunHistoryUiHandler extends MessageUiHandler { } } else if (this.runs.length > 0) { switch (button) { - case Button.UP: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else if (this.runs.length > 1) { + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else if (this.runs.length > 1) { // wrap around to the bottom - success = this.setCursor(Math.min(this.runs.length - 1, this.maxRows - 1)); - success = this.setScrollCursor(Math.max(0, this.runs.length - this.maxRows)) || success; - } - break; - case Button.DOWN: - if (this.cursor < Math.min(this.maxRows - 1, this.runs.length - this.scrollCursor - 1)) { - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.runs.length - this.maxRows) { - success = this.setScrollCursor(this.scrollCursor + 1); - } else if (this.runs.length > 1) { + success = this.setCursor(Math.min(this.runs.length - 1, this.maxRows - 1)); + success = this.setScrollCursor(Math.max(0, this.runs.length - this.maxRows)) || success; + } + break; + case Button.DOWN: + if (this.cursor < Math.min(this.maxRows - 1, this.runs.length - this.scrollCursor - 1)) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.runs.length - this.maxRows) { + success = this.setScrollCursor(this.scrollCursor + 1); + } else if (this.runs.length > 1) { // wrap around to the top - success = this.setCursor(0); - success = this.setScrollCursor(0) || success; - } - break; + success = this.setCursor(0); + success = this.setScrollCursor(0) || success; + } + break; } } @@ -333,19 +333,19 @@ class RunEntryContainer extends Phaser.GameObjects.Container { const gameModeLabel = addTextObject(this.scene, 8, 19, "", TextStyle.WINDOW); let mode = ""; switch (data.gameMode) { - case GameModes.DAILY: - mode = i18next.t("gameMode:dailyRun"); - break; - case GameModes.SPLICED_ENDLESS: - case GameModes.ENDLESS: - mode = i18next.t("gameMode:endless"); - break; - case GameModes.CLASSIC: - mode = i18next.t("gameMode:classic"); - break; - case GameModes.CHALLENGE: - mode = i18next.t("gameMode:challenge"); - break; + case GameModes.DAILY: + mode = i18next.t("gameMode:dailyRun"); + break; + case GameModes.SPLICED_ENDLESS: + case GameModes.ENDLESS: + mode = i18next.t("gameMode:endless"); + break; + case GameModes.CLASSIC: + mode = i18next.t("gameMode:classic"); + break; + case GameModes.CHALLENGE: + mode = i18next.t("gameMode:challenge"); + break; } gameModeLabel.appendText(mode, false); if (data.gameMode === GameModes.SPLICED_ENDLESS) { diff --git a/src/ui/run-info-ui-handler.ts b/src/ui/run-info-ui-handler.ts index 1976d5a997b..0ca47241136 100644 --- a/src/ui/run-info-ui-handler.ts +++ b/src/ui/run-info-ui-handler.ts @@ -229,14 +229,14 @@ export default class RunInfoUiHandler extends UiHandler { // Wild - Single and Doubles if (this.runInfo.battleType === BattleType.WILD || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && !this.runInfo.trainer)) { switch (this.runInfo.enemyParty.length) { - case 1: + case 1: // Wild - Singles - this.parseWildSingleDefeat(enemyContainer); - break; - case 2: + this.parseWildSingleDefeat(enemyContainer); + break; + case 2: //Wild - Doubles - this.parseWildDoubleDefeat(enemyContainer); - break; + this.parseWildDoubleDefeat(enemyContainer); + break; } } else if (this.runInfo.battleType === BattleType.TRAINER || (this.runInfo.battleType === BattleType.MYSTERY_ENCOUNTER && this.runInfo.trainer)) { this.parseTrainerDefeat(enemyContainer); @@ -474,33 +474,33 @@ export default class RunInfoUiHandler extends UiHandler { modeText.setPosition(7, 5); modeText.appendText(i18next.t("runHistory:mode") + ": ", false); switch (this.runInfo.gameMode) { - case GameModes.DAILY: - modeText.appendText(`${i18next.t("gameMode:dailyRun")}`, false); - break; - case GameModes.SPLICED_ENDLESS: - modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); - break; - case GameModes.CHALLENGE: - modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); - modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); - modeText.setWrapMode(1); // wrap by word - modeText.setWrapWidth(500); - const rules: string[] = this.challengeParser(); - if (rules) { - for (let i = 0; i < rules.length; i++) { - if (i > 0) { - modeText.appendText(" + ", false); + case GameModes.DAILY: + modeText.appendText(`${i18next.t("gameMode:dailyRun")}`, false); + break; + case GameModes.SPLICED_ENDLESS: + modeText.appendText(`${i18next.t("gameMode:endlessSpliced")}`, false); + break; + case GameModes.CHALLENGE: + modeText.appendText(`${i18next.t("gameMode:challenge")}`, false); + modeText.appendText(`${i18next.t("runHistory:challengeRules")}: `); + modeText.setWrapMode(1); // wrap by word + modeText.setWrapWidth(500); + const rules: string[] = this.challengeParser(); + if (rules) { + for (let i = 0; i < rules.length; i++) { + if (i > 0) { + modeText.appendText(" + ", false); + } + modeText.appendText(rules[i], false); } - modeText.appendText(rules[i], false); } - } - break; - case GameModes.ENDLESS: - modeText.appendText(`${i18next.t("gameMode:endless")}`, false); - break; - case GameModes.CLASSIC: - modeText.appendText(`${i18next.t("gameMode:classic")}`, false); - break; + break; + case GameModes.ENDLESS: + modeText.appendText(`${i18next.t("gameMode:endless")}`, false); + break; + case GameModes.CLASSIC: + modeText.appendText(`${i18next.t("gameMode:classic")}`, false); + break; } // If the player achieves a personal best in Endless, the mode text will be tinted similarly to SSS luck to celebrate their achievement. @@ -577,23 +577,23 @@ export default class RunInfoUiHandler extends UiHandler { for (let i = 0; i < this.runInfo.challenges.length; i++) { if (this.runInfo.challenges[i].value !== 0) { switch (this.runInfo.challenges[i].id) { - case Challenges.SINGLE_GENERATION: - rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); - break; - case Challenges.SINGLE_TYPE: - const typeRule = Type[this.runInfo.challenges[i].value - 1]; - const typeTextColor = `[color=${TypeColor[typeRule]}]`; - const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; - const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; - rules.push(typeText); - 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; + case Challenges.SINGLE_GENERATION: + rules.push(i18next.t(`runHistory:challengeMonoGen${this.runInfo.challenges[i].value}`)); + break; + case Challenges.SINGLE_TYPE: + const typeRule = Type[this.runInfo.challenges[i].value - 1]; + const typeTextColor = `[color=${TypeColor[typeRule]}]`; + const typeShadowColor = `[shadow=${TypeShadow[typeRule]}]`; + const typeText = typeTextColor + typeShadowColor + i18next.t(`pokemonInfo:Type.${typeRule}`)! + "[/color]" + "[/shadow]"; + rules.push(typeText); + 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; } } } @@ -911,36 +911,36 @@ export default class RunInfoUiHandler extends UiHandler { const error = false; switch (button) { - case Button.CANCEL: - success = true; - if (this.pageMode === RunInfoUiMode.MAIN) { - this.runInfoContainer.removeAll(true); - this.runResultContainer.removeAll(true); - this.partyContainer.removeAll(true); - this.runContainer.removeAll(true); - if (this.isVictory) { - this.hallofFameContainer.removeAll(true); + case Button.CANCEL: + success = true; + if (this.pageMode === RunInfoUiMode.MAIN) { + this.runInfoContainer.removeAll(true); + this.runResultContainer.removeAll(true); + this.partyContainer.removeAll(true); + this.runContainer.removeAll(true); + if (this.isVictory) { + this.hallofFameContainer.removeAll(true); + } + super.clear(); + this.runContainer.setVisible(false); + ui.revertMode(); + } else if (this.pageMode === RunInfoUiMode.HALL_OF_FAME) { + this.hallofFameContainer.setVisible(false); + this.pageMode = RunInfoUiMode.MAIN; + } else if (this.pageMode === RunInfoUiMode.ENDING_ART) { + this.endCardContainer.setVisible(false); + this.runContainer.remove(this.endCardContainer); + this.pageMode = RunInfoUiMode.MAIN; } - super.clear(); - this.runContainer.setVisible(false); - ui.revertMode(); - } else if (this.pageMode === RunInfoUiMode.HALL_OF_FAME) { - this.hallofFameContainer.setVisible(false); - this.pageMode = RunInfoUiMode.MAIN; - } else if (this.pageMode === RunInfoUiMode.ENDING_ART) { - this.endCardContainer.setVisible(false); - this.runContainer.remove(this.endCardContainer); - this.pageMode = RunInfoUiMode.MAIN; - } - break; - case Button.DOWN: - case Button.UP: - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - case Button.CYCLE_ABILITY: - this.buttonCycleOption(button); - break; + break; + case Button.DOWN: + case Button.UP: + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + case Button.CYCLE_ABILITY: + this.buttonCycleOption(button); + break; } if (success) { @@ -960,40 +960,40 @@ export default class RunInfoUiHandler extends UiHandler { */ private buttonCycleOption(button: Button) { switch (button) { - case Button.CYCLE_FORM: - if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) { - if (!this.endCardContainer || !this.endCardContainer.visible) { - this.createVictorySplash(); - this.endCardContainer.setVisible(true); - this.runContainer.add(this.endCardContainer); - this.pageMode = RunInfoUiMode.ENDING_ART; - } else { - this.endCardContainer.setVisible(false); - this.runContainer.remove(this.endCardContainer); - this.pageMode = RunInfoUiMode.MAIN; + case Button.CYCLE_FORM: + if (this.isVictory && this.pageMode !== RunInfoUiMode.HALL_OF_FAME) { + if (!this.endCardContainer || !this.endCardContainer.visible) { + this.createVictorySplash(); + this.endCardContainer.setVisible(true); + this.runContainer.add(this.endCardContainer); + this.pageMode = RunInfoUiMode.ENDING_ART; + } else { + this.endCardContainer.setVisible(false); + this.runContainer.remove(this.endCardContainer); + this.pageMode = RunInfoUiMode.MAIN; + } } - } - break; - case Button.CYCLE_SHINY: - if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) { - if (!this.hallofFameContainer.visible) { - this.hallofFameContainer.setVisible(true); - this.pageMode = RunInfoUiMode.HALL_OF_FAME; - } else { - this.hallofFameContainer.setVisible(false); - this.pageMode = RunInfoUiMode.MAIN; + break; + case Button.CYCLE_SHINY: + if (this.isVictory && this.pageMode !== RunInfoUiMode.ENDING_ART) { + if (!this.hallofFameContainer.visible) { + this.hallofFameContainer.setVisible(true); + this.pageMode = RunInfoUiMode.HALL_OF_FAME; + } else { + this.hallofFameContainer.setVisible(false); + this.pageMode = RunInfoUiMode.MAIN; + } } - } - break; - case Button.CYCLE_ABILITY: - if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { - if (this.partyVisibility) { - this.showParty(false); - } else { - this.showParty(true); + break; + case Button.CYCLE_ABILITY: + if (this.runInfo.modifiers.length !== 0 && this.pageMode === RunInfoUiMode.MAIN) { + if (this.partyVisibility) { + this.showParty(false); + } else { + this.showParty(true); + } } - } - break; + break; } } } diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 2e664db8d43..7bfecb1f99b 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -106,40 +106,40 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { error = true; } else { switch (this.uiMode) { - case SaveSlotUiMode.LOAD: - this.saveSlotSelectCallback = null; - originalCallback && originalCallback(cursor); - break; - case SaveSlotUiMode.SAVE: - const saveAndCallback = () => { - const originalCallback = this.saveSlotSelectCallback; + case SaveSlotUiMode.LOAD: this.saveSlotSelectCallback = null; - ui.revertMode(); - ui.showText("", 0); - ui.setMode(Mode.MESSAGE); originalCallback && originalCallback(cursor); - }; - if (this.sessionSlots[cursor].hasData) { - ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { - ui.setOverlayMode(Mode.CONFIRM, () => { - this.scene.gameData.deleteSession(cursor).then(response => { - if (response === false) { - this.scene.reset(true); - } else { - saveAndCallback(); - } - }); - }, () => { - ui.revertMode(); - ui.showText("", 0); - }, false, 0, 19, 2000); - }); - } else if (this.sessionSlots[cursor].hasData === false) { - saveAndCallback(); - } else { - return false; - } - break; + break; + case SaveSlotUiMode.SAVE: + const saveAndCallback = () => { + const originalCallback = this.saveSlotSelectCallback; + this.saveSlotSelectCallback = null; + ui.revertMode(); + ui.showText("", 0); + ui.setMode(Mode.MESSAGE); + originalCallback && originalCallback(cursor); + }; + if (this.sessionSlots[cursor].hasData) { + ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { + ui.setOverlayMode(Mode.CONFIRM, () => { + this.scene.gameData.deleteSession(cursor).then(response => { + if (response === false) { + this.scene.reset(true); + } else { + saveAndCallback(); + } + }); + }, () => { + ui.revertMode(); + ui.showText("", 0); + }, false, 0, 19, 2000); + }); + } else if (this.sessionSlots[cursor].hasData === false) { + saveAndCallback(); + } else { + return false; + } + break; } success = true; } @@ -151,26 +151,26 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } else { const cursorPosition = this.cursor + this.scrollCursor; switch (button) { - case Button.UP: - if (this.cursor) { + case Button.UP: + if (this.cursor) { // Check to prevent cursor from accessing a negative index - success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); - } else if (this.scrollCursor) { - success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); - } - break; - case Button.DOWN: - if (this.cursor < (SLOTS_ON_SCREEN - 1)) { - success = this.setCursor(this.cursor + 1, cursorPosition); - } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { - success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); - } - break; - case Button.RIGHT: - if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { - this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); - success = true; - } + success = (this.cursor === 0) ? this.setCursor(this.cursor) : this.setCursor(this.cursor - 1, cursorPosition); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1, cursorPosition); + } + break; + case Button.DOWN: + if (this.cursor < (SLOTS_ON_SCREEN - 1)) { + success = this.setCursor(this.cursor + 1, cursorPosition); + } else if (this.scrollCursor < SESSION_SLOTS_COUNT - SLOTS_ON_SCREEN) { + success = this.setScrollCursor(this.scrollCursor + 1, cursorPosition); + } + break; + case Button.RIGHT: + if (this.sessionSlots[cursorPosition].hasData && this.sessionSlots[cursorPosition].saveData) { + this.scene.ui.setOverlayMode(Mode.RUN_INFO, this.sessionSlots[cursorPosition].saveData, RunDisplayMode.SESSION_PREVIEW); + success = true; + } } } diff --git a/src/ui/scrollable-grid-handler.ts b/src/ui/scrollable-grid-handler.ts index f96f3c995b7..cced92a2083 100644 --- a/src/ui/scrollable-grid-handler.ts +++ b/src/ui/scrollable-grid-handler.ts @@ -106,48 +106,48 @@ export default class ScrollableGridUiHandler { const itemOffset = this.scrollCursor * this.COLUMNS; const lastVisibleIndex = Math.min(this.totalElements - 1, this.totalElements - maxScrollCursor * this.COLUMNS - 1); switch (button) { - case Button.UP: - if (currentRowIndex > 0) { - success = this.setCursor(this.cursor - this.COLUMNS); - } else if (this.scrollCursor > 0) { - success = this.setScrollCursor(this.scrollCursor - 1); - } else { + case Button.UP: + if (currentRowIndex > 0) { + success = this.setCursor(this.cursor - this.COLUMNS); + } else if (this.scrollCursor > 0) { + success = this.setScrollCursor(this.scrollCursor - 1); + } else { // wrap around to the last row - let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS; - if (newCursor > lastVisibleIndex) { - newCursor -= this.COLUMNS; + let newCursor = this.cursor + (onScreenRows - 1) * this.COLUMNS; + if (newCursor > lastVisibleIndex) { + newCursor -= this.COLUMNS; + } + success = this.setScrollCursor(maxScrollCursor, newCursor); } - success = this.setScrollCursor(maxScrollCursor, newCursor); - } - break; - case Button.DOWN: - if (currentRowIndex < onScreenRows - 1) { + break; + case Button.DOWN: + if (currentRowIndex < onScreenRows - 1) { // Go down one row - success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1)); - } else if (this.scrollCursor < maxScrollCursor) { + success = this.setCursor(Math.min(this.cursor + this.COLUMNS, this.totalElements - itemOffset - 1)); + } else if (this.scrollCursor < maxScrollCursor) { // Scroll down one row - success = this.setScrollCursor(this.scrollCursor + 1); - } else { + success = this.setScrollCursor(this.scrollCursor + 1); + } else { // Wrap around to the top row - success = this.setScrollCursor(0, this.cursor % this.COLUMNS); - } - break; - case Button.LEFT: - if (currentColumnIndex > 0) { - success = this.setCursor(this.cursor - 1); - } else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) { - success = this.setCursor(lastVisibleIndex); - } else { - success = this.setCursor(this.cursor + this.COLUMNS - 1); - } - break; - case Button.RIGHT: - if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) { - success = this.setCursor(this.cursor + 1); - } else { - success = this.setCursor(this.cursor - currentColumnIndex); - } - break; + success = this.setScrollCursor(0, this.cursor % this.COLUMNS); + } + break; + case Button.LEFT: + if (currentColumnIndex > 0) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor === maxScrollCursor && currentRowIndex === onScreenRows - 1) { + success = this.setCursor(lastVisibleIndex); + } else { + success = this.setCursor(this.cursor + this.COLUMNS - 1); + } + break; + case Button.RIGHT: + if (currentColumnIndex < this.COLUMNS - 1 && this.cursor + itemOffset < this.totalElements - 1) { + success = this.setCursor(this.cursor + 1); + } else { + success = this.setCursor(this.cursor - currentColumnIndex); + } + break; } return success; } diff --git a/src/ui/settings/abstract-binding-ui-handler.ts b/src/ui/settings/abstract-binding-ui-handler.ts index 9ee741f7bd4..9ebc3c493a4 100644 --- a/src/ui/settings/abstract-binding-ui-handler.ts +++ b/src/ui/settings/abstract-binding-ui-handler.ts @@ -170,22 +170,22 @@ export default abstract class AbstractBindingUiHandler extends UiHandler { const ui = this.getUi(); let success = false; switch (button) { - case Button.LEFT: - case Button.RIGHT: + case Button.LEFT: + case Button.RIGHT: // Toggle between action and cancel options. - const cursor = this.cursor ? 0 : 1; - success = this.setCursor(cursor); - break; - case Button.ACTION: + const cursor = this.cursor ? 0 : 1; + success = this.setCursor(cursor); + break; + case Button.ACTION: // Process actions based on current cursor position. - if (this.cursor === 0) { - this.cancelFn && this.cancelFn(); - } else { - success = this.swapAction(); - NavigationManager.getInstance().updateIcons(); - this.cancelFn && this.cancelFn(success); - } - break; + if (this.cursor === 0) { + this.cancelFn && this.cancelFn(); + } else { + success = this.swapAction(); + NavigationManager.getInstance().updateIcons(); + this.cancelFn && this.cancelFn(success); + } + break; } // Plays a select sound effect if an action was successfully processed. diff --git a/src/ui/settings/abstract-control-settings-ui-handler.ts b/src/ui/settings/abstract-control-settings-ui-handler.ts index 477eaf0a68b..69f8eb241d3 100644 --- a/src/ui/settings/abstract-control-settings-ui-handler.ts +++ b/src/ui/settings/abstract-control-settings-ui-handler.ts @@ -449,78 +449,78 @@ export default abstract class AbstractControlSettingsUiHandler extends UiHandler const cursor = this.cursor + this.scrollCursor; // Calculate the absolute cursor position. const setting = this.setting[Object.keys(this.setting)[cursor]]; switch (button) { - case Button.ACTION: - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { - success = false; - } else { - success = this.setSetting(this.scene, setting, 1); - } - break; - case Button.UP: // Move up in the menu. - if (!this.optionValueLabels) { - return false; - } - if (cursor) { // If not at the top, move the cursor up. - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } else {// If at the top of the visible items, scroll up. - success = this.setScrollCursor(this.scrollCursor - 1); + case Button.ACTION: + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) } - } else { + if (this.settingBlacklisted.includes(setting) || !setting.includes("BUTTON_")) { + success = false; + } else { + success = this.setSetting(this.scene, setting, 1); + } + break; + case Button.UP: // Move up in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor) { // If not at the top, move the cursor up. + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else {// If at the top of the visible items, scroll up. + success = this.setScrollCursor(this.scrollCursor - 1); + } + } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(this.rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } - break; - case Button.DOWN: // Move down in the menu. - if (!this.optionValueLabels) { - return false; - } - if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) { - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { - success = this.setScrollCursor(this.scrollCursor + 1); + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect } - } else { + break; + case Button.DOWN: // Move down in the menu. + if (!this.optionValueLabels) { + return false; + } + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { // When at the bottom of the menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, resetting the scroll to the top. - const successA = this.setCursor(0); - // Then, reset the scroll to start from the first element of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // Indicates a successful cursor and scroll adjustment. - } - break; - case Button.LEFT: // Move selection left within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor]) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); - } - break; - case Button.RIGHT: // Move selection right within the current option set. - if (!this.optionCursors || !this.optionValueLabels) { - return false; // TODO: is false correct as default? (previously was `undefined`) - } - if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { - success = false; - } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); - } - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - success = this.navigationContainer.navigate(button); - break; + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: // Move selection left within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor]) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Move selection right within the current option set. + if (!this.optionCursors || !this.optionValueLabels) { + return false; // TODO: is false correct as default? (previously was `undefined`) + } + if (this.settingBlacklisted.includes(setting) || setting.includes("BUTTON_")) { + success = false; + } else if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; } } diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 47eceddc813..83219e1ef5a 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -224,59 +224,59 @@ export default class AbstractSettingsUiHandler extends UiHandler { } else { const cursor = this.cursor + this.scrollCursor; switch (button) { - case Button.UP: - if (cursor) { - if (this.cursor) { - success = this.setCursor(this.cursor - 1); + case Button.UP: + if (cursor) { + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else { + success = this.setScrollCursor(this.scrollCursor - 1); + } } else { - success = this.setScrollCursor(this.scrollCursor - 1); - } - } else { // When at the top of the menu and pressing UP, move to the bottommost item. // First, set the cursor to the last visible element, preparing for the scroll to the end. - const successA = this.setCursor(this.rowsToDisplay - 1); - // Then, adjust the scroll to display the bottommost elements of the menu. - const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); - success = successA && successB; // success is just there to play the little validation sound effect - } - break; - case Button.DOWN: - if (cursor < this.optionValueLabels.length - 1) { - if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 - success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { - success = this.setScrollCursor(this.scrollCursor + 1); + const successA = this.setCursor(this.rowsToDisplay - 1); + // Then, adjust the scroll to display the bottommost elements of the menu. + const successB = this.setScrollCursor(this.optionValueLabels.length - this.rowsToDisplay); + success = successA && successB; // success is just there to play the little validation sound effect } - } else { + break; + case Button.DOWN: + if (cursor < this.optionValueLabels.length - 1) { + if (this.cursor < this.rowsToDisplay - 1) {// if the visual cursor is in the frame of 0 to 8 + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.optionValueLabels.length - this.rowsToDisplay) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + } else { // When at the bottom of the menu and pressing DOWN, move to the topmost item. // First, set the cursor to the first visible element, resetting the scroll to the top. - const successA = this.setCursor(0); - // Then, reset the scroll to start from the first element of the menu. - const successB = this.setScrollCursor(0); - success = successA && successB; // Indicates a successful cursor and scroll adjustment. - } - break; - case Button.LEFT: - if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. - success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); - } - break; - case Button.RIGHT: + const successA = this.setCursor(0); + // Then, reset the scroll to start from the first element of the menu. + const successB = this.setScrollCursor(0); + success = successA && successB; // Indicates a successful cursor and scroll adjustment. + } + break; + case Button.LEFT: + if (this.optionCursors[cursor]) {// Moves the option cursor left, if possible. + success = this.setOptionCursor(cursor, this.optionCursors[cursor] - 1, true); + } + break; + case Button.RIGHT: // Moves the option cursor right, if possible. - if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { - success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); - } - break; - case Button.CYCLE_FORM: - case Button.CYCLE_SHINY: - success = this.navigationContainer.navigate(button); - break; - case Button.ACTION: - const setting: Setting = this.settings[cursor]; - if (setting?.activatable) { - success = this.activateSetting(setting); - } - break; + if (this.optionCursors[cursor] < this.optionValueLabels[cursor].length - 1) { + success = this.setOptionCursor(cursor, this.optionCursors[cursor] + 1, true); + } + break; + case Button.CYCLE_FORM: + case Button.CYCLE_SHINY: + success = this.navigationContainer.navigate(button); + break; + case Button.ACTION: + const setting: Setting = this.settings[cursor]; + if (setting?.activatable) { + success = this.activateSetting(setting); + } + break; } } @@ -295,9 +295,9 @@ export default class AbstractSettingsUiHandler extends UiHandler { */ activateSetting(setting: Setting): boolean { switch (setting.key) { - case SettingKeys.Move_Touch_Controls: - this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene); - return true; + case SettingKeys.Move_Touch_Controls: + this.scene.inputController.moveTouchControlsHandler.enableConfigurationMode(this.getUi(), this.scene); + return true; } return false; } diff --git a/src/ui/settings/navigationMenu.ts b/src/ui/settings/navigationMenu.ts index 45209c220fa..ab86fa1569a 100644 --- a/src/ui/settings/navigationMenu.ts +++ b/src/ui/settings/navigationMenu.ts @@ -198,12 +198,12 @@ export default class NavigationMenu extends Phaser.GameObjects.Container { navigate(button: Button): boolean { const navigationManager = NavigationManager.getInstance(); switch (button) { - case Button.CYCLE_FORM: - navigationManager.navigate(this.scene, LEFT); - return true; - case Button.CYCLE_SHINY: - navigationManager.navigate(this.scene, RIGHT); - return true; + case Button.CYCLE_FORM: + navigationManager.navigate(this.scene, LEFT); + return true; + case Button.CYCLE_SHINY: + navigationManager.navigate(this.scene, RIGHT); + return true; } return false; } diff --git a/src/ui/settings/settings-display-ui-handler.ts b/src/ui/settings/settings-display-ui-handler.ts index 3d602c50a78..a25dbf87b7d 100644 --- a/src/ui/settings/settings-display-ui-handler.ts +++ b/src/ui/settings/settings-display-ui-handler.ts @@ -23,79 +23,79 @@ export default class SettingsDisplayUiHandler extends AbstractSettingsUiHandler if (languageIndex >= 0) { const currentLocale = localStorage.getItem("prLang"); switch (currentLocale) { - case "en": - this.settings[languageIndex].options[0] = { - value: "English", - label: "English", - }; - break; - case "es": - this.settings[languageIndex].options[0] = { - value: "Español", - label: "Español", - }; - break; - case "it": - this.settings[languageIndex].options[0] = { - value: "Italiano", - label: "Italiano", - }; - break; - case "fr": - this.settings[languageIndex].options[0] = { - value: "Français", - label: "Français", - }; - break; - case "de": - this.settings[languageIndex].options[0] = { - value: "Deutsch", - label: "Deutsch", - }; - break; - case "pt-BR": - this.settings[languageIndex].options[0] = { - value: "Português (BR)", - label: "Português (BR)", - }; - break; - case "zh-CN": - this.settings[languageIndex].options[0] = { - value: "简体中文", - label: "简体中文", - }; - break; - case "zh-TW": - this.settings[languageIndex].options[0] = { - value: "繁體中文", - label: "繁體中文", - }; - break; - case "ko": - case "ko-KR": - this.settings[languageIndex].options[0] = { - value: "한국어", - label: "한국어", - }; - break; - case "ja": - this.settings[languageIndex].options[0] = { - value: "日本語", - label: "日本語", - }; - break; - case "ca-ES": - this.settings[languageIndex].options[0] = { - value: "Català", - label: "Català", - }; - break; - default: - this.settings[languageIndex].options[0] = { - value: "English", - label: "English", - }; - break; + case "en": + this.settings[languageIndex].options[0] = { + value: "English", + label: "English", + }; + break; + case "es": + this.settings[languageIndex].options[0] = { + value: "Español", + label: "Español", + }; + break; + case "it": + this.settings[languageIndex].options[0] = { + value: "Italiano", + label: "Italiano", + }; + break; + case "fr": + this.settings[languageIndex].options[0] = { + value: "Français", + label: "Français", + }; + break; + case "de": + this.settings[languageIndex].options[0] = { + value: "Deutsch", + label: "Deutsch", + }; + break; + case "pt-BR": + this.settings[languageIndex].options[0] = { + value: "Português (BR)", + label: "Português (BR)", + }; + break; + case "zh-CN": + this.settings[languageIndex].options[0] = { + value: "简体中文", + label: "简体中文", + }; + break; + case "zh-TW": + this.settings[languageIndex].options[0] = { + value: "繁體中文", + label: "繁體中文", + }; + break; + case "ko": + case "ko-KR": + this.settings[languageIndex].options[0] = { + value: "한국어", + label: "한국어", + }; + break; + case "ja": + this.settings[languageIndex].options[0] = { + value: "日本語", + label: "日本語", + }; + break; + case "ca-ES": + this.settings[languageIndex].options[0] = { + value: "Català", + label: "Català", + }; + break; + default: + this.settings[languageIndex].options[0] = { + value: "English", + label: "English", + }; + break; } } diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 2be89052bc1..bb999dc736a 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1303,119 +1303,119 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } else if (this.startCursorObj.visible) { // this checks to see if the start button is selected switch (button) { - case Button.ACTION: - if (this.tryStart(true)) { - success = true; - } else { - error = true; - } - break; - case Button.UP: + case Button.ACTION: + if (this.tryStart(true)) { + success = true; + } else { + error = true; + } + break; + case Button.UP: // UP from start button: go to pokemon in team if any, otherwise filter - this.startCursorObj.setVisible(false); - if (this.starterSpecies.length > 0) { - this.starterIconsCursorIndex = this.starterSpecies.length - 1; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } else { + this.startCursorObj.setVisible(false); + if (this.starterSpecies.length > 0) { + this.starterIconsCursorIndex = this.starterSpecies.length - 1; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.startCursorObj.setVisible(false); + this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); + this.setFilterMode(true); + } + success = true; + break; + case Button.DOWN: + // DOWN from start button: Go to filters this.startCursorObj.setVisible(false); this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); this.setFilterMode(true); - } - success = true; - break; - case Button.DOWN: - // DOWN from start button: Go to filters - this.startCursorObj.setVisible(false); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); - success = true; - break; - case Button.LEFT: - if (numberOfStarters > 0) { - this.startCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9 + 8); // set last column success = true; - } - break; - case Button.RIGHT: - if (numberOfStarters > 0) { - this.startCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9); // set first column - success = true; - } - break; + break; + case Button.LEFT: + if (numberOfStarters > 0) { + this.startCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9 + 8); // set last column + success = true; + } + break; + case Button.RIGHT: + if (numberOfStarters > 0) { + this.startCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + (onScreenNumberOfRows - 1) * 9); // set first column + success = true; + } + break; } } else if (this.filterMode) { switch (button) { - case Button.LEFT: - if (this.filterBarCursor > 0) { - success = this.setCursor(this.filterBarCursor - 1); - } else { - success = this.setCursor(this.filterBar.numFilters - 1); - } - break; - case Button.RIGHT: - if (this.filterBarCursor < this.filterBar.numFilters - 1) { - success = this.setCursor(this.filterBarCursor + 1); - } else { - success = this.setCursor(0); - } - break; - case Button.UP: - if (this.filterBar.openDropDown) { - success = this.filterBar.decDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // UP from the last filter, move to start button - this.setFilterMode(false); - this.cursorObj.setVisible(false); - this.startCursorObj.setVisible(true); - success = true; - } else if (numberOfStarters > 0) { - // UP from filter bar to bottom of Pokemon list - this.setFilterMode(false); - this.scrollCursor = Math.max(0, numOfRows - 9); - this.updateScroll(); - const proportion = (this.filterBarCursor + 0.5) / this.filterBar.numFilters; - const targetCol = Math.min(8, Math.floor(proportion * 11)); - if (numberOfStarters % 9 > targetCol) { - this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + case Button.LEFT: + if (this.filterBarCursor > 0) { + success = this.setCursor(this.filterBarCursor - 1); } else { - this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9, 0)); + success = this.setCursor(this.filterBar.numFilters - 1); + } + break; + case Button.RIGHT: + if (this.filterBarCursor < this.filterBar.numFilters - 1) { + success = this.setCursor(this.filterBarCursor + 1); + } else { + success = this.setCursor(0); + } + break; + case Button.UP: + if (this.filterBar.openDropDown) { + success = this.filterBar.decDropDownCursor(); + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + // UP from the last filter, move to start button + this.setFilterMode(false); + this.cursorObj.setVisible(false); + this.startCursorObj.setVisible(true); + success = true; + } else if (numberOfStarters > 0) { + // UP from filter bar to bottom of Pokemon list + this.setFilterMode(false); + this.scrollCursor = Math.max(0, numOfRows - 9); + this.updateScroll(); + const proportion = (this.filterBarCursor + 0.5) / this.filterBar.numFilters; + const targetCol = Math.min(8, Math.floor(proportion * 11)); + if (numberOfStarters % 9 > targetCol) { + this.setCursor(numberOfStarters - (numberOfStarters) % 9 + targetCol); + } else { + this.setCursor(Math.max(numberOfStarters - (numberOfStarters) % 9 + targetCol - 9, 0)); + } + success = true; + } + break; + case Button.DOWN: + if (this.filterBar.openDropDown) { + success = this.filterBar.incDropDownCursor(); + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + // DOWN from the last filter, move to Pokemon in party if any + this.setFilterMode(false); + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + success = true; + } else if (numberOfStarters > 0) { + // DOWN from filter bar to top of Pokemon list + this.setFilterMode(false); + this.scrollCursor = 0; + this.updateScroll(); + const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); + const targetCol = Math.min(8, Math.floor(proportion * 11)); + this.setCursor(Math.min(targetCol, numberOfStarters)); + success = true; + } + break; + case Button.ACTION: + if (!this.filterBar.openDropDown) { + this.filterBar.toggleDropDown(this.filterBarCursor); + } else { + this.filterBar.toggleOptionState(); } success = true; - } - break; - case Button.DOWN: - if (this.filterBar.openDropDown) { - success = this.filterBar.incDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // DOWN from the last filter, move to Pokemon in party if any - this.setFilterMode(false); - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = 0; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - success = true; - } else if (numberOfStarters > 0) { - // DOWN from filter bar to top of Pokemon list - this.setFilterMode(false); - this.scrollCursor = 0; - this.updateScroll(); - const proportion = this.filterBarCursor / Math.max(1, this.filterBar.numFilters - 1); - const targetCol = Math.min(8, Math.floor(proportion * 11)); - this.setCursor(Math.min(targetCol, numberOfStarters)); - success = true; - } - break; - case Button.ACTION: - if (!this.filterBar.openDropDown) { - this.filterBar.toggleDropDown(this.filterBarCursor); - } else { - this.filterBar.toggleOptionState(); - } - success = true; - break; + break; } } else { @@ -1856,259 +1856,259 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } else { const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); switch (button) { - case Button.CYCLE_SHINY: - if (this.canCycleShiny) { - starterAttributes.shiny = starterAttributes.shiny !== undefined ? !starterAttributes.shiny : false; + case Button.CYCLE_SHINY: + if (this.canCycleShiny) { + starterAttributes.shiny = starterAttributes.shiny !== undefined ? !starterAttributes.shiny : false; - if (starterAttributes.shiny) { + if (starterAttributes.shiny) { // Change to shiny, we need to get the proper default variant - const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); - const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant; - this.setSpeciesDetails(this.lastSpecies, true, undefined, undefined, newVariant, undefined, undefined); + const newProps = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)); + const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : newProps.variant; + this.setSpeciesDetails(this.lastSpecies, true, undefined, undefined, newVariant, undefined, undefined); - this.scene.playSound("se/sparkle"); - // Set the variant label to the shiny tint - const tint = getVariantTint(newVariant); - this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); - this.pokemonShinyIcon.setTint(tint); - this.pokemonShinyIcon.setVisible(true); - } else { - this.setSpeciesDetails(this.lastSpecies, false, undefined, undefined, 0, undefined, undefined); - this.pokemonShinyIcon.setVisible(false); - success = true; - } - } - break; - case Button.CYCLE_FORM: - if (this.canCycleForm) { - const formCount = this.lastSpecies.forms.length; - let newFormIndex = props.formIndex; - do { - newFormIndex = (newFormIndex + 1) % formCount; - if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & this.scene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? - break; + this.scene.playSound("se/sparkle"); + // Set the variant label to the shiny tint + const tint = getVariantTint(newVariant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant)); + this.pokemonShinyIcon.setTint(tint); + this.pokemonShinyIcon.setVisible(true); + } else { + this.setSpeciesDetails(this.lastSpecies, false, undefined, undefined, 0, undefined, undefined); + this.pokemonShinyIcon.setVisible(false); + success = true; } - } while (newFormIndex !== props.formIndex); - starterAttributes.form = newFormIndex; // store the selected form - this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined, undefined); - success = true; - } - break; - case Button.CYCLE_GENDER: - if (this.canCycleGender) { - starterAttributes.female = !props.female; - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined, undefined); - success = true; - } - break; - case Button.CYCLE_ABILITY: - if (this.canCycleAbility) { - const abilityCount = this.lastSpecies.getAbilityCount(); - const abilityAttr = this.scene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; - const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; - let newAbilityIndex = this.abilityCursor; - do { - newAbilityIndex = (newAbilityIndex + 1) % abilityCount; - if (newAbilityIndex === 0) { - if (hasAbility1) { + } + break; + case Button.CYCLE_FORM: + if (this.canCycleForm) { + const formCount = this.lastSpecies.forms.length; + let newFormIndex = props.formIndex; + do { + newFormIndex = (newFormIndex + 1) % formCount; + if (this.lastSpecies.forms[newFormIndex].isStarterSelectable && this.speciesStarterDexEntry!.caughtAttr! & this.scene.gameData.getFormAttr(newFormIndex)) { // TODO: are those bangs correct? break; } - } else if (newAbilityIndex === 1) { + } while (newFormIndex !== props.formIndex); + starterAttributes.form = newFormIndex; // store the selected form + this.setSpeciesDetails(this.lastSpecies, undefined, newFormIndex, undefined, undefined, undefined, undefined); + success = true; + } + break; + case Button.CYCLE_GENDER: + if (this.canCycleGender) { + starterAttributes.female = !props.female; + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, !props.female, undefined, undefined, undefined); + success = true; + } + break; + case Button.CYCLE_ABILITY: + if (this.canCycleAbility) { + const abilityCount = this.lastSpecies.getAbilityCount(); + const abilityAttr = this.scene.gameData.starterData[this.lastSpecies.speciesId].abilityAttr; + const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1; + let newAbilityIndex = this.abilityCursor; + do { + newAbilityIndex = (newAbilityIndex + 1) % abilityCount; + if (newAbilityIndex === 0) { + if (hasAbility1) { + break; + } + } else if (newAbilityIndex === 1) { // If ability 1 and 2 are the same and ability 1 is unlocked, skip over ability 2 - if (this.lastSpecies.ability1 === this.lastSpecies.ability2 && hasAbility1) { - newAbilityIndex = (newAbilityIndex + 1) % abilityCount; - } - break; - } else { - if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) { + if (this.lastSpecies.ability1 === this.lastSpecies.ability2 && hasAbility1) { + newAbilityIndex = (newAbilityIndex + 1) % abilityCount; + } break; + } else { + if (abilityAttr & AbilityAttr.ABILITY_HIDDEN) { + break; + } } - } - } while (newAbilityIndex !== this.abilityCursor); - starterAttributes.ability = newAbilityIndex; // store the selected ability + } while (newAbilityIndex !== this.abilityCursor); + starterAttributes.ability = newAbilityIndex; // store the selected ability - const { visible: tooltipVisible } = this.scene.ui.getTooltip(); + const { visible: tooltipVisible } = this.scene.ui.getTooltip(); - if (tooltipVisible && this.activeTooltip === "ABILITY") { - const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)]; - this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); - } + if (tooltipVisible && this.activeTooltip === "ABILITY") { + const newAbility = allAbilities[this.lastSpecies.getAbility(newAbilityIndex)]; + this.scene.ui.editTooltip(`${newAbility.name}`, `${newAbility.description}`); + } - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); - success = true; - } - break; - case Button.CYCLE_NATURE: - if (this.canCycleNature) { - const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); - const natureIndex = natures.indexOf(this.natureCursor); - const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; - // store cycled nature as default - starterAttributes.nature = newNature as unknown as integer; - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, newNature, undefined); - success = true; - } - break; - case Button.V: - if (this.canCycleVariant) { - let newVariant = props.variant; - do { - newVariant = (newVariant + 1) % 3; - if (!newVariant) { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.DEFAULT_VARIANT) { // TODO: is this bang correct? - break; - } - } else if (newVariant === 1) { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_2) { // TODO: is this bang correct? - break; - } - } else { - if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) { // TODO: is this bang correct? - break; - } - } - } while (newVariant !== props.variant); - starterAttributes.variant = newVariant; // store the selected variant - this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant as Variant, undefined, undefined); - // Cycle tint based on current sprite tint - const tint = getVariantTint(newVariant as Variant); - this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); - this.pokemonShinyIcon.setTint(tint); - success = true; - } - break; - case Button.UP: - if (!this.starterIconsCursorObj.visible) { - if (currentRow > 0) { - if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { - this.scrollCursor--; - this.updateScroll(); - } - success = this.setCursor(this.cursor - 9); - } else { - this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); - this.setFilterMode(true); + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, newAbilityIndex, undefined); success = true; } - } else { - if (this.starterIconsCursorIndex === 0) { + break; + case Button.CYCLE_NATURE: + if (this.canCycleNature) { + const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); + const natureIndex = natures.indexOf(this.natureCursor); + const newNature = natures[natureIndex < natures.length - 1 ? natureIndex + 1 : 0]; + // store cycled nature as default + starterAttributes.nature = newNature as unknown as integer; + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, newNature, undefined); + success = true; + } + break; + case Button.V: + if (this.canCycleVariant) { + let newVariant = props.variant; + do { + newVariant = (newVariant + 1) % 3; + if (!newVariant) { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.DEFAULT_VARIANT) { // TODO: is this bang correct? + break; + } + } else if (newVariant === 1) { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_2) { // TODO: is this bang correct? + break; + } + } else { + if (this.speciesStarterDexEntry!.caughtAttr & DexAttr.VARIANT_3) { // TODO: is this bang correct? + break; + } + } + } while (newVariant !== props.variant); + starterAttributes.variant = newVariant; // store the selected variant + this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, newVariant as Variant, undefined, undefined); + // Cycle tint based on current sprite tint + const tint = getVariantTint(newVariant as Variant); + this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant)); + this.pokemonShinyIcon.setTint(tint); + success = true; + } + break; + case Button.UP: + if (!this.starterIconsCursorObj.visible) { + if (currentRow > 0) { + if (this.scrollCursor > 0 && currentRow - this.scrollCursor === 0) { + this.scrollCursor--; + this.updateScroll(); + } + success = this.setCursor(this.cursor - 9); + } else { + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } + } else { + if (this.starterIconsCursorIndex === 0) { // Up from first Pokemon in the team > go to filter - this.starterIconsCursorObj.setVisible(false); - this.setSpecies(null); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); - } else { - this.starterIconsCursorIndex--; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } - success = true; - } - break; - case Button.DOWN: - if (!this.starterIconsCursorObj.visible) { - if (currentRow < numOfRows - 1) { // not last row - if (currentRow - this.scrollCursor === 8) { // last row of visible starters - this.scrollCursor++; + this.starterIconsCursorObj.setVisible(false); + this.setSpecies(null); + this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); + this.setFilterMode(true); + } else { + this.starterIconsCursorIndex--; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); } - success = this.setCursor(this.cursor + 9); - this.updateScroll(); - } else if (numOfRows > 1) { - // DOWN from last row of Pokemon > Wrap around to first row - this.scrollCursor = 0; - this.updateScroll(); - success = this.setCursor(this.cursor % 9); - } else { - // DOWN from single row of Pokemon > Go to filters - this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); - this.setFilterMode(true); success = true; } - } else { - if (this.starterIconsCursorIndex <= this.starterSpecies.length - 2) { - this.starterIconsCursorIndex++; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + break; + case Button.DOWN: + if (!this.starterIconsCursorObj.visible) { + if (currentRow < numOfRows - 1) { // not last row + if (currentRow - this.scrollCursor === 8) { // last row of visible starters + this.scrollCursor++; + } + success = this.setCursor(this.cursor + 9); + this.updateScroll(); + } else if (numOfRows > 1) { + // DOWN from last row of Pokemon > Wrap around to first row + this.scrollCursor = 0; + this.updateScroll(); + success = this.setCursor(this.cursor % 9); + } else { + // DOWN from single row of Pokemon > Go to filters + this.filterBarCursor = this.filterBar.getNearestFilter(this.filteredStarterContainers[this.cursor]); + this.setFilterMode(true); + success = true; + } } else { - this.starterIconsCursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); + if (this.starterIconsCursorIndex <= this.starterSpecies.length - 2) { + this.starterIconsCursorIndex++; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.starterIconsCursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; } - success = true; - } - break; - case Button.LEFT: - if (!this.starterIconsCursorObj.visible) { - if (this.cursor % 9 !== 0) { - success = this.setCursor(this.cursor - 1); - } else { + break; + case Button.LEFT: + if (!this.starterIconsCursorObj.visible) { + if (this.cursor % 9 !== 0) { + success = this.setCursor(this.cursor - 1); + } else { // LEFT from filtered Pokemon, on the left edge - if (this.starterSpecies.length === 0) { + if (this.starterSpecies.length === 0) { // no starter in team > wrap around to the last column - success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); + success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); - } else if (onScreenCurrentRow < 7) { + } else if (onScreenCurrentRow < 7) { // at least one pokemon in team > for the first 7 rows, go to closest starter - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); - } else { + } else { // at least one pokemon in team > from the bottom 2 rows, go to start run button - this.cursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; } - success = true; - } - } else if (numberOfStarters > 0) { + } else if (numberOfStarters > 0) { // LEFT from team > Go to closest filtered Pokemon - const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); - this.starterIconsCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9 + 8, onScreenLastIndex)); - success = true; - } else { - // LEFT from team and no Pokemon in filter > do nothing - success = false; - } - break; - case Button.RIGHT: - if (!this.starterIconsCursorObj.visible) { - // is not right edge - if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { - success = this.setCursor(this.cursor + 1); - } else { - // RIGHT from filtered Pokemon, on the right edge - if (this.starterSpecies.length === 0) { - // no selected starter in team > wrap around to the first column - success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); - - } else if (onScreenCurrentRow < 7) { - // at least one pokemon in team > for the first 7 rows, go to closest starter - this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); - this.moveStarterIconsCursor(this.starterIconsCursorIndex); - - } else { - // at least one pokemon in team > from the bottom 2 rows, go to start run button - this.cursorObj.setVisible(false); - this.setSpecies(null); - this.startCursorObj.setVisible(true); - } + const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); + this.starterIconsCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9 + 8, onScreenLastIndex)); success = true; + } else { + // LEFT from team and no Pokemon in filter > do nothing + success = false; } - } else if (numberOfStarters > 0) { + break; + case Button.RIGHT: + if (!this.starterIconsCursorObj.visible) { + // is not right edge + if (this.cursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfStarters - 1) % 9)) { + success = this.setCursor(this.cursor + 1); + } else { + // RIGHT from filtered Pokemon, on the right edge + if (this.starterSpecies.length === 0) { + // no selected starter in team > wrap around to the first column + success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); + + } else if (onScreenCurrentRow < 7) { + // at least one pokemon in team > for the first 7 rows, go to closest starter + this.cursorObj.setVisible(false); + this.starterIconsCursorIndex = findClosestStarterIndex(this.cursorObj.y - 1, this.starterSpecies.length); + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + + } else { + // at least one pokemon in team > from the bottom 2 rows, go to start run button + this.cursorObj.setVisible(false); + this.setSpecies(null); + this.startCursorObj.setVisible(true); + } + success = true; + } + } else if (numberOfStarters > 0) { // RIGHT from team > Go to closest filtered Pokemon - const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); - this.starterIconsCursorObj.setVisible(false); - this.cursorObj.setVisible(true); - this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9, onScreenLastIndex - (onScreenLastIndex % 9))); - success = true; - } else { + const closestRowIndex = findClosestStarterRow(this.starterIconsCursorIndex, onScreenNumberOfRows); + this.starterIconsCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(Math.min(onScreenFirstIndex + closestRowIndex * 9, onScreenLastIndex - (onScreenLastIndex % 9))); + success = true; + } else { // RIGHT from team and no Pokemon in filter > do nothing - success = false; - } - break; + success = false; + } + break; } } } @@ -2210,29 +2210,29 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (gamepadType === "touch") { gamepadType = "keyboard"; switch (iconSetting) { - case SettingKeyboard.Button_Cycle_Shiny: - iconPath = "R.png"; - break; - case SettingKeyboard.Button_Cycle_Form: - iconPath = "F.png"; - break; - case SettingKeyboard.Button_Cycle_Gender: - iconPath = "G.png"; - break; - case SettingKeyboard.Button_Cycle_Ability: - iconPath = "E.png"; - break; - case SettingKeyboard.Button_Cycle_Nature: - iconPath = "N.png"; - break; - case SettingKeyboard.Button_Cycle_Variant: - iconPath = "V.png"; - break; - case SettingKeyboard.Button_Stats: - iconPath = "C.png"; - break; - default: - break; + case SettingKeyboard.Button_Cycle_Shiny: + iconPath = "R.png"; + break; + case SettingKeyboard.Button_Cycle_Form: + iconPath = "F.png"; + break; + case SettingKeyboard.Button_Cycle_Gender: + iconPath = "G.png"; + break; + case SettingKeyboard.Button_Cycle_Ability: + iconPath = "E.png"; + break; + case SettingKeyboard.Button_Cycle_Nature: + iconPath = "N.png"; + break; + case SettingKeyboard.Button_Cycle_Variant: + iconPath = "V.png"; + break; + case SettingKeyboard.Button_Stats: + iconPath = "C.png"; + break; + default: + break; } } else { iconPath = this.scene.inputController?.getIconForLatestInputRecorded(iconSetting); @@ -2323,12 +2323,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { getValueLimit(): integer { const valueLimit = new Utils.IntegerHolder(0); switch (this.scene.gameMode.modeId) { - case GameModes.ENDLESS: - case GameModes.SPLICED_ENDLESS: - valueLimit.value = 15; - break; - default: - valueLimit.value = 10; + case GameModes.ENDLESS: + case GameModes.SPLICED_ENDLESS: + valueLimit.value = 15; + break; + default: + valueLimit.value = 10; } Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit); @@ -2532,22 +2532,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const sort = this.filterBar.getVals(DropDownColumn.SORT)[0]; this.filteredStarterContainers.sort((a, b) => { switch (sort.val) { - default: - break; - case SortCriteria.NUMBER: - return (a.species.speciesId - b.species.speciesId) * -sort.dir; - case SortCriteria.COST: - return (a.cost - b.cost) * -sort.dir; - case SortCriteria.CANDY: - const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; - const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; - return (candyCountA - candyCountB) * -sort.dir; - case SortCriteria.IV: - const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length; - const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length; - return (avgIVsA - avgIVsB) * -sort.dir; - case SortCriteria.NAME: - return a.species.name.localeCompare(b.species.name) * -sort.dir; + default: + break; + case SortCriteria.NUMBER: + return (a.species.speciesId - b.species.speciesId) * -sort.dir; + case SortCriteria.COST: + return (a.cost - b.cost) * -sort.dir; + case SortCriteria.CANDY: + const candyCountA = this.scene.gameData.starterData[a.species.speciesId].candyCount; + const candyCountB = this.scene.gameData.starterData[b.species.speciesId].candyCount; + return (candyCountA - candyCountB) * -sort.dir; + case SortCriteria.IV: + const avgIVsA = this.scene.gameData.dexData[a.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[a.species.speciesId].ivs.length; + const avgIVsB = this.scene.gameData.dexData[b.species.speciesId].ivs.reduce((a, b) => a + b, 0) / this.scene.gameData.dexData[b.species.speciesId].ivs.length; + return (avgIVsA - avgIVsB) * -sort.dir; + case SortCriteria.NAME: + return a.species.name.localeCompare(b.species.name) * -sort.dir; } return 0; }); @@ -3362,16 +3362,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starter.label.setText(valueStr); let textStyle: TextStyle; switch (baseStarterValue - starterValue) { - case 0: - textStyle = TextStyle.WINDOW; - break; - case 1: - case 0.5: - textStyle = TextStyle.SUMMARY_BLUE; - break; - default: - textStyle = TextStyle.SUMMARY_GOLD; - break; + case 0: + textStyle = TextStyle.WINDOW; + break; + case 1: + case 0.5: + textStyle = TextStyle.SUMMARY_BLUE; + break; + default: + textStyle = TextStyle.SUMMARY_GOLD; + break; } if (baseStarterValue - starterValue > 0) { starter.label.setColor(this.getTextColor(textStyle)); diff --git a/src/ui/summary-ui-handler.ts b/src/ui/summary-ui-handler.ts index 86b00f512a9..b35c0ca3303 100644 --- a/src/ui/summary-ui-handler.ts +++ b/src/ui/summary-ui-handler.ts @@ -406,22 +406,22 @@ export default class SummaryUiHandler extends UiHandler { this.genderText.setShadowColor(getGenderColor(this.pokemon.getGender(true), true)); switch (this.summaryUiMode) { - case SummaryUiMode.DEFAULT: - const page = args.length < 2 ? Page.PROFILE : args[2] as Page; - this.hideMoveEffect(true); - this.setCursor(page); - if (args.length > 3) { - this.selectCallback = args[3]; - } - break; - case SummaryUiMode.LEARN_MOVE: - this.newMove = args[2] as Move; - this.moveSelectFunction = args[3] as Function; + case SummaryUiMode.DEFAULT: + const page = args.length < 2 ? Page.PROFILE : args[2] as Page; + this.hideMoveEffect(true); + this.setCursor(page); + if (args.length > 3) { + this.selectCallback = args[3]; + } + break; + case SummaryUiMode.LEARN_MOVE: + this.newMove = args[2] as Move; + this.moveSelectFunction = args[3] as Function; - this.showMoveEffect(true); - this.setCursor(Page.MOVES); - this.showMoveSelect(); - break; + this.showMoveEffect(true); + this.setCursor(Page.MOVES); + this.showMoveSelect(); + break; } const fromSummary = args.length >= 2; @@ -489,25 +489,25 @@ export default class SummaryUiHandler extends UiHandler { success = true; } else { switch (button) { - case Button.UP: - success = this.setCursor(this.moveCursor ? this.moveCursor - 1 : 4); - break; - case Button.DOWN: - success = this.setCursor(this.moveCursor < 4 ? this.moveCursor + 1 : 0); - break; - case Button.LEFT: - this.moveSelect = false; - this.setCursor(Page.STATS); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - this.hideMoveEffect(); - this.destroyBlinkCursor(); - success = true; + case Button.UP: + success = this.setCursor(this.moveCursor ? this.moveCursor - 1 : 4); break; - } else { - this.hideMoveSelect(); - success = true; + case Button.DOWN: + success = this.setCursor(this.moveCursor < 4 ? this.moveCursor + 1 : 0); break; - } + case Button.LEFT: + this.moveSelect = false; + this.setCursor(Page.STATS); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + this.hideMoveEffect(); + this.destroyBlinkCursor(); + success = true; + break; + } else { + this.hideMoveSelect(); + success = true; + break; + } } } } else { @@ -546,35 +546,35 @@ export default class SummaryUiHandler extends UiHandler { } else { const pages = Utils.getEnumValues(Page); switch (button) { - case Button.UP: - case Button.DOWN: - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - break; - } else if (!fromPartyMode) { - break; - } - const isDown = button === Button.DOWN; - const party = this.scene.getParty(); - const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; - if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { - const page = this.cursor; - this.clear(); - this.show([ party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page ]); - } - break; - case Button.LEFT: - if (this.cursor) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if (this.cursor < pages.length - 1) { - success = this.setCursor(this.cursor + 1); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) { - this.moveSelect = true; + case Button.UP: + case Button.DOWN: + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + break; + } else if (!fromPartyMode) { + break; } - } - break; + const isDown = button === Button.DOWN; + const party = this.scene.getParty(); + const partyMemberIndex = this.pokemon ? party.indexOf(this.pokemon) : -1; + if ((isDown && partyMemberIndex < party.length - 1) || (!isDown && partyMemberIndex)) { + const page = this.cursor; + this.clear(); + this.show([ party[partyMemberIndex + (isDown ? 1 : -1)], this.summaryUiMode, page ]); + } + break; + case Button.LEFT: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if (this.cursor < pages.length - 1) { + success = this.setCursor(this.cursor + 1); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.cursor === Page.MOVES) { + this.moveSelect = true; + } + } + break; } } } @@ -730,308 +730,308 @@ export default class SummaryUiHandler extends UiHandler { } switch (page) { - case Page.PROFILE: - const profileContainer = this.scene.add.container(0, -pageBg.height); - pageContainer.add(profileContainer); + case Page.PROFILE: + const profileContainer = this.scene.add.container(0, -pageBg.height); + pageContainer.add(profileContainer); - // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs - const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); - trainerText.setOrigin(0, 0); - profileContainer.add(trainerText); + // TODO: should add field for original trainer name to Pokemon object, to support gift/traded Pokemon from MEs + const trainerText = addBBCodeTextObject(this.scene, 7, 12, `${i18next.t("pokemonSummary:ot")}/${getBBCodeFrag(loggedInUser?.username || i18next.t("pokemonSummary:unknown"), this.scene.gameData.gender === PlayerGender.FEMALE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE)}`, TextStyle.SUMMARY_ALT); + trainerText.setOrigin(0, 0); + profileContainer.add(trainerText); - const trainerIdText = addTextObject(this.scene, 174, 12, this.scene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); - trainerIdText.setOrigin(0, 0); - profileContainer.add(trainerIdText); + const trainerIdText = addTextObject(this.scene, 174, 12, this.scene.gameData.trainerId.toString(), TextStyle.SUMMARY_ALT); + trainerIdText.setOrigin(0, 0); + profileContainer.add(trainerIdText); - const typeLabel = addTextObject(this.scene, 7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); - typeLabel.setOrigin(0, 0); - profileContainer.add(typeLabel); + const typeLabel = addTextObject(this.scene, 7, 28, `${i18next.t("pokemonSummary:type")}/`, TextStyle.WINDOW_ALT); + typeLabel.setOrigin(0, 0); + profileContainer.add(typeLabel); - const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { - const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; - const typeIcon = !tera - ? this.scene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) - : this.scene.add.sprite(xCoord, 42, "type_tera"); - if (tera) { - typeIcon.setScale(0.5); - const typeRgb = getTypeRgb(type); - typeIcon.setTint(Phaser.Display.Color.GetColor(typeRgb[0], typeRgb[1], typeRgb[2])); + const getTypeIcon = (index: integer, type: Type, tera: boolean = false) => { + const xCoord = typeLabel.width * typeLabel.scale + 9 + 34 * index; + const typeIcon = !tera + ? this.scene.add.sprite(xCoord, 42, Utils.getLocalizedSpriteKey("types"), Type[type].toLowerCase()) + : this.scene.add.sprite(xCoord, 42, "type_tera"); + if (tera) { + typeIcon.setScale(0.5); + const typeRgb = getTypeRgb(type); + typeIcon.setTint(Phaser.Display.Color.GetColor(typeRgb[0], typeRgb[1], typeRgb[2])); + } + typeIcon.setOrigin(0, 1); + return typeIcon; + }; + + const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct? + profileContainer.add(getTypeIcon(0, types[0])); + if (types.length > 1) { + profileContainer.add(getTypeIcon(1, types[1])); + } + if (this.pokemon?.isTerastallized()) { + profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); } - typeIcon.setOrigin(0, 1); - return typeIcon; - }; - const types = this.pokemon?.getTypes(false, false, true)!; // TODO: is this bang correct? - profileContainer.add(getTypeIcon(0, types[0])); - if (types.length > 1) { - profileContainer.add(getTypeIcon(1, types[1])); - } - if (this.pokemon?.isTerastallized()) { - profileContainer.add(getTypeIcon(types.length, this.pokemon.getTeraType(), true)); - } + if (this.pokemon?.getLuck()) { + const luckLabelText = addTextObject(this.scene, 141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); + luckLabelText.setOrigin(0, 0); + profileContainer.add(luckLabelText); - if (this.pokemon?.getLuck()) { - const luckLabelText = addTextObject(this.scene, 141, 28, i18next.t("common:luckIndicator"), TextStyle.SUMMARY_ALT); - luckLabelText.setOrigin(0, 0); - profileContainer.add(luckLabelText); + const luckText = addTextObject(this.scene, 141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); + luckText.setOrigin(0, 0); + luckText.setTint(getVariantTint((Math.min(this.pokemon.getLuck() - 1, 2)) as Variant)); + profileContainer.add(luckText); + } - const luckText = addTextObject(this.scene, 141 + luckLabelText.displayWidth + 2, 28, this.pokemon.getLuck().toString(), TextStyle.SUMMARY); - luckText.setOrigin(0, 0); - luckText.setTint(getVariantTint((Math.min(this.pokemon.getLuck() - 1, 2)) as Variant)); - profileContainer.add(luckText); - } - - this.abilityContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_ability"), - ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct? - nameText: null, - descriptionText: null }; - - const allAbilityInfo = [ this.abilityContainer ]; // Creates an array to iterate through - // Only add to the array and set up displaying a passive if it's unlocked - if (this.pokemon?.hasPassive()) { - this.passiveContainer = { - labelImage: this.scene.add.image(0, 0, "summary_profile_passive"), - ability: this.pokemon.getPassiveAbility(), + this.abilityContainer = { + labelImage: this.scene.add.image(0, 0, "summary_profile_ability"), + ability: this.pokemon?.getAbility(true)!, // TODO: is this bang correct? nameText: null, descriptionText: null }; - allAbilityInfo.push(this.passiveContainer); - // Sets up the pixel button prompt image - this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); - this.abilityPrompt.setPosition(8, 43); - this.abilityPrompt.setVisible(true); - this.abilityPrompt.setOrigin(0, 0); - profileContainer.add(this.abilityPrompt); - } + const allAbilityInfo = [ this.abilityContainer ]; // Creates an array to iterate through + // Only add to the array and set up displaying a passive if it's unlocked + if (this.pokemon?.hasPassive()) { + this.passiveContainer = { + labelImage: this.scene.add.image(0, 0, "summary_profile_passive"), + ability: this.pokemon.getPassiveAbility(), + nameText: null, + descriptionText: null }; + allAbilityInfo.push(this.passiveContainer); - allAbilityInfo.forEach(abilityInfo => { - abilityInfo.labelImage.setPosition(17, 43); - abilityInfo.labelImage.setVisible(true); - abilityInfo.labelImage.setOrigin(0, 0); - profileContainer.add(abilityInfo.labelImage); - - abilityInfo.nameText = addTextObject(this.scene, 7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? - abilityInfo.nameText.setOrigin(0, 1); - profileContainer.add(abilityInfo.nameText); - - abilityInfo.descriptionText = addTextObject(this.scene, 7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? - abilityInfo.descriptionText.setOrigin(0, 0); - profileContainer.add(abilityInfo.descriptionText); - - // Sets up the mask that hides the description text to give an illusion of scrolling - const descriptionTextMaskRect = this.scene.make.graphics({}); - descriptionTextMaskRect.setScale(6); - descriptionTextMaskRect.fillStyle(0xFFFFFF); - descriptionTextMaskRect.beginPath(); - descriptionTextMaskRect.fillRect(110, 90.5, 206, 31); - - const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask(); - - abilityInfo.descriptionText.setMask(abilityDescriptionTextMask); - - const abilityDescriptionLineCount = Math.floor(abilityInfo.descriptionText.displayHeight / 14.83); - - // Animates the description text moving upwards - if (abilityDescriptionLineCount > 2) { - abilityInfo.descriptionText.setY(69); - this.descriptionScrollTween = this.scene.tweens.add({ - targets: abilityInfo.descriptionText, - delay: Utils.fixedInt(2000), - loop: -1, - hold: Utils.fixedInt(2000), - duration: Utils.fixedInt((abilityDescriptionLineCount - 2) * 2000), - y: `-=${14.83 * (abilityDescriptionLineCount - 2)}` - }); + // Sets up the pixel button prompt image + this.abilityPrompt = this.scene.add.image(0, 0, !this.scene.inputController?.gamepadSupport ? "summary_profile_prompt_z" : "summary_profile_prompt_a"); + this.abilityPrompt.setPosition(8, 43); + this.abilityPrompt.setVisible(true); + this.abilityPrompt.setOrigin(0, 0); + profileContainer.add(this.abilityPrompt); } - }); - // Turn off visibility of passive info by default - this.passiveContainer?.labelImage.setVisible(false); - this.passiveContainer?.nameText?.setVisible(false); - this.passiveContainer?.descriptionText?.setVisible(false); - const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); - const rawNature = Utils.toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? - const nature = `${getBBCodeFrag(Utils.toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + allAbilityInfo.forEach(abilityInfo => { + abilityInfo.labelImage.setPosition(17, 43); + abilityInfo.labelImage.setVisible(true); + abilityInfo.labelImage.setOrigin(0, 0); + profileContainer.add(abilityInfo.labelImage); - const memoString = i18next.t("pokemonSummary:memoString", { - metFragment: i18next.t(`pokemonSummary:metFragment.${this.pokemon?.metBiome === -1 ? "apparently" : "normal"}`, { - biome: `${getBBCodeFrag(getBiomeName(this.pokemon?.metBiome!), TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? - level: `${getBBCodeFrag(this.pokemon?.metLevel.toString()!, TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? - wave: `${getBBCodeFrag((this.pokemon?.metWave ? this.pokemon.metWave.toString()! : i18next.t("pokemonSummary:unknownTrainer")), TextStyle.SUMMARY_RED)}${closeFragment}`, - }), - natureFragment: i18next.t(`pokemonSummary:natureFragment.${rawNature}`, { nature: nature }) - }); + abilityInfo.nameText = addTextObject(this.scene, 7, 66, abilityInfo.ability?.name!, TextStyle.SUMMARY_ALT); // TODO: is this bang correct? + abilityInfo.nameText.setOrigin(0, 1); + profileContainer.add(abilityInfo.nameText); - const memoText = addBBCodeTextObject(this.scene, 7, 113, String(memoString), TextStyle.WINDOW_ALT); - memoText.setOrigin(0, 0); - profileContainer.add(memoText); - break; - case Page.STATS: - const statsContainer = this.scene.add.container(0, -pageBg.height); - pageContainer.add(statsContainer); + abilityInfo.descriptionText = addTextObject(this.scene, 7, 69, abilityInfo.ability?.description!, TextStyle.WINDOW_ALT, { wordWrap: { width: 1224 }}); // TODO: is this bang correct? + abilityInfo.descriptionText.setOrigin(0, 0); + profileContainer.add(abilityInfo.descriptionText); - PERMANENT_STATS.forEach((stat, s) => { - const statName = i18next.t(getStatKey(stat)); - const rowIndex = s % 3; - const colIndex = Math.floor(s / 3); + // Sets up the mask that hides the description text to give an illusion of scrolling + const descriptionTextMaskRect = this.scene.make.graphics({}); + descriptionTextMaskRect.setScale(6); + descriptionTextMaskRect.fillStyle(0xFFFFFF); + descriptionTextMaskRect.beginPath(); + descriptionTextMaskRect.fillRect(110, 90.5, 206, 31); - const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? + const abilityDescriptionTextMask = descriptionTextMaskRect.createGeometryMask(); - const statLabel = addTextObject(this.scene, 27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); - statLabel.setOrigin(0.5, 0); - statsContainer.add(statLabel); + abilityInfo.descriptionText.setMask(abilityDescriptionTextMask); - const statValueText = stat !== Stat.HP - ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? - : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + const abilityDescriptionLineCount = Math.floor(abilityInfo.descriptionText.displayHeight / 14.83); - const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); - statValue.setOrigin(1, 0); - statsContainer.add(statValue); - }); + // Animates the description text moving upwards + if (abilityDescriptionLineCount > 2) { + abilityInfo.descriptionText.setY(69); + this.descriptionScrollTween = this.scene.tweens.add({ + targets: abilityInfo.descriptionText, + delay: Utils.fixedInt(2000), + loop: -1, + hold: Utils.fixedInt(2000), + duration: Utils.fixedInt((abilityDescriptionLineCount - 2) * 2000), + y: `-=${14.83 * (abilityDescriptionLineCount - 2)}` + }); + } + }); + // Turn off visibility of passive info by default + this.passiveContainer?.labelImage.setVisible(false); + this.passiveContainer?.nameText?.setVisible(false); + this.passiveContainer?.descriptionText?.setVisible(false); - const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier + const closeFragment = getBBCodeFrag("", TextStyle.WINDOW_ALT); + const rawNature = Utils.toReadableString(Nature[this.pokemon?.getNature()!]); // TODO: is this bang correct? + const nature = `${getBBCodeFrag(Utils.toReadableString(getNatureName(this.pokemon?.getNature()!)), TextStyle.SUMMARY_RED)}${closeFragment}`; // TODO: is this bang correct? + + const memoString = i18next.t("pokemonSummary:memoString", { + metFragment: i18next.t(`pokemonSummary:metFragment.${this.pokemon?.metBiome === -1 ? "apparently" : "normal"}`, { + biome: `${getBBCodeFrag(getBiomeName(this.pokemon?.metBiome!), TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? + level: `${getBBCodeFrag(this.pokemon?.metLevel.toString()!, TextStyle.SUMMARY_RED)}${closeFragment}`, // TODO: is this bang correct? + wave: `${getBBCodeFrag((this.pokemon?.metWave ? this.pokemon.metWave.toString()! : i18next.t("pokemonSummary:unknownTrainer")), TextStyle.SUMMARY_RED)}${closeFragment}`, + }), + natureFragment: i18next.t(`pokemonSummary:natureFragment.${rawNature}`, { nature: nature }) + }); + + const memoText = addBBCodeTextObject(this.scene, 7, 113, String(memoString), TextStyle.WINDOW_ALT); + memoText.setOrigin(0, 0); + profileContainer.add(memoText); + break; + case Page.STATS: + const statsContainer = this.scene.add.container(0, -pageBg.height); + pageContainer.add(statsContainer); + + PERMANENT_STATS.forEach((stat, s) => { + const statName = i18next.t(getStatKey(stat)); + const rowIndex = s % 3; + const colIndex = Math.floor(s / 3); + + const natureStatMultiplier = getNatureStatMultiplier(this.pokemon?.getNature()!, s); // TODO: is this bang correct? + + const statLabel = addTextObject(this.scene, 27 + 115 * colIndex + (colIndex === 1 ? 5 : 0), 56 + 16 * rowIndex, statName, natureStatMultiplier === 1 ? TextStyle.SUMMARY : natureStatMultiplier > 1 ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY_BLUE); + statLabel.setOrigin(0.5, 0); + statsContainer.add(statLabel); + + const statValueText = stat !== Stat.HP + ? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct? + : `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct? + + const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT); + statValue.setOrigin(1, 0); + statsContainer.add(statValue); + }); + + const itemModifiers = (this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === this.pokemon?.id, this.playerParty) as PokemonHeldItemModifier[]) - .sort(modifierSortFunc); + .sort(modifierSortFunc); - itemModifiers.forEach((item, i) => { - const icon = item.getIcon(this.scene, true); + itemModifiers.forEach((item, i) => { + const icon = item.getIcon(this.scene, true); - icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); - statsContainer.add(icon); + icon.setPosition((i % 17) * 12 + 3, 14 * Math.floor(i / 17) + 15); + statsContainer.add(icon); - icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); - icon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(item.type.name, item.type.getDescription(this.scene), true)); - icon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); - }); + icon.setInteractive(new Phaser.Geom.Rectangle(0, 0, 32, 32), Phaser.Geom.Rectangle.Contains); + icon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(item.type.name, item.type.getDescription(this.scene), true)); + icon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + }); - const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? - const pkmLvlExp = this.pokemon?.levelExp!; // TODO: is this bang correct? - const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? - const pkmSpeciesGrowthRate = this.pokemon?.species.growthRate!; // TODO: is this bang correct? - const relLvExp = getLevelRelExp(pkmLvl + 1, pkmSpeciesGrowthRate); - const expRatio = pkmLvl < this.scene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; + const pkmLvl = this.pokemon?.level!; // TODO: is this bang correct? + const pkmLvlExp = this.pokemon?.levelExp!; // TODO: is this bang correct? + const pkmExp = this.pokemon?.exp!; // TODO: is this bang correct? + const pkmSpeciesGrowthRate = this.pokemon?.species.growthRate!; // TODO: is this bang correct? + const relLvExp = getLevelRelExp(pkmLvl + 1, pkmSpeciesGrowthRate); + const expRatio = pkmLvl < this.scene.getMaxExpLevel() ? pkmLvlExp / relLvExp : 0; - const expLabel = addTextObject(this.scene, 6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); - expLabel.setOrigin(0, 0); - statsContainer.add(expLabel); + const expLabel = addTextObject(this.scene, 6, 112, i18next.t("pokemonSummary:expPoints"), TextStyle.SUMMARY); + expLabel.setOrigin(0, 0); + statsContainer.add(expLabel); - const nextLvExpLabel = addTextObject(this.scene, 6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); - nextLvExpLabel.setOrigin(0, 0); - statsContainer.add(nextLvExpLabel); + const nextLvExpLabel = addTextObject(this.scene, 6, 128, i18next.t("pokemonSummary:nextLv"), TextStyle.SUMMARY); + nextLvExpLabel.setOrigin(0, 0); + statsContainer.add(nextLvExpLabel); - const expText = addTextObject(this.scene, 208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); - expText.setOrigin(1, 0); - statsContainer.add(expText); + const expText = addTextObject(this.scene, 208, 112, pkmExp.toString(), TextStyle.WINDOW_ALT); + expText.setOrigin(1, 0); + statsContainer.add(expText); - const nextLvExp = pkmLvl < this.scene.getMaxExpLevel() - ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp - : 0; - const nextLvExpText = addTextObject(this.scene, 208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); - nextLvExpText.setOrigin(1, 0); - statsContainer.add(nextLvExpText); + const nextLvExp = pkmLvl < this.scene.getMaxExpLevel() + ? getLevelTotalExp(pkmLvl + 1, pkmSpeciesGrowthRate) - pkmExp + : 0; + const nextLvExpText = addTextObject(this.scene, 208, 128, nextLvExp.toString(), TextStyle.WINDOW_ALT); + nextLvExpText.setOrigin(1, 0); + statsContainer.add(nextLvExpText); - const expOverlay = this.scene.add.image(140, 145, "summary_stats_overlay_exp"); - expOverlay.setOrigin(0, 0); - statsContainer.add(expOverlay); + const expOverlay = this.scene.add.image(140, 145, "summary_stats_overlay_exp"); + expOverlay.setOrigin(0, 0); + statsContainer.add(expOverlay); - const expMaskRect = this.scene.make.graphics({}); - expMaskRect.setScale(6); - expMaskRect.fillStyle(0xFFFFFF); - expMaskRect.beginPath(); - expMaskRect.fillRect(140 + pageContainer.x, 145 + pageContainer.y + 21, Math.floor(expRatio * 64), 3); + const expMaskRect = this.scene.make.graphics({}); + expMaskRect.setScale(6); + expMaskRect.fillStyle(0xFFFFFF); + expMaskRect.beginPath(); + expMaskRect.fillRect(140 + pageContainer.x, 145 + pageContainer.y + 21, Math.floor(expRatio * 64), 3); - const expMask = expMaskRect.createGeometryMask(); + const expMask = expMaskRect.createGeometryMask(); - expOverlay.setMask(expMask); - break; - case Page.MOVES: - this.movesContainer = this.scene.add.container(5, -pageBg.height + 26); - pageContainer.add(this.movesContainer); + expOverlay.setMask(expMask); + break; + case Page.MOVES: + this.movesContainer = this.scene.add.container(5, -pageBg.height + 26); + pageContainer.add(this.movesContainer); - this.extraMoveRowContainer = this.scene.add.container(0, 64); - this.extraMoveRowContainer.setVisible(false); - this.movesContainer.add(this.extraMoveRowContainer); + this.extraMoveRowContainer = this.scene.add.container(0, 64); + this.extraMoveRowContainer.setVisible(false); + this.movesContainer.add(this.extraMoveRowContainer); - const extraRowOverlay = this.scene.add.image(-2, 1, "summary_moves_overlay_row"); - extraRowOverlay.setOrigin(0, 1); - this.extraMoveRowContainer.add(extraRowOverlay); + const extraRowOverlay = this.scene.add.image(-2, 1, "summary_moves_overlay_row"); + extraRowOverlay.setOrigin(0, 1); + this.extraMoveRowContainer.add(extraRowOverlay); - const extraRowText = addTextObject(this.scene, 35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), - this.summaryUiMode === SummaryUiMode.LEARN_MOVE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY); - extraRowText.setOrigin(0, 1); - this.extraMoveRowContainer.add(extraRowText); + const extraRowText = addTextObject(this.scene, 35, 0, this.summaryUiMode === SummaryUiMode.LEARN_MOVE && this.newMove ? this.newMove.name : i18next.t("pokemonSummary:cancel"), + this.summaryUiMode === SummaryUiMode.LEARN_MOVE ? TextStyle.SUMMARY_PINK : TextStyle.SUMMARY); + extraRowText.setOrigin(0, 1); + this.extraMoveRowContainer.add(extraRowText); - if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { - this.extraMoveRowContainer.setVisible(true); + if (this.summaryUiMode === SummaryUiMode.LEARN_MOVE) { + this.extraMoveRowContainer.setVisible(true); - if (this.newMove && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); - const moveType = this.pokemon.getMoveType(this.newMove); - const newMoveTypeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); - newMoveTypeIcon.setOrigin(0, 1); - this.extraMoveRowContainer.add(newMoveTypeIcon); - } - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); - ppOverlay.setOrigin(0, 1); - this.extraMoveRowContainer.add(ppOverlay); + if (this.newMove && this.pokemon) { + const spriteKey = Utils.getLocalizedSpriteKey("types"); + const moveType = this.pokemon.getMoveType(this.newMove); + const newMoveTypeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + newMoveTypeIcon.setOrigin(0, 1); + this.extraMoveRowContainer.add(newMoveTypeIcon); + } + const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + ppOverlay.setOrigin(0, 1); + this.extraMoveRowContainer.add(ppOverlay); - const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? - const ppText = addTextObject(this.scene, 173, 1, `${pp}/${pp}`, TextStyle.WINDOW); - ppText.setOrigin(0, 1); - this.extraMoveRowContainer.add(ppText); - } - - this.moveRowsContainer = this.scene.add.container(0, 0); - this.movesContainer.add(this.moveRowsContainer); - - for (let m = 0; m < 4; m++) { - const move: PokemonMove | null = this.pokemon && this.pokemon.moveset.length > m ? this.pokemon?.moveset[m] : null; - const moveRowContainer = this.scene.add.container(0, 16 * m); - this.moveRowsContainer.add(moveRowContainer); - - if (move && this.pokemon) { - const spriteKey = Utils.getLocalizedSpriteKey("types"); - const moveType = this.pokemon.getMoveType(move.getMove()); - const typeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); - typeIcon.setOrigin(0, 1); - moveRowContainer.add(typeIcon); + const pp = Utils.padInt(this.newMove?.pp!, 2, " "); // TODO: is this bang correct? + const ppText = addTextObject(this.scene, 173, 1, `${pp}/${pp}`, TextStyle.WINDOW); + ppText.setOrigin(0, 1); + this.extraMoveRowContainer.add(ppText); } - const moveText = addTextObject(this.scene, 35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); - moveText.setOrigin(0, 1); - moveRowContainer.add(moveText); + this.moveRowsContainer = this.scene.add.container(0, 0); + this.movesContainer.add(this.moveRowsContainer); - const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); - ppOverlay.setOrigin(0, 1); - moveRowContainer.add(ppOverlay); + for (let m = 0; m < 4; m++) { + const move: PokemonMove | null = this.pokemon && this.pokemon.moveset.length > m ? this.pokemon?.moveset[m] : null; + const moveRowContainer = this.scene.add.container(0, 16 * m); + this.moveRowsContainer.add(moveRowContainer); - const ppText = addTextObject(this.scene, 173, 1, "--/--", TextStyle.WINDOW); - ppText.setOrigin(0, 1); + if (move && this.pokemon) { + const spriteKey = Utils.getLocalizedSpriteKey("types"); + const moveType = this.pokemon.getMoveType(move.getMove()); + const typeIcon = this.scene.add.sprite(0, 0, spriteKey, Type[moveType].toLowerCase()); + typeIcon.setOrigin(0, 1); + moveRowContainer.add(typeIcon); + } - if (move) { - const maxPP = move.getMovePp(); - const pp = maxPP - move.ppUsed; - ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); + const moveText = addTextObject(this.scene, 35, 0, move ? move.getName() : "-", TextStyle.SUMMARY); + moveText.setOrigin(0, 1); + moveRowContainer.add(moveText); + + const ppOverlay = this.scene.add.image(163, -1, "summary_moves_overlay_pp"); + ppOverlay.setOrigin(0, 1); + moveRowContainer.add(ppOverlay); + + const ppText = addTextObject(this.scene, 173, 1, "--/--", TextStyle.WINDOW); + ppText.setOrigin(0, 1); + + if (move) { + const maxPP = move.getMovePp(); + const pp = maxPP - move.ppUsed; + ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); + } + + moveRowContainer.add(ppText); } - moveRowContainer.add(ppText); - } + this.moveDescriptionText = addTextObject(this.scene, 2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); + this.movesContainer.add(this.moveDescriptionText); - this.moveDescriptionText = addTextObject(this.scene, 2, 84, "", TextStyle.WINDOW_ALT, { wordWrap: { width: 1212 }}); - this.movesContainer.add(this.moveDescriptionText); + const moveDescriptionTextMaskRect = this.scene.make.graphics({}); + moveDescriptionTextMaskRect.setScale(6); + moveDescriptionTextMaskRect.fillStyle(0xFFFFFF); + moveDescriptionTextMaskRect.beginPath(); + moveDescriptionTextMaskRect.fillRect(112, 130, 202, 46); - const moveDescriptionTextMaskRect = this.scene.make.graphics({}); - moveDescriptionTextMaskRect.setScale(6); - moveDescriptionTextMaskRect.fillStyle(0xFFFFFF); - moveDescriptionTextMaskRect.beginPath(); - moveDescriptionTextMaskRect.fillRect(112, 130, 202, 46); + const moveDescriptionTextMask = moveDescriptionTextMaskRect.createGeometryMask(); - const moveDescriptionTextMask = moveDescriptionTextMaskRect.createGeometryMask(); - - this.moveDescriptionText.setMask(moveDescriptionTextMask); - break; + this.moveDescriptionText.setMask(moveDescriptionTextMask); + break; } } diff --git a/src/ui/target-select-ui-handler.ts b/src/ui/target-select-ui-handler.ts index 85b671c2617..4c55a4b960e 100644 --- a/src/ui/target-select-ui-handler.ts +++ b/src/ui/target-select-ui-handler.ts @@ -71,26 +71,26 @@ export default class TargetSelectUiHandler extends UiHandler { success = false; } else { switch (button) { - case Button.UP: - if (this.cursor < BattlerIndex.ENEMY && this.targets.findIndex(t => t >= BattlerIndex.ENEMY) > -1) { - success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY)!); // TODO: is the bang correct here? - } - break; - case Button.DOWN: - if (this.cursor >= BattlerIndex.ENEMY && this.targets.findIndex(t => t < BattlerIndex.ENEMY) > -1) { - success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY)!); // TODO: is the bang correct here? - } - break; - case Button.LEFT: - if (this.cursor % 2 && this.targets.findIndex(t => t === this.cursor - 1) > -1) { - success = this.setCursor(this.cursor - 1); - } - break; - case Button.RIGHT: - if (!(this.cursor % 2) && this.targets.findIndex(t => t === this.cursor + 1) > -1) { - success = this.setCursor(this.cursor + 1); - } - break; + case Button.UP: + if (this.cursor < BattlerIndex.ENEMY && this.targets.findIndex(t => t >= BattlerIndex.ENEMY) > -1) { + success = this.setCursor(this.targets.find(t => t >= BattlerIndex.ENEMY)!); // TODO: is the bang correct here? + } + break; + case Button.DOWN: + if (this.cursor >= BattlerIndex.ENEMY && this.targets.findIndex(t => t < BattlerIndex.ENEMY) > -1) { + success = this.setCursor(this.targets.find(t => t < BattlerIndex.ENEMY)!); // TODO: is the bang correct here? + } + break; + case Button.LEFT: + if (this.cursor % 2 && this.targets.findIndex(t => t === this.cursor - 1) > -1) { + success = this.setCursor(this.cursor - 1); + } + break; + case Button.RIGHT: + if (!(this.cursor % 2) && this.targets.findIndex(t => t === this.cursor + 1) > -1) { + success = this.setCursor(this.cursor + 1); + } + break; } } diff --git a/src/ui/text.ts b/src/ui/text.ts index 22dd3f4cd6a..069aa8680fc 100644 --- a/src/ui/text.ts +++ b/src/ui/text.ts @@ -129,84 +129,84 @@ export function getTextStyleOptions(style: TextStyle, uiTheme: UiTheme, extraSty } switch (style) { - case TextStyle.SUMMARY: - case TextStyle.SUMMARY_ALT: - case TextStyle.SUMMARY_BLUE: - case TextStyle.SUMMARY_RED: - case TextStyle.SUMMARY_PINK: - case TextStyle.SUMMARY_GOLD: - case TextStyle.SUMMARY_GRAY: - case TextStyle.SUMMARY_GREEN: - case TextStyle.WINDOW: - case TextStyle.WINDOW_ALT: - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.STATS_LABEL: - let fontSizeLabel = "96px"; - switch (lang) { - case "de": + case TextStyle.SUMMARY: + case TextStyle.SUMMARY_ALT: + case TextStyle.SUMMARY_BLUE: + case TextStyle.SUMMARY_RED: + case TextStyle.SUMMARY_PINK: + case TextStyle.SUMMARY_GOLD: + case TextStyle.SUMMARY_GRAY: + case TextStyle.SUMMARY_GREEN: + case TextStyle.WINDOW: + case TextStyle.WINDOW_ALT: shadowXpos = 3; shadowYpos = 3; - fontSizeLabel = "80px"; break; - default: - fontSizeLabel = "96px"; + case TextStyle.STATS_LABEL: + let fontSizeLabel = "96px"; + switch (lang) { + case "de": + shadowXpos = 3; + shadowYpos = 3; + fontSizeLabel = "80px"; + break; + default: + fontSizeLabel = "96px"; + break; + } + styleOptions.fontSize = fontSizeLabel; break; - } - styleOptions.fontSize = fontSizeLabel; - break; - case TextStyle.STATS_VALUE: - shadowXpos = 3; - shadowYpos = 3; - let fontSizeValue = "96px"; - switch (lang) { - case "de": - fontSizeValue = "80px"; + case TextStyle.STATS_VALUE: + shadowXpos = 3; + shadowYpos = 3; + let fontSizeValue = "96px"; + switch (lang) { + case "de": + fontSizeValue = "80px"; + break; + default: + fontSizeValue = "96px"; + break; + } + styleOptions.fontSize = fontSizeValue; break; - default: - fontSizeValue = "96px"; + case TextStyle.MESSAGE: + case TextStyle.SETTINGS_LABEL: + case TextStyle.SETTINGS_LOCKED: + case TextStyle.SETTINGS_SELECTED: + break; + case TextStyle.BATTLE_INFO: + case TextStyle.MONEY: + case TextStyle.TOOLTIP_TITLE: + styleOptions.fontSize = defaultFontSize - 24; + shadowXpos = 3.5; + shadowYpos = 3.5; + break; + case TextStyle.PARTY: + case TextStyle.PARTY_RED: + styleOptions.fontSize = defaultFontSize - 30; + styleOptions.fontFamily = "pkmnems"; + break; + case TextStyle.TOOLTIP_CONTENT: + styleOptions.fontSize = defaultFontSize - 32; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.MOVE_INFO_CONTENT: + styleOptions.fontSize = defaultFontSize - 40; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.SMALLER_WINDOW_ALT: + styleOptions.fontSize = defaultFontSize - 36; + shadowXpos = 3; + shadowYpos = 3; + break; + case TextStyle.BGM_BAR: + styleOptions.fontSize = defaultFontSize - 24; + shadowXpos = 3; + shadowYpos = 3; break; - } - styleOptions.fontSize = fontSizeValue; - break; - case TextStyle.MESSAGE: - case TextStyle.SETTINGS_LABEL: - case TextStyle.SETTINGS_LOCKED: - case TextStyle.SETTINGS_SELECTED: - break; - case TextStyle.BATTLE_INFO: - case TextStyle.MONEY: - case TextStyle.TOOLTIP_TITLE: - styleOptions.fontSize = defaultFontSize - 24; - shadowXpos = 3.5; - shadowYpos = 3.5; - break; - case TextStyle.PARTY: - case TextStyle.PARTY_RED: - styleOptions.fontSize = defaultFontSize - 30; - styleOptions.fontFamily = "pkmnems"; - break; - case TextStyle.TOOLTIP_CONTENT: - styleOptions.fontSize = defaultFontSize - 32; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.MOVE_INFO_CONTENT: - styleOptions.fontSize = defaultFontSize - 40; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.SMALLER_WINDOW_ALT: - styleOptions.fontSize = defaultFontSize - 36; - shadowXpos = 3; - shadowYpos = 3; - break; - case TextStyle.BGM_BAR: - styleOptions.fontSize = defaultFontSize - 24; - shadowXpos = 3; - shadowYpos = 3; - break; } const shadowColor = getTextColor(style, true, uiTheme); @@ -257,110 +257,110 @@ export function getTextWithColors(content: string, primaryStyle: TextStyle, uiTh export function getTextColor(textStyle: TextStyle, shadow?: boolean, uiTheme: UiTheme = UiTheme.DEFAULT): string { const isLegacyTheme = uiTheme === UiTheme.LEGACY; switch (textStyle) { - case TextStyle.MESSAGE: - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.WINDOW: - case TextStyle.MOVE_INFO_CONTENT: - case TextStyle.MOVE_PP_FULL: - case TextStyle.TOOLTIP_CONTENT: - case TextStyle.SETTINGS_VALUE: - if (isLegacyTheme) { + case TextStyle.MESSAGE: + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.WINDOW: + case TextStyle.MOVE_INFO_CONTENT: + case TextStyle.MOVE_PP_FULL: + case TextStyle.TOOLTIP_CONTENT: + case TextStyle.SETTINGS_VALUE: + if (isLegacyTheme) { + return !shadow ? "#484848" : "#d0d0c8"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.MOVE_PP_HALF_FULL: + if (isLegacyTheme) { + return !shadow ? "#a68e17" : "#ebd773"; + } + return !shadow ? "#ccbe00" : "#6e672c"; + case TextStyle.MOVE_PP_NEAR_EMPTY: + if (isLegacyTheme) { + return !shadow ? "#d64b00" : "#f7b18b"; + } + return !shadow ? "#d64b00" : "#69402a"; + case TextStyle.MOVE_PP_EMPTY: + if (isLegacyTheme) { + return !shadow ? "#e13d3d" : "#fca2a2"; + } + return !shadow ? "#e13d3d" : "#632929"; + case TextStyle.WINDOW_ALT: return !shadow ? "#484848" : "#d0d0c8"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.MOVE_PP_HALF_FULL: - if (isLegacyTheme) { - return !shadow ? "#a68e17" : "#ebd773"; - } - return !shadow ? "#ccbe00" : "#6e672c"; - case TextStyle.MOVE_PP_NEAR_EMPTY: - if (isLegacyTheme) { - return !shadow ? "#d64b00" : "#f7b18b"; - } - return !shadow ? "#d64b00" : "#69402a"; - case TextStyle.MOVE_PP_EMPTY: - if (isLegacyTheme) { - return !shadow ? "#e13d3d" : "#fca2a2"; - } - return !shadow ? "#e13d3d" : "#632929"; - case TextStyle.WINDOW_ALT: - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.BATTLE_INFO: - if (isLegacyTheme) { - return !shadow ? "#404040" : "#ded6b5"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.PARTY: - return !shadow ? "#f8f8f8" : "#707070"; - case TextStyle.PARTY_RED: - return !shadow ? "#f89890" : "#984038"; - case TextStyle.SUMMARY: - return !shadow ? "#f8f8f8" : "#636363"; - case TextStyle.SUMMARY_ALT: - if (isLegacyTheme) { + case TextStyle.BATTLE_INFO: + if (isLegacyTheme) { + return !shadow ? "#404040" : "#ded6b5"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.PARTY: + return !shadow ? "#f8f8f8" : "#707070"; + case TextStyle.PARTY_RED: + return !shadow ? "#f89890" : "#984038"; + case TextStyle.SUMMARY: return !shadow ? "#f8f8f8" : "#636363"; - } - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.SUMMARY_RED: - case TextStyle.TOOLTIP_TITLE: - return !shadow ? "#e70808" : "#ffbd73"; - case TextStyle.SUMMARY_BLUE: - return !shadow ? "#40c8f8" : "#006090"; - case TextStyle.SUMMARY_PINK: - return !shadow ? "#f89890" : "#984038"; - case TextStyle.SUMMARY_GOLD: - case TextStyle.MONEY: - return !shadow ? "#e8e8a8" : "#a0a060"; - case TextStyle.SETTINGS_LOCKED: - case TextStyle.SUMMARY_GRAY: - return !shadow ? "#a0a0a0" : "#636363"; - case TextStyle.STATS_LABEL: - return !shadow ? "#f8b050" : "#c07800"; - case TextStyle.STATS_VALUE: - if (isLegacyTheme) { + case TextStyle.SUMMARY_ALT: + if (isLegacyTheme) { + return !shadow ? "#f8f8f8" : "#636363"; + } return !shadow ? "#484848" : "#d0d0c8"; - } - return !shadow ? "#f8f8f8" : "#6b5a73"; - case TextStyle.SUMMARY_GREEN: - return !shadow ? "#78c850" : "#306850"; - case TextStyle.SETTINGS_LABEL: - case TextStyle.PERFECT_IV: - return !shadow ? "#f8b050" : "#c07800"; - case TextStyle.SETTINGS_SELECTED: - return !shadow ? "#f88880" : "#f83018"; - case TextStyle.SMALLER_WINDOW_ALT: - return !shadow ? "#484848" : "#d0d0c8"; - case TextStyle.BGM_BAR: - return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.SUMMARY_RED: + case TextStyle.TOOLTIP_TITLE: + return !shadow ? "#e70808" : "#ffbd73"; + case TextStyle.SUMMARY_BLUE: + return !shadow ? "#40c8f8" : "#006090"; + case TextStyle.SUMMARY_PINK: + return !shadow ? "#f89890" : "#984038"; + case TextStyle.SUMMARY_GOLD: + case TextStyle.MONEY: + return !shadow ? "#e8e8a8" : "#a0a060"; + case TextStyle.SETTINGS_LOCKED: + case TextStyle.SUMMARY_GRAY: + return !shadow ? "#a0a0a0" : "#636363"; + case TextStyle.STATS_LABEL: + return !shadow ? "#f8b050" : "#c07800"; + case TextStyle.STATS_VALUE: + if (isLegacyTheme) { + return !shadow ? "#484848" : "#d0d0c8"; + } + return !shadow ? "#f8f8f8" : "#6b5a73"; + case TextStyle.SUMMARY_GREEN: + return !shadow ? "#78c850" : "#306850"; + case TextStyle.SETTINGS_LABEL: + case TextStyle.PERFECT_IV: + return !shadow ? "#f8b050" : "#c07800"; + case TextStyle.SETTINGS_SELECTED: + return !shadow ? "#f88880" : "#f83018"; + case TextStyle.SMALLER_WINDOW_ALT: + return !shadow ? "#484848" : "#d0d0c8"; + case TextStyle.BGM_BAR: + return !shadow ? "#f8f8f8" : "#6b5a73"; } } export function getModifierTierTextTint(tier: ModifierTier): integer { switch (tier) { - case ModifierTier.COMMON: - return 0xf8f8f8; - case ModifierTier.GREAT: - return 0x4998f8; - case ModifierTier.ULTRA: - return 0xf8d038; - case ModifierTier.ROGUE: - return 0xdb4343; - case ModifierTier.MASTER: - return 0xe331c5; - case ModifierTier.LUXURY: - return 0xe74c18; + case ModifierTier.COMMON: + return 0xf8f8f8; + case ModifierTier.GREAT: + return 0x4998f8; + case ModifierTier.ULTRA: + return 0xf8d038; + case ModifierTier.ROGUE: + return 0xdb4343; + case ModifierTier.MASTER: + return 0xe331c5; + case ModifierTier.LUXURY: + return 0xe74c18; } } export function getEggTierTextTint(tier: EggTier): integer { switch (tier) { - case EggTier.COMMON: - return getModifierTierTextTint(ModifierTier.COMMON); - case EggTier.RARE: - return getModifierTierTextTint(ModifierTier.GREAT); - case EggTier.EPIC: - return getModifierTierTextTint(ModifierTier.ULTRA); - case EggTier.LEGENDARY: - return getModifierTierTextTint(ModifierTier.MASTER); + case EggTier.COMMON: + return getModifierTierTextTint(ModifierTier.COMMON); + case EggTier.RARE: + return getModifierTierTextTint(ModifierTier.GREAT); + case EggTier.EPIC: + return getModifierTierTextTint(ModifierTier.ULTRA); + case EggTier.LEGENDARY: + return getModifierTierTextTint(ModifierTier.MASTER); } } diff --git a/src/ui/ui-theme.ts b/src/ui/ui-theme.ts index 8ff50667248..89c56384bd0 100644 --- a/src/ui/ui-theme.ts +++ b/src/ui/ui-theme.ts @@ -10,12 +10,12 @@ export enum WindowVariant { export function getWindowVariantSuffix(windowVariant: WindowVariant): string { switch (windowVariant) { - case WindowVariant.THIN: - return "_thin"; - case WindowVariant.XTHIN: - return "_xthin"; - default: - return ""; + case WindowVariant.THIN: + return "_thin"; + case WindowVariant.XTHIN: + return "_xthin"; + default: + return ""; } } diff --git a/src/utils.ts b/src/utils.ts index c2ee7100909..8a35a4b3f07 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -194,23 +194,23 @@ export function formatLargeNumber(count: integer, threshold: integer): string { const ret = count.toString(); let suffix = ""; switch (Math.ceil(ret.length / 3) - 1) { - case 1: - suffix = "K"; - break; - case 2: - suffix = "M"; - break; - case 3: - suffix = "B"; - break; - case 4: - suffix = "T"; - break; - case 5: - suffix = "q"; - break; - default: - return "?"; + case 1: + suffix = "K"; + break; + case 2: + suffix = "M"; + break; + case 3: + suffix = "B"; + break; + case 4: + suffix = "T"; + break; + case 5: + suffix = "q"; + break; + default: + return "?"; } const digits = ((ret.length + 2) % 3) + 1; let decimalNumber = ret.slice(digits, digits + 2); @@ -487,18 +487,18 @@ export function verifyLang(lang?: string): boolean { } switch (lang) { - case "es": - case "fr": - case "de": - case "it": - case "zh-CN": - case "zh-TW": - case "pt-BR": - case "ko": - case "ja": - return true; - default: - return false; + case "es": + case "fr": + case "de": + case "it": + case "zh-CN": + case "zh-TW": + case "pt-BR": + case "ko": + case "ja": + return true; + default: + return false; } } From e6c06d57be18ef893aa19f5efab81bb67379638f Mon Sep 17 00:00:00 2001 From: MokaStitcher <54149968+MokaStitcher@users.noreply.github.com> Date: Sun, 20 Oct 2024 23:55:07 +0200 Subject: [PATCH 70/75] [P All][Bug] Various ME bugfixes (copy) (#4695) * Mystery Encounter bugfixes * more ME bug fixes * update allowed pokemon in ME requirements * some unit test cleanup and general tidying * fix null exception on isBattleMysteryEncounter * clean up tsdocs and fix pokemon hasAbility check * fix double battle crash in challenge mode with a single eligible pokemon * apply suggestions from PR#4619's code reviews * revert fix for Keldeo crashes + implement fix suggestion from PR #4619 * fix session migration for PokemonCustomData * prevent test failure due to keldeo fix --------- Co-authored-by: ImperialSympathizer --- public/images/items.json | 10 +-- public/images/items.png | Bin 58664 -> 58527 bytes public/images/items/black_sludge.png | Bin 1026 -> 285 bytes src/battle-scene.ts | 37 ++++++-- ...pokemon-data.ts => custom-pokemon-data.ts} | 9 +- .../an-offer-you-cant-refuse-encounter.ts | 4 +- .../encounters/bug-type-superfan-encounter.ts | 13 ++- .../encounters/clowning-around-encounter.ts | 28 +++--- .../encounters/dancing-lessons-encounter.ts | 2 +- .../encounters/fight-or-flight-encounter.ts | 2 +- .../encounters/part-timer-encounter.ts | 2 +- .../shady-vitamin-dealer-encounter.ts | 2 +- .../slumbering-snorlax-encounter.ts | 6 +- .../the-expert-pokemon-breeder-encounter.ts | 28 +++--- .../encounters/the-strong-stuff-encounter.ts | 4 +- .../encounters/uncommon-breed-encounter.ts | 2 +- .../encounters/weird-dream-encounter.ts | 8 +- .../mystery-encounter-requirements.ts | 35 +++++--- .../mystery-encounters/mystery-encounter.ts | 2 +- .../utils/encounter-phase-utils.ts | 10 +-- src/field/pokemon.ts | 80 ++++++++++++------ src/modifier/modifier-type.ts | 4 +- src/modifier/modifier.ts | 2 +- src/phases/encounter-phase.ts | 10 ++- src/phases/mystery-encounter-phases.ts | 2 +- src/system/pokemon-data.ts | 19 +++-- .../version_migration/version_converter.ts | 15 ++++ .../version_migration/versions/v1_1_0.ts | 32 +++++++ .../bug-type-superfan-encounter.test.ts | 7 +- .../clowning-around-encounter.test.ts | 16 ++-- .../the-strong-stuff-encounter.test.ts | 4 +- .../encounters/weird-dream-encounter.test.ts | 2 +- 32 files changed, 263 insertions(+), 134 deletions(-) rename src/data/{mystery-encounters/mystery-encounter-pokemon-data.ts => custom-pokemon-data.ts} (68%) create mode 100644 src/system/version_migration/versions/v1_1_0.ts diff --git a/public/images/items.json b/public/images/items.json index 779823d1293..05d021b6a06 100644 --- a/public/images/items.json +++ b/public/images/items.json @@ -3416,12 +3416,12 @@ "rotated": false, "trimmed": true, "sourceSize": { - "w": 24, - "h": 24 + "w": 32, + "h": 32 }, "spriteSourceSize": { - "x": 1, - "y": 2, + "x": 5, + "y": 7, "w": 22, "h": 19 }, @@ -8415,6 +8415,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:934ea4080bad980d4fea720cc771f133:ed564bc47b79b15a763de57045178e88:110e074689c9edd2c54833ce2e4d9270$" + "smartupdate": "$TexturePacker:SmartUpdate:9ef21166268f7487fc9ff8d0f9b996e4:82658ac7bdd4c2b417e1f59168179262:110e074689c9edd2c54833ce2e4d9270$" } } diff --git a/public/images/items.png b/public/images/items.png index 5f032b30cfb048c2d0cf71ec1e6ad3f051d832c9..8aaa0281c0088a4b0ded764af22c3d5456ce167f 100644 GIT binary patch literal 58527 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 literal 58664 zcmV*IKxe;+P)eEYJmOHfSv^xDv^OF=z7#$;!jQa|^)+x^qh z(?mh?`uBf(eqUf?_A4yn#q{8Yb-RK(_nD51kB$BN#I@t;o~yUGi*ks@@cH5J`n_Im zZE@4Una}n8yvqCJ!e+S4*KU@t<@){jSy=tT^Ppdp^wh5A&rWWR_Vwtazm+20;Po$_2u#ke!$(TBD2{$GJ|ic_wpqCBXi z2FdS$vh1K9c)RJ|Kv#uschagP*0Y zoWMI;Y32IToN^V0UugZv5pUc4eq_?1kXp^R3|BpEFlNMaoNVQ!MQmvdpo|<-Nye$A zT*ZeXMD;(~Cwj*OK^xwLo+5`&X7H_NSce}QEP=I#S z)wc1~7a}+xXBJnc<9G4$2C%3z}F!<~Kqn&?QZH1X8h4sf9ld~)>77d+IGz=p4fYlcj79vL=C{E~G`dX9GvQ7mB?p`%%0000AbW%=J0RR90 zd;kCbC7a7)pnCuS010qNS#tmY4#WTe4#WYKD-Ig~03ZNKL_t(|+U&gvd=*u;@1JvW zccpKI%+m=R2!sq^7$On~7@LqV6U@+`Kp;xO7%(8iRU(Rf@&W>y76L;01~q^X1Ohl9 zjev~^3W%Tx7g~sd3Q?clwiT83{psPq)*h<%uBwy7sqcOF^VZ35QkCJPzV%yc?X_3! z^7H$D_J6DC`VC*Zn)rJwMhKxF@RKmF*BdCFIR1qE&HPui@;bk7Vc)fu{=XmhbL7U4 z_?^E0nByKdHRFlf+-iMh{+_SDPP?Hy{(+`Vx?fX6OaCwX$5K*;6S5;E#Xr(}-@?ijGxP=JyYicdos9CvVVFmiIklQ1hUzneF|e(P)dpV#yA50y0ekwYc< zO|9VCF&y4@>;PUxMCJt_BI$de11>P*yZy+0gDYB=*!NF#c*56lzgAUP<@1EkUY70i z=T`Mz!$9ma5}$z>_J|!At5obF(L3V#N?Hi7Z!%J@&?#> z`Q6<5wZuz2(@XM84sB{GDLHhgsYPI92fzYcJN%Q93<80mDuxO^1lo8Nsldoi;C0VE z*PW2*I{usAF9lvUU~J#DdBCg6d7s`9cxC(exf8%^mGizEJ%QJ1AHVZy;N<{JKYaMZ zHETXRd{}Se9yce2784sQ=_@a6z<^++=ESS1d28d>;Qbi9>+E+nZ8~)M5ZL8I&CNGk ziUA}dg32p9+mx3VQAGywcWOvs|8*V<%_H;#n6B47G3SXV=IEBA zR|Tf)d#bXks}c9?bFDllE2y{gZuReh}cR)gt!+3NGFSD&};+Y6@a z?;);uJCwzxhcmzqmoC=*boq$++T#XDu~b*v!6sSv>oXaT1u?zp4wi4Jq-sQLn0z^N)LYfn{SR+g$SpcMu$DJ%?m zg{k@iTEJ=5lqs`U_3qP0)fdnLPOBf@UbcGC`SYs2fEIAt`}W(wD_(Vo1dj|ETUuIL z4ezB3;sW8LuI?$d>;WvHu}oTAW3dJ=kX4t7U|quUth{vn&>{4BbLQk9BIN6@x0V?% zno|RPyhRpU9W~C~@Tn`IdwhTZ4s+#-5di{S&(E6}XZ!lyw zdqJP^%FmJQ=NEw?v$93!BX26d^X+&vWcFTs{E9gF$9kT=Q`gt589R3S-5*zGWGpxl zxZCAgtjl{csF`~ZODv-m!-2&nnY<`20A}T-*VaN>v9sh5W*0ZFzqR$I@gi>|-`INN zMpIKwO$`8Re(>hkC39+PbszGQq8FDJrW|O9OXbC~c|v4)-E;l?b&wd?_4JCt_uYrU z^d3FtVAw)rb+>eB;_*Y^%EGXP$o6FY^;=~hlDhwXV1~SEJj%Wg$M(Ung~(?2`A+@0 zhaWzVVGEI!oiDqo?0frd$_kOa7ysutWnWB2#+_dOPd-@#|8B_GPu6_02K*k%$lSAF zK}N<{uepcuij83w0#8GQynt68@QPgF$ZJM^e#wyL%lSa8r1^4l+6|#QP*&X#fK^vl z$B=C-$*(PGa+cQ)CtgfpUdT5TUZg+R0x~j8me=*`>E{vGbt

?z@lBAUfj?z?JaC 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 diff --git a/public/images/items/black_sludge.png b/public/images/items/black_sludge.png index 39684a403108232447925646dd56e61186de91c0..37aa31de43eaa5314563d42550b2fc912c1a4210 100644 GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10LMXr|#1%--S+b?Odrfh1 zx0hFxf`UTZnx~V1qKqX$e!&b5&u*jvIklcHjv*Dd-cEMxVs_*?a%|F{|N8F}72Ch0 z*iCp5)pV$BSKXZryU%a@wP4MehToe0a=of8?ui-)?*3-$W_;rpDLg~+Oy4}-)iPfE z$-E4QSsur3VBF9WyhCiZBtyVCu8tzN)puSC_$&^3y5kU|f$gXIvT(NAy`NIzYwipE ZW0)Dp%oLNzu)Ha0{(J4HS} zS3f{kKR{VPK370NT0lZnLPAhNLRmvZT0}!yL_}0XL|sHhTSY@(MMPaiMqEZjPDV#y zMo3vkNmEBeU`Iw)M@CpjNMT1wTS!G@NJd~tM^H#dS4c-$NJv#lM`1}vV@XI|Nl8^n zNm)rrU`a`0NlIKwNMcG#WlBp@N=sBqO;t-tV@pj@OHNo!OJYn*WK2w4Oig1=PGwC_ zXH8I8PE1x#Ok++>R8CK3PflV_Qe96|TTo9{QBqrGXMJdBc4=yWYHNaOYlLfRd~Ium zZES;WZFy~Ngl%tpZfu5bY>IAegl=w!Zf}KeZHI4diEnR+Z*Ychaei=diE(d+a&L=r zZ;f(qkaBQ`a&U=qaf5PleR6Vta&wGxafx$ri*#|2ba9e&a*lL!h;(#|b#sk%bB}d& ziFI~}b#{t&c8GR&jdysCcz20-d60W~k$ZcKet(yLfSG@Rm4Jbof`XfZgO!7WoP&g# zgn^=jgq?(ikc5Ssg@l`hg_VVcmxYF$g@}}fhn|LqpoWN=hlQSphMI?mqlbu}hlrtv ziJ6CrmxzU=h=!hshnk6or-_M|iHV?ziK2>#qKb)}iix0#ikgawql$~6i;J0yi=B&$ zq>PKAjEtU)jiZf@rHziKj*F>|jH8Z^rjC!OkBym+jirx{p^%ZIl98yBl&X`Itdy0m zm6orUm#~&_Lxr7i+QEB~r zPKXBfybUXo)DjaFmRaf;#+2riE%t)C$~yn}o*4_)M>rOy7iL$)P2z$3gPnt~dF_#f w4PnbC?W$sQMhO%fejYAXRskL+HU|0u0CSsUMn*K#UjP6A07*qoM6N<$g7)&aN&o-= diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 0d482a5f1a5..096a84dde3d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -86,7 +86,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { TurnInitPhase } from "#app/phases/turn-init-phase"; import { ShopCursorTarget } from "#app/enums/shop-cursor-target"; import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; -import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; +import { allMysteryEncounters, ANTI_VARIANCE_WEIGHT_MODIFIER, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, MYSTERY_ENCOUNTER_SPAWN_MAX_WEIGHT, mysteryEncountersByBiome } from "#app/data/mystery-encounters/mystery-encounters"; import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -1207,12 +1207,10 @@ export default class BattleScene extends SceneBase { // Check for mystery encounter // Can only occur in place of a standard (non-boss) wild battle, waves 10-180 - if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex, mysteryEncounterType) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { + if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) { newBattleType = BattleType.MYSTERY_ENCOUNTER; - // Reset base spawn weight + // Reset to base spawn weight this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT; - } else if (newBattleType === BattleType.WILD) { - this.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; } } @@ -2364,6 +2362,19 @@ export default class BattleScene extends SceneBase { return false; } + /** + * Will search for a specific phase in {@linkcode phaseQueuePrepend} via filter, and remove the first result if a match is found. + * @param phaseFilter filter function + */ + tryRemoveUnshiftedPhase(phaseFilter: (phase: Phase) => boolean): boolean { + const phaseIndex = this.phaseQueuePrepend.findIndex(phaseFilter); + if (phaseIndex > -1) { + this.phaseQueuePrepend.splice(phaseIndex, 1); + return true; + } + return false; + } + /** * Tries to add the input phase to index before target phase in the phaseQueue, else simply calls unshiftPhase() * @param phase {@linkcode Phase} the phase to be added @@ -3125,18 +3136,26 @@ export default class BattleScene extends SceneBase { } } + /** + * Returns if a wave COULD spawn a {@linkcode MysteryEncounter}. + * Even if returns `true`, does not guarantee that a wave will actually be a ME. + * That check is made in {@linkcode BattleScene.isWaveMysteryEncounter} instead. + */ + isMysteryEncounterValidForWave(battleType: BattleType, waveIndex: number): boolean { + const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); + return this.gameMode.hasMysteryEncounters && battleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave; + } + /** * Determines whether a wave should randomly generate a {@linkcode MysteryEncounter}. * Currently, the only modes that MEs are allowed in are Classic and Challenge. * Additionally, MEs cannot spawn outside of waves 10-180 in those modes - * * @param newBattleType * @param waveIndex - * @param sessionDataEncounterType */ - private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number, sessionDataEncounterType?: MysteryEncounterType): boolean { + private isWaveMysteryEncounter(newBattleType: BattleType, waveIndex: number): boolean { const [ lowestMysteryEncounterWave, highestMysteryEncounterWave ] = this.gameMode.getMysteryEncounterLegalWaves(); - if (this.gameMode.hasMysteryEncounters && newBattleType === BattleType.WILD && !this.gameMode.isBoss(waveIndex) && waveIndex < highestMysteryEncounterWave && waveIndex > lowestMysteryEncounterWave) { + if (this.isMysteryEncounterValidForWave(newBattleType, waveIndex)) { // Base spawn weight is BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT/256, and increases by WEIGHT_INCREMENT_ON_SPAWN_MISS/256 for each missed attempt at spawning an encounter on a valid floor const sessionEncounterRate = this.mysteryEncounterSaveData.encounterSpawnChance; const encounteredEvents = this.mysteryEncounterSaveData.encounteredEvents; diff --git a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts b/src/data/custom-pokemon-data.ts similarity index 68% rename from src/data/mystery-encounters/mystery-encounter-pokemon-data.ts rename to src/data/custom-pokemon-data.ts index fc6ce313d41..2e94123fc84 100644 --- a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts +++ b/src/data/custom-pokemon-data.ts @@ -1,18 +1,20 @@ import { Abilities } from "#enums/abilities"; import { Type } from "#app/data/type"; import { isNullOrUndefined } from "#app/utils"; +import { Nature } from "#enums/nature"; /** * Data that can customize a Pokemon in non-standard ways from its Species - * Currently only used by Mystery Encounters, may need to be renamed if it becomes more widely used + * Currently only used by Mystery Encounters and Mints. */ -export class MysteryEncounterPokemonData { +export class CustomPokemonData { public spriteScale: number; public ability: Abilities | -1; public passive: Abilities | -1; + public nature: Nature | -1; public types: Type[]; - constructor(data?: MysteryEncounterPokemonData | Partial) { + constructor(data?: CustomPokemonData | Partial) { if (!isNullOrUndefined(data)) { Object.assign(this, data); } @@ -20,6 +22,7 @@ export class MysteryEncounterPokemonData { this.spriteScale = this.spriteScale ?? -1; this.ability = this.ability ?? -1; this.passive = this.passive ?? -1; + this.nature = this.nature ?? -1; this.types = this.types ?? []; } } diff --git a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts index ab892ae00f2..8dc4eca25bf 100644 --- a/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts +++ b/src/data/mystery-encounters/encounters/an-offer-you-cant-refuse-encounter.ts @@ -133,8 +133,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( - new MoveRequirement(EXTORTION_MOVES), - new AbilityRequirement(EXTORTION_ABILITIES)) + new MoveRequirement(EXTORTION_MOVES, true), + new AbilityRequirement(EXTORTION_ABILITIES, true)) ) .withDialogue({ buttonLabel: `${namespace}:option.2.label`, diff --git a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts index b5d47cf6912..d316ab14cde 100644 --- a/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts +++ b/src/data/mystery-encounters/encounters/bug-type-superfan-encounter.ts @@ -42,6 +42,8 @@ import { AttackTypeBoosterModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, + GigantamaxAccessModifier, + MegaEvolutionAccessModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import i18next from "i18next"; @@ -356,10 +358,17 @@ export const BugTypeSuperfanEncounter: MysteryEncounter = }, ]; } else { - // If player has any evolution/form change items that are valid for their party, will spawn one of those items in addition to a Master Ball - const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)!, generateModifierTypeOption(scene, modifierTypes.MAX_LURE)! ]; + // If the player has any evolution/form change items that are valid for their party, + // spawn one of those items in addition to Dynamax Band, Mega Band, and Master Ball + const modifierOptions: ModifierTypeOption[] = [ generateModifierTypeOption(scene, modifierTypes.MASTER_BALL)! ]; const specialOptions: ModifierTypeOption[] = []; + if (!scene.findModifier(m => m instanceof MegaEvolutionAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.MEGA_BRACELET)!); + } + if (!scene.findModifier(m => m instanceof GigantamaxAccessModifier)) { + modifierOptions.push(generateModifierTypeOption(scene, modifierTypes.DYNAMAX_BAND)!); + } const nonRareEvolutionModifier = generateModifierTypeOption(scene, modifierTypes.EVOLUTION_ITEM); if (nonRareEvolutionModifier) { specialOptions.push(nonRareEvolutionModifier); diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index be52ab42c9d..57c8aa7a561 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -28,7 +28,7 @@ import { BattlerIndex } from "#app/battle"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { MoveCategory } from "#app/data/move"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { EncounterAnim } from "#enums/encounter-anims"; import { Challenges } from "#enums/challenges"; @@ -133,7 +133,7 @@ export const ClowningAroundEncounter: MysteryEncounter = }, { // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter species: getPokemonSpecies(Species.BLACEPHALON), - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}), + customPokemonData: new CustomPokemonData({ ability: ability, types: [ randSeedInt(18), randSeedInt(18) ]}), isBoss: true, moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ] }, @@ -353,15 +353,15 @@ export const ClowningAroundEncounter: MysteryEncounter = newTypes.push(secondType); // Apply the type changes (to both base and fusion, if pokemon is fused) - if (!pokemon.mysteryEncounterPokemonData) { - pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); } - pokemon.mysteryEncounterPokemonData.types = newTypes; + pokemon.customPokemonData.types = newTypes; if (pokemon.isFusion()) { - if (!pokemon.fusionMysteryEncounterPokemonData) { - pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); } - pokemon.fusionMysteryEncounterPokemonData.types = newTypes; + pokemon.fusionCustomPokemonData.types = newTypes; } } }) @@ -426,15 +426,15 @@ function onYesAbilitySwap(scene: BattleScene, resolve) { // Do ability swap const encounter = scene.currentBattle.mysteryEncounter!; if (pokemon.isFusion()) { - if (!pokemon.fusionMysteryEncounterPokemonData) { - pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); } - pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability; + pokemon.fusionCustomPokemonData.ability = encounter.misc.ability; } else { - if (!pokemon.mysteryEncounterPokemonData) { - pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); } - pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability; + pokemon.customPokemonData.ability = encounter.misc.ability; } encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index d7f71194f48..55d7ce0e92d 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -236,7 +236,7 @@ export const DancingLessonsEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(DANCING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 380662ca817..889868519d2 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -145,7 +145,7 @@ export const FightOrFlightEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.2.label`, buttonTooltip: `${namespace}:option.2.tooltip`, diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index 17a3a366569..092d2ab2673 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -227,7 +227,7 @@ export const PartTimerEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option3PrimaryName and option3PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, 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 5b609a2b1c3..d30c97b27de 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -138,7 +138,7 @@ export const ShadyVitaminDealerEncounter: MysteryEncounter = newNature = randSeedInt(25) as Nature; } - chosenPokemon.nature = newNature; + chosenPokemon.customPokemonData.nature = newNature; encounter.setDialogueToken("newNature", getNatureName(newNature)); queueEncounterMessage(scene, `${namespace}:cheap_side_effects`); setEncounterExp(scene, [ chosenPokemon.id ], 100); diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 3a4bf465a78..8ea19e1225b 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -18,7 +18,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { PartyHealPhase } from "#app/phases/party-heal-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { BerryType } from "#enums/berry-type"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; /** i18n namespace for the encounter */ const namespace = "mysteryEncounters/slumberingSnorlax"; @@ -72,7 +72,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = stackCount: 2 }, ], - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep }; const config: EnemyPartyConfig = { @@ -143,7 +143,7 @@ export const SlumberingSnorlaxEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES)) + .withPrimaryPokemonRequirement(new MoveRequirement(STEALING_MOVES, true)) .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, 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 945e7ee188d..9c10d33d019 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 @@ -25,6 +25,7 @@ import { achvs } from "#app/system/achv"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { Type } from "#app/data/type"; import { getPokeballTintColor } from "#app/data/pokeball"; +import { PokemonHeldItemModifier } from "#app/modifier/modifier"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/theExpertPokemonBreeder"; @@ -163,7 +164,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = if (pokemon2CommonEggs > 0) { const eggsText = i18next.t(`${namespace}:numEggs`, { count: pokemon2CommonEggs, rarity: i18next.t("egg:defaultTier") }); pokemon2Tooltip += i18next.t(`${namespace}:eggs_tooltip`, { eggs: eggsText }); - encounter.setDialogueToken("pokemon1CommonEggs", eggsText); + encounter.setDialogueToken("pokemon2CommonEggs", eggsText); } encounter.options[1].dialogue!.buttonTooltip = pokemon2Tooltip; @@ -221,7 +222,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon1; encounter.setDialogueToken("chosenPokemon", pokemon1.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon1CommonEggs, pokemon1RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); @@ -247,9 +248,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.onGameOver = onGameOver; await initBattleWithEnemyConfig(scene, config); }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); - }) .build() ) .withOption( @@ -273,7 +271,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon2; encounter.setDialogueToken("chosenPokemon", pokemon2.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon2CommonEggs, pokemon2RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); @@ -299,9 +297,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.onGameOver = onGameOver; await initBattleWithEnemyConfig(scene, config); }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); - }) .build() ) .withOption( @@ -325,7 +320,7 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.misc.chosenPokemon = pokemon3; encounter.setDialogueToken("chosenPokemon", pokemon3.getNameToRender()); const eggOptions = getEggOptions(scene, pokemon3CommonEggs, pokemon3RareEggs); - setEncounterRewards(scene, { fillRemaining: true }, eggOptions); + setEncounterRewards(scene, { fillRemaining: true }, eggOptions, () => doPostEncounterCleanup(scene)); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); @@ -351,9 +346,6 @@ export const TheExpertPokemonBreederEncounter: MysteryEncounter = encounter.onGameOver = onGameOver; await initBattleWithEnemyConfig(scene, config); }) - .withPostOptionPhase(async (scene: BattleScene) => { - await doPostEncounterCleanup(scene); - }) .build() ) .withOutroDialogue([ @@ -521,19 +513,19 @@ function checkAchievement(scene: BattleScene) { } } -async function restorePartyAndHeldItems(scene: BattleScene) { +function restorePartyAndHeldItems(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; // Restore original party scene.getParty().push(...encounter.misc.originalParty); // Restore held items const originalHeldItems = encounter.misc.originalPartyHeldItems; - originalHeldItems.forEach(pokemonHeldItemsList => { + originalHeldItems.forEach((pokemonHeldItemsList: PokemonHeldItemModifier[]) => { pokemonHeldItemsList.forEach(heldItem => { scene.addModifier(heldItem, true, false, false, true); }); }); - await scene.updateModifiers(true); + scene.updateModifiers(true); } function onGameOver(scene: BattleScene) { @@ -609,13 +601,13 @@ function onGameOver(scene: BattleScene) { return false; } -async function doPostEncounterCleanup(scene: BattleScene) { +function doPostEncounterCleanup(scene: BattleScene) { const encounter = scene.currentBattle.mysteryEncounter!; if (!encounter.misc.encounterFailed) { // Give achievement if in Space biome checkAchievement(scene); // Give 20 friendship to the chosen pokemon encounter.misc.chosenPokemon.addFriendship(FRIENDSHIP_ADDED); - await restorePartyAndHeldItems(scene); + restorePartyAndHeldItems(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 03cf86d06a5..397d2af9522 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -14,7 +14,7 @@ import { BattlerIndex } from "#app/battle"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { Stat } from "#enums/stat"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; @@ -79,7 +79,7 @@ export const TheStrongStuffEncounter: MysteryEncounter = species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], modifierConfigs: [ diff --git a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts index 13594f273d9..b7ce3bab48c 100644 --- a/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts +++ b/src/data/mystery-encounters/encounters/uncommon-breed-encounter.ts @@ -210,7 +210,7 @@ export const UncommonBreedEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically + .withPrimaryPokemonRequirement(new MoveRequirement(CHARMING_MOVES, true)) // Will set option2PrimaryName and option2PrimaryMove dialogue tokens automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 6e2f8352480..b97a22dbe51 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -12,7 +12,7 @@ import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from " import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { achvs } from "#app/system/achv"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; @@ -379,10 +379,10 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon newType = randSeedInt(18) as Type; } newTypes.push(newType); - if (!newPokemon.mysteryEncounterPokemonData) { - newPokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + if (!newPokemon.customPokemonData) { + newPokemon.customPokemonData = new CustomPokemonData(); } - newPokemon.mysteryEncounterPokemonData.types = newTypes; + newPokemon.customPokemonData.types = newTypes; for (const item of transformation.heldItems) { item.pokemonId = newPokemon.id; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 86e04e80807..a57cedc8fa3 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -15,6 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { AttackTypeBoosterModifier } from "#app/modifier/modifier"; import { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type"; import { SpeciesFormKey } from "#enums/species-form-key"; +import { allAbilities } from "#app/data/ability"; export interface EncounterRequirement { meetsRequirement(scene: BattleScene): boolean; // Boolean to see if a requirement is met @@ -476,9 +477,11 @@ export class MoveRequirement extends EncounterPokemonRequirement { requiredMoves: Moves[] = []; minNumberOfPokemon: number; invertQuery: boolean; + excludeDisallowedPokemon: boolean; - constructor(moves: Moves | Moves[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { + constructor(moves: Moves | Moves[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) { super(); + this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; this.requiredMoves = Array.isArray(moves) ? moves : [ moves ]; @@ -494,10 +497,15 @@ export class MoveRequirement extends EncounterPokemonRequirement { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length > 0).length > 0); + // get the Pokemon with at least one move in the required moves list + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId))); } else { // for an inverted query, we only want to get the pokemon that don't have ANY of the listed moves - return partyPokemon.filter((pokemon) => this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move?.moveId === reqMove).length === 0).length === 0); + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && !pokemon.moveset.some((move) => move?.moveId && this.requiredMoves.includes(move.moveId))); } } @@ -559,9 +567,11 @@ export class AbilityRequirement extends EncounterPokemonRequirement { requiredAbilities: Abilities[]; minNumberOfPokemon: number; invertQuery: boolean; + excludeDisallowedPokemon: boolean; - constructor(abilities: Abilities | Abilities[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { + constructor(abilities: Abilities | Abilities[], excludeDisallowedPokemon: boolean, minNumberOfPokemon: number = 1, invertQuery: boolean = false) { super(); + this.excludeDisallowedPokemon = excludeDisallowedPokemon; this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; this.requiredAbilities = Array.isArray(abilities) ? abilities : [ abilities ]; @@ -577,16 +587,21 @@ export class AbilityRequirement extends EncounterPokemonRequirement { override queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => this.requiredAbilities.some((ability) => pokemon.getAbility().id === ability)); + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && this.requiredAbilities.some((ability) => pokemon.hasAbility(ability, false))); } else { - // for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilitiess - return partyPokemon.filter((pokemon) => this.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability).length === 0); + // for an inverted query, we only want to get the pokemon that don't have ANY of the listed abilities + return partyPokemon.filter((pokemon) => + (!this.excludeDisallowedPokemon || pokemon.isAllowedInBattle()) + && this.requiredAbilities.filter((ability) => pokemon.hasAbility(ability, false)).length === 0); } } - override getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - if (pokemon?.getAbility().id && this.requiredAbilities.some(a => pokemon.getAbility().id === a)) { - return [ "ability", pokemon.getAbility().name ]; + override getDialogueToken(_scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { + const matchingAbility = this.requiredAbilities.find(a => pokemon?.hasAbility(a, false)); + if (!isNullOrUndefined(matchingAbility)) { + return [ "ability", allAbilities[matchingAbility].name ]; } return [ "ability", "" ]; } diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index 7e175957e21..ee9eb159e10 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -325,7 +325,7 @@ export default class MysteryEncounter implements IMysteryEncounter { if (activeMon.length > 0) { this.primaryPokemon = activeMon[0]; } else { - this.primaryPokemon = scene.getParty().filter(p => !p.isFainted())[0]; + this.primaryPokemon = scene.getParty().filter(p => p.isAllowedInBattle())[0]; } return true; } diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index 485b955f998..5cd2fbffd5f 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -27,7 +27,7 @@ import { Status, StatusEffect } from "#app/data/status-effect"; import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-config"; import PokemonSpecies from "#app/data/pokemon-species"; import { Egg, IEggOptions } from "#app/data/egg"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import HeldModifierConfig from "#app/interfaces/held-modifier-config"; import { MovePhase } from "#app/phases/move-phase"; import { EggLapsePhase } from "#app/phases/egg-lapse-phase"; @@ -71,7 +71,7 @@ export interface EnemyPokemonConfig { nickname?: string; bossSegments?: number; bossSegmentModifier?: number; // Additive to the determined segment number - mysteryEncounterPokemonData?: MysteryEncounterPokemonData; + customPokemonData?: CustomPokemonData; formIndex?: number; abilityIndex?: number; level?: number; @@ -145,7 +145,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: newTrainer.setVisible(false); scene.field.add(newTrainer); scene.currentBattle.trainer = newTrainer; - loadEnemyAssets.push(newTrainer.loadAssets()); + loadEnemyAssets.push(newTrainer.loadAssets().then(() => newTrainer.initSprite())); battle.enemyLevels = scene.currentBattle.trainer.getPartyLevels(scene.currentBattle.waveIndex); } else { @@ -250,8 +250,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: } // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) - if (!isNullOrUndefined(config.mysteryEncounterPokemonData)) { - enemyPokemon.mysteryEncounterPokemonData = config.mysteryEncounterPokemonData; + if (!isNullOrUndefined(config.customPokemonData)) { + enemyPokemon.customPokemonData = config.customPokemonData; } // Set Boss diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0afbbf105e2..7f2b4ec015d 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -62,7 +62,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { Challenges } from "#enums/challenges"; import { PokemonAnimType } from "#enums/pokemon-anim-type"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; import { BASE_HIDDEN_ABILITY_CHANCE, BASE_SHINY_CHANCE, SHINY_EPIC_CHANCE, SHINY_VARIANT_CHANCE } from "#app/data/balance/rates"; @@ -114,7 +114,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData | null; + public fusionCustomPokemonData: CustomPokemonData | null; private summonDataPrimer: PokemonSummonData | null; @@ -122,7 +122,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public battleData: PokemonBattleData; public battleSummonData: PokemonBattleSummonData; public turnData: PokemonTurnData; - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; /** Used by Mystery Encounters to execute pokemon-specific logic (such as stat boosts) at start of battle */ public mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; @@ -193,7 +193,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.nature = dataSource.nature || 0 as Nature; this.nickname = dataSource.nickname; - this.natureOverride = dataSource.natureOverride !== undefined ? dataSource.natureOverride : -1; this.moveset = dataSource.moveset; this.status = dataSource.status!; // TODO: is this bang correct? this.friendship = dataSource.friendship !== undefined ? dataSource.friendship : this.species.baseFriendship; @@ -212,9 +211,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = dataSource.fusionVariant || 0; this.fusionGender = dataSource.fusionGender; this.fusionLuck = dataSource.fusionLuck; - this.fusionMysteryEncounterPokemonData = dataSource.fusionMysteryEncounterPokemonData; + this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.usedTMs = dataSource.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(dataSource.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); } else { this.id = Utils.randSeedInt(4294967296); this.ivs = ivs || Utils.getIvsFromId(this.id); @@ -235,7 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.variant = this.shiny ? this.generateVariant() : 0; } - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); + this.customPokemonData = new CustomPokemonData(); if (nature !== undefined) { this.setNature(nature); @@ -243,8 +242,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.generateNature(); } - this.natureOverride = -1; - this.friendship = species.baseFriendship; this.metLevel = level; this.metBiome = scene.currentBattle ? scene.arena.biomeType : -1; @@ -593,8 +590,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const formKey = this.getFormKey(); if (this.isMax() === true || formKey === "segin-starmobile" || formKey === "schedar-starmobile" || formKey === "navi-starmobile" || formKey === "ruchbah-starmobile" || formKey === "caph-starmobile") { return 1.5; - } else if (this.mysteryEncounterPokemonData.spriteScale > 0) { - return this.mysteryEncounterPokemonData.spriteScale; + } else if (this.customPokemonData.spriteScale > 0) { + return this.customPokemonData.spriteScale; } return 1; } @@ -1023,7 +1020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } getNature(): Nature { - return this.natureOverride !== -1 ? this.natureOverride : this.nature; + return this.customPokemonData.nature !== -1 ? this.customPokemonData.nature : this.nature; } setNature(nature: Nature): void { @@ -1198,15 +1195,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (!types.length || !includeTeraType) { if (!ignoreOverride && this.summonData?.types && this.summonData.types.length > 0) { this.summonData.types.forEach(t => types.push(t)); - } else if (this.mysteryEncounterPokemonData.types && this.mysteryEncounterPokemonData.types.length > 0) { + } else if (this.customPokemonData.types && this.customPokemonData.types.length > 0) { // "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters - types.push(this.mysteryEncounterPokemonData.types[0]); + types.push(this.customPokemonData.types[0]); // Fusing a Pokemon onto something with "permanently changed" types will still apply the fusion's types as normal const fusionSpeciesForm = this.getFusionSpeciesForm(ignoreOverride); if (fusionSpeciesForm) { // Check if the fusion Pokemon also had "permanently changed" types - const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; + const fusionMETypes = this.fusionCustomPokemonData?.types; if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { types.push(fusionMETypes[1]); } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { @@ -1218,8 +1215,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } - if (types.length === 1 && this.mysteryEncounterPokemonData.types.length >= 2) { - types.push(this.mysteryEncounterPokemonData.types[1]); + if (types.length === 1 && this.customPokemonData.types.length >= 2) { + types.push(this.customPokemonData.types[1]); } } else { const speciesForm = this.getSpeciesForm(ignoreOverride); @@ -1230,7 +1227,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (fusionSpeciesForm) { // Check if the fusion Pokemon also had "permanently changed" types // Otherwise, use standard fusion type logic - const fusionMETypes = this.fusionMysteryEncounterPokemonData?.types; + const fusionMETypes = this.fusionCustomPokemonData?.types; if (fusionMETypes && fusionMETypes.length >= 2 && fusionMETypes[1] !== types[0]) { types.push(fusionMETypes[1]); } else if (fusionMETypes && fusionMETypes.length === 1 && fusionMETypes[0] !== types[0]) { @@ -1262,6 +1259,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } } + // If both types are the same (can happen in weird custom typing scenarios), reduce to single type + if (types.length > 1 && types[0] === types[1]) { + types.splice(0, 1); + } + return types; } @@ -1288,14 +1290,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } if (this.isFusion()) { - if (!isNullOrUndefined(this.fusionMysteryEncounterPokemonData?.ability) && this.fusionMysteryEncounterPokemonData.ability !== -1) { - return allAbilities[this.fusionMysteryEncounterPokemonData.ability]; + if (!isNullOrUndefined(this.fusionCustomPokemonData?.ability) && this.fusionCustomPokemonData.ability !== -1) { + return allAbilities[this.fusionCustomPokemonData.ability]; } else { return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } } - if (!isNullOrUndefined(this.mysteryEncounterPokemonData.ability) && this.mysteryEncounterPokemonData.ability !== -1) { - return allAbilities[this.mysteryEncounterPokemonData.ability]; + if (!isNullOrUndefined(this.customPokemonData.ability) && this.customPokemonData.ability !== -1) { + return allAbilities[this.customPokemonData.ability]; } let abilityId = this.getSpeciesForm(ignoreOverride).getAbility(this.abilityIndex); if (abilityId === Abilities.NONE) { @@ -1318,8 +1320,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; } - if (!isNullOrUndefined(this.mysteryEncounterPokemonData.passive) && this.mysteryEncounterPokemonData.passive !== -1) { - return allAbilities[this.mysteryEncounterPokemonData.passive]; + if (!isNullOrUndefined(this.customPokemonData.passive) && this.customPokemonData.passive !== -1) { + return allAbilities[this.customPokemonData.passive]; } let starterSpeciesId = this.species.speciesId; @@ -2018,7 +2020,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = 0; this.fusionGender = 0; this.fusionLuck = 0; - this.fusionMysteryEncounterPokemonData = null; + this.fusionCustomPokemonData = null; this.generateName(); this.calculateStats(); @@ -2187,7 +2189,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.moveset.push(new PokemonMove(movePool[index][0], 0, 0)); } - this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); + // Trigger FormChange, except for enemy Pokemon during Mystery Encounters, to avoid crashes + if (this.isPlayer() || !this.scene.currentBattle?.isBattleMysteryEncounter() || !this.scene.currentBattle?.mysteryEncounter) { + this.scene.triggerPokemonFormChange(this, SpeciesFormChangeMoveLearnedTrigger); + } } trySelectMove(moveIndex: integer, ignorePp?: boolean): boolean { @@ -4292,12 +4297,33 @@ export class PlayerPokemon extends Pokemon { changeForm(formChange: SpeciesFormChange): Promise { return new Promise(resolve => { + const previousFormIndex = this.formIndex; this.formIndex = Math.max(this.species.forms.findIndex(f => f.formKey === formChange.formKey), 0); this.generateName(); const abilityCount = this.getSpeciesForm().getAbilityCount(); if (this.abilityIndex >= abilityCount) { // Shouldn't happen this.abilityIndex = abilityCount - 1; } + + // In cases where a form change updates the type of a Pokemon from its previous form (Arceus, Silvally, Castform, etc.), + // persist that type change in customPokemonData if necessary + const baseForm = this.species.forms[previousFormIndex]; + const baseFormTypes = [ baseForm.type1, baseForm.type2 ]; + if (this.customPokemonData.types.length > 0) { + if (this.getSpeciesForm().type1 !== baseFormTypes[0]) { + this.customPokemonData.types[0] = this.getSpeciesForm().type1; + } + + const type2 = this.getSpeciesForm().type2; + if (!isNullOrUndefined(type2) && type2 !== baseFormTypes[1]) { + if (this.customPokemonData.types.length > 1) { + this.customPokemonData.types[1] = type2; + } else { + this.customPokemonData.types.push(type2); + } + } + } + this.compatibleTms.splice(0, this.compatibleTms.length); this.generateCompatibleTms(); const updateAndResolve = () => { @@ -4334,7 +4360,7 @@ export class PlayerPokemon extends Pokemon { this.fusionVariant = pokemon.variant; this.fusionGender = pokemon.gender; this.fusionLuck = pokemon.luck; - this.fusionMysteryEncounterPokemonData = pokemon.mysteryEncounterPokemonData; + this.fusionCustomPokemonData = pokemon.customPokemonData; if ((pokemon.pauseEvolutions) || (this.pauseEvolutions)) { this.pauseEvolutions = true; } diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 674c5f47c88..8e7853a41bb 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1126,7 +1126,7 @@ class EvolutionItemModifierTypeGenerator extends ModifierTypeGenerator { } class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { - constructor(rare: boolean) { + constructor(isRareFormChangeItem: boolean) { super((party: Pokemon[], pregenArgs?: any[]) => { if (pregenArgs && (pregenArgs.length === 1) && (pregenArgs[0] in FormChangeItem)) { return new FormChangeItemModifierType(pregenArgs[0] as FormChangeItem); @@ -1167,7 +1167,7 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator { } return formChangeItemTriggers; }).flat()) - ].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === rare); + ].flat().flatMap(fc => fc.item).filter(i => (i && i < 100) === isRareFormChangeItem); // convert it into a set to remove duplicate values, which can appear when the same species with a potential form change is in the party. if (!formChangeItemPool.length) { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 2cd96496f45..b699c2483c9 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -2181,7 +2181,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { * @returns */ override apply(playerPokemon: PlayerPokemon): boolean { - playerPokemon.natureOverride = this.nature; + playerPokemon.customPokemonData.nature = this.nature; let speciesId = playerPokemon.species.speciesId; playerPokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 84f07f3ee94..b7071c4cc6f 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -35,6 +35,7 @@ import { getEncounterText } from "#app/data/mystery-encounters/utils/encounter-d import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; import { getGoldenBugNetSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Biome } from "#enums/biome"; +import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; export class EncounterPhase extends BattlePhase { private loaded: boolean; @@ -68,7 +69,7 @@ export class EncounterPhase extends BattlePhase { this.scene.executeWithSeedOffset(() => { const currentSessionEncounterType = battle.mysteryEncounterType; battle.mysteryEncounter = this.scene.getMysteryEncounter(currentSessionEncounterType); - }, battle.waveIndex << 4); + }, battle.waveIndex * 16); } const mysteryEncounter = battle.mysteryEncounter; if (mysteryEncounter) { @@ -251,6 +252,13 @@ export class EncounterPhase extends BattlePhase { this.scene.updateModifiers(true); }*/ + const { battleType, waveIndex } = this.scene.currentBattle; + if (this.scene.isMysteryEncounterValidForWave(battleType, waveIndex) && !this.scene.currentBattle.isBattleMysteryEncounter()) { + // Increment ME spawn chance if an ME could have spawned but did not + // Only do this AFTER session has been saved to avoid duplicating increments + this.scene.mysteryEncounterSaveData.encounterSpawnChance += WEIGHT_INCREMENT_ON_SPAWN_MISS; + } + for (const pokemon of this.scene.getParty()) { if (pokemon) { pokemon.resetBattleData(); diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index 0166b2d6abd..49e78fa5369 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -402,7 +402,7 @@ export class MysteryEncounterBattlePhase extends Phase { } } - const availablePartyMembers = scene.getParty().filter(p => !p.isFainted()); + const availablePartyMembers = scene.getParty().filter(p => p.isAllowedInBattle()); if (!availablePartyMembers[0].isOnField()) { scene.pushPhase(new SummonPhase(scene, 0)); diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 8240b6bcf84..cddc5798872 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -12,7 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; export default class PokemonData { public id: integer; @@ -33,7 +33,6 @@ export default class PokemonData { public stats: integer[]; public ivs: integer[]; public nature: Nature; - public natureOverride: Nature | -1; public moveset: (PokemonMove | null)[]; public status: Status | null; public friendship: integer; @@ -54,14 +53,20 @@ export default class PokemonData { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData; public boss: boolean; public bossSegments?: integer; public summonData: PokemonSummonData; + /** Data that can customize a Pokemon in non-standard ways from its Species */ - public mysteryEncounterPokemonData: MysteryEncounterPokemonData; + public customPokemonData: CustomPokemonData; + public fusionCustomPokemonData: CustomPokemonData; + + // Deprecated attributes, needed for now to allow SessionData migration (see PR#4619 comments) + public natureOverride: Nature | -1; + public mysteryEncounterPokemonData: CustomPokemonData | null; + public fusionMysteryEncounterPokemonData: CustomPokemonData | null; constructor(source: Pokemon | any, forHistory: boolean = false) { const sourcePokemon = source instanceof Pokemon ? source : null; @@ -107,9 +112,13 @@ export default class PokemonData { this.fusionVariant = source.fusionVariant; this.fusionGender = source.fusionGender; this.fusionLuck = source.fusionLuck !== undefined ? source.fusionLuck : (source.fusionShiny ? source.fusionVariant + 1 : 0); + this.fusionCustomPokemonData = new CustomPokemonData(source.fusionCustomPokemonData); this.usedTMs = source.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(source.customPokemonData); + + this.mysteryEncounterPokemonData = new CustomPokemonData(source.mysteryEncounterPokemonData); + this.fusionMysteryEncounterPokemonData = new CustomPokemonData(source.fusionMysteryEncounterPokemonData); if (!forHistory) { this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss); diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index f93e09b7a90..e96afb5cbd4 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -4,6 +4,9 @@ import { version } from "../../../package.json"; // --- v1.0.4 (and below) PATCHES --- // import * as v1_0_4 from "./versions/v1_0_4"; +// --- v1.1.0 PATCHES --- // +import * as v1_1_0 from "./versions/v1_1_0"; + const LATEST_VERSION = version.split(".").map(value => parseInt(value)); /** @@ -131,6 +134,10 @@ class SessionVersionConverter extends VersionConverter { this.callMigrators(data, v1_0_4.sessionMigrators); } } + if (curMinor <= 1) { + console.log("Applying v1.1.0 session data migration!"); + this.callMigrators(data, v1_1_0.sessionMigrators); + } } console.log(`Session data successfully migrated to v${version}!`); @@ -153,6 +160,10 @@ class SystemVersionConverter extends VersionConverter { this.callMigrators(data, v1_0_4.systemMigrators); } } + if (curMinor <= 1) { + console.log("Applying v1.1.0 system data migraton!"); + this.callMigrators(data, v1_1_0.systemMigrators); + } } console.log(`System data successfully migrated to v${version}!`); @@ -175,6 +186,10 @@ class SettingsVersionConverter extends VersionConverter { this.callMigrators(data, v1_0_4.settingsMigrators); } } + if (curMinor <= 1) { + console.log("Applying v1.1.0 settings data migraton!"); + this.callMigrators(data, v1_1_0.settingsMigrators); + } } console.log(`System data successfully migrated to v${version}!`); diff --git a/src/system/version_migration/versions/v1_1_0.ts b/src/system/version_migration/versions/v1_1_0.ts new file mode 100644 index 00000000000..aac554c4531 --- /dev/null +++ b/src/system/version_migration/versions/v1_1_0.ts @@ -0,0 +1,32 @@ +import { SessionSaveData } from "../../game-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; + +export const systemMigrators = [] as const; + +export const settingsMigrators = [] as const; + +export const sessionMigrators = [ + /** + * Converts old Pokemon natureOverride and mysteryEncounterData + * to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead. + * @param data {@linkcode SessionSaveData} + */ + function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) { + // Fix Pokemon nature overrides and custom data migration + data.party.forEach(pokemon => { + if (pokemon["mysteryEncounterPokemonData"]) { + pokemon.customPokemonData = new CustomPokemonData(pokemon["mysteryEncounterPokemonData"]); + pokemon["mysteryEncounterPokemonData"] = null; + } + if (pokemon["fusionMysteryEncounterPokemonData"]) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(pokemon["fusionMysteryEncounterPokemonData"]); + pokemon["fusionMysteryEncounterPokemonData"] = null; + } + pokemon.customPokemonData = pokemon.customPokemonData ?? new CustomPokemonData(); + if (pokemon["natureOverride"] && pokemon["natureOverride"] >= 0) { + pokemon.customPokemonData.nature = pokemon["natureOverride"]; + pokemon["natureOverride"] = -1; + } + }); + } +] as const; diff --git a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts index 0dc02e03c6d..e0f37c7e045 100644 --- a/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/bug-type-superfan-encounter.test.ts @@ -476,10 +476,11 @@ describe("Bug-Type Superfan - Mystery Encounter", () => { expect(scene.ui.getMode()).to.equal(Mode.MODIFIER_SELECT); const modifierSelectHandler = scene.ui.handlers.find(h => h instanceof ModifierSelectUiHandler) as ModifierSelectUiHandler; - expect(modifierSelectHandler.options.length).toEqual(3); + expect(modifierSelectHandler.options.length).toEqual(4); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MASTER_BALL"); - expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MAX_LURE"); - expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM"); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toBe("MEGA_BRACELET"); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toBe("DYNAMAX_BAND"); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toBe("FORM_CHANGE_ITEM"); }); it("should leave encounter without battle", async () => { diff --git a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts index b92a4d96adc..82ed558e6db 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -118,11 +118,11 @@ describe("Clowning Around - Mystery Encounter", () => { }); expect(config.pokemonConfigs?.[1]).toEqual({ species: getPokemonSpecies(Species.BLACEPHALON), - mysteryEncounterPokemonData: expect.anything(), + customPokemonData: expect.anything(), isBoss: true, moveSet: [ Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN ] }); - expect(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.types.length).toBe(2); + expect(config.pokemonConfigs?.[1].customPokemonData?.types.length).toBe(2); expect([ Abilities.STURDY, Abilities.PICKUP, @@ -139,8 +139,8 @@ describe("Clowning Around - Mystery Encounter", () => { Abilities.MAGICIAN, Abilities.SHEER_FORCE, Abilities.PRANKSTER - ]).toContain(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability); - expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].mysteryEncounterPokemonData?.ability); + ]).toContain(config.pokemonConfigs?.[1].customPokemonData?.ability); + expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs?.[1].customPokemonData?.ability); await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled()); await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled()); expect(onInitResult).toBe(true); @@ -219,7 +219,7 @@ describe("Clowning Around - Mystery Encounter", () => { await game.phaseInterceptor.to(NewBattlePhase, false); const leadPokemon = scene.getParty()[0]; - expect(leadPokemon.mysteryEncounterPokemonData?.ability).toBe(abilityToTrain); + expect(leadPokemon.customPokemonData?.ability).toBe(abilityToTrain); }); }); @@ -340,9 +340,9 @@ describe("Clowning Around - Mystery Encounter", () => { scene.getParty()[2].moveset = []; await runMysteryEncounterToEnd(game, 3); - const leadTypesAfter = scene.getParty()[0].mysteryEncounterPokemonData?.types; - const secondaryTypesAfter = scene.getParty()[1].mysteryEncounterPokemonData?.types; - const thirdTypesAfter = scene.getParty()[2].mysteryEncounterPokemonData?.types; + const leadTypesAfter = scene.getParty()[0].customPokemonData?.types; + const secondaryTypesAfter = scene.getParty()[1].customPokemonData?.types; + const thirdTypesAfter = scene.getParty()[2].customPokemonData?.types; expect(leadTypesAfter.length).toBe(2); expect(leadTypesAfter[0]).toBe(Type.WATER); diff --git a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts index f64124790b7..4d95ff31d36 100644 --- a/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/the-strong-stuff-encounter.test.ts @@ -21,7 +21,7 @@ import { BerryModifier, PokemonBaseStatTotalModifier } from "#app/modifier/modif import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { CommandPhase } from "#app/phases/command-phase"; import { MovePhase } from "#app/phases/move-phase"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; @@ -109,7 +109,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, - mysteryEncounterPokemonData: new MysteryEncounterPokemonData({ spriteScale: 1.25 }), + customPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), nature: Nature.BOLD, moveSet: [ Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER ], modifierConfigs: expect.any(Array), 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 f4d055c69aa..0d463655a52 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -122,7 +122,7 @@ describe("Weird Dream - Mystery Encounter", () => { for (let i = 0; i < pokemonAfter.length; i++) { const newPokemon = pokemonAfter[i]; expect(newPokemon.getSpeciesForm().speciesId).not.toBe(pokemonPrior[i].getSpeciesForm().speciesId); - expect(newPokemon.mysteryEncounterPokemonData?.types.length).toBe(2); + expect(newPokemon.customPokemonData?.types.length).toBe(2); } const plus90To110 = bstDiff.filter(bst => bst > 80); From 75a114a89f0e7fd1d8509e6573973f61cb1bc0f7 Mon Sep 17 00:00:00 2001 From: Lugiad <2070109+Adri1@users.noreply.github.com> Date: Mon, 21 Oct 2024 04:45:12 +0200 Subject: [PATCH 71/75] [Localization][UI/UX] emerald-pro font update (#4697) * Add files via upload * Delete public/fonts/pokemon-emerald-pro.ttf * Rename pokemon-emerald-pro2.ttf to pokemon-emerald-pro.ttf --- public/fonts/pokemon-emerald-pro.ttf | Bin 132104 -> 93528 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/fonts/pokemon-emerald-pro.ttf b/public/fonts/pokemon-emerald-pro.ttf index 509bd1aae868ee0779cd3443c72bcbd76e3f9d21..84e49ebbc40def70813370e460324d84e021cc69 100644 GIT binary patch literal 93528 zcmdSC3$$HTnddwA+UF!ZW5VGO-ky^~2ts(qknj)|4C><>h+Ow&`lyOC2eSrqs4(ReKp+uU0!84z8C{starfU2WH;Y1*!~NOFJwZ_c^a zTzl^m0%hMZdY-Jk)?Rzf@B4qx`OUf3-f=zW+(KT)oLjTzymx=~(o=rrdLO=+v-`g1 z+{2H$qW;Aq=X&?^{^HB7yJX{yPybo%+@kM0H}BxfZn=4?=PKUstqBpyKELwXk6-bf{r}Ti=azWa+y9xXF27{M(u2P9pP|1C zpC5e{C&urY_uqJbD(?@t>bje6{kO|D-^u%zoO{c2*WPg1CBOJD2i^$3&pS8&sLFcQ&72#r0FyUvk~$-}`@#-^2AzI?cHq|Idv#+;sE%FMsX#UGI$D`S_%bH(tJR z_LW08-dlJM+j!#*8}Ix#d+z|g+oADuCta}J_RCFYUG$dY|7*1~N})Y}_TqiDfAP3W z9@<)Ot^0LfZ+Fm(HTv}3R?n;7TJu^PrTCe>_J5-@{r~39bQ5->;(g`zFm~~}s&`87 z^KRUYRea#w))4m`uW&!qB@v5rkGSg7yfp|qzvkTY&T+My`unP?4TE!js@L24qpeW) zRm~++uGjl}_O#yncnNGi@~&!K)owa(pNHaHI@bB(+PXL{&o{>%I(6H(Dvn=|#+;vb zu{1n~u6K!V-h1Ac$9g|vGyT-@Gdmt}O)q^us(*9N(bSh+FQj9=*T#G88~U}YtD1A1 z%AeVLpU!Dtr_Hg~d$xwY&S?uopKrH(-Kf2;8+Cr@95jc{6@BHi+xmR2&*tLqJiZK1 z7q$j%bM?>X-z@*!e_MN_4VG+^ZC(4OJ`-)vM$~Jc-BO_h`JhurGmelH{(RKFZu>-E z#IHEl=xx#V#uHM#o)X>g-T#I3df4*iL`$mTWI(g&z=KMDL z4q?%?qA{wE&e!R)xvo(^$98JEmU+b>E%f7hC$e#^9oHOl*lc?5)8}y?3==f%dUnmV zo#U-ryXTAdkxoN5)Bm4kZ*n7p`Nx}-WpI3(>;0fk=DKe1e5YS%ZCIQ)Ur;~UNivmN zab3qwgV$6w{e1NAK6}$m4b9L!ZhXOuU#z?3zotX>=F5Y7KF<9zUwg0X^x5F`gYR`s z+M52JbHD4YPVue1i<|KX9M|bHANM^j&}ViXpV_gl2{xy96~}fiy*E0(z|47zzq~I$ z({UZI%{H-Xbw0%NL-cZ=)UVO0&a(+;YJK+F_BExtu_@k3z5BYcu`%|K!*iZH%H8R{ z*}JRvh2B?tv%P=S`+4s__Ws}Af9-9p_Ni7?7gS%Uo~r(@>KE0E{eFMP{!IU){X6@c z`VaKK+W(9G|2=lm*kxlM9s9>)|LfR48~c}Ie>C=;u|FUC!PsAo{fDvtJoewm_Zy!b z|C90WjQ`E}s|$`R(UKn_fJ9;PheB7f#LzcXK$?;20Uvl}-}YYZ{kGaOVVA80o56f9m>=x_Q!u-+OUG^; z+cfs2u}8+fG4|NllVf4*{}}tx*b8I79(#3sZ!kXr<{yv0yr8$>#06*Wcgp@A%#Tbh zoZ55hh^b=?^Tw&04fDU4`qs3Yo|v8j^9QFdp8oujYROv?=F^s3y!4T!KRB@d``*^A zTVLGzXUtmN)~{{-CtLr~*4wv!bn8u9Z`k_bt>;=Hzd|Or}G%|G4z^ybGm&u)Hf^EWpC!RALde{J(4&TanE<}dQP@wMk) z`^(p!d+q7h{_NF1es%WMKXmSwn|^uYFF(TS)caq2&x>chxaP&p4N*y_sz5)-hJ79!%of)e>BIL2krc0 zBR+jJo%`{K&zyV4{X6$-_iFRm-(_&X)Oy+ey@Pto+1I+FcT$hlz}`*0o8$GpkM?fq zeXMsY=RV%Mt@nxEr+W8s{ORh$)wR`ix~96mx}n-wp)ZbatZuUX&DBS%TdI#$_u0yt ztM0EpQ*F^FT(cFwN)YR`s4A_{=EMDzO4h|dmq-h{j&p!Xte(3)hpZ|IKKIIr4C=MW=FqIcG`3K?78 zr?*49*t74(+&GzJKC7QQx}Dt4ZWp(!Tj1W}7P>`lH&!=ycayAZE_Qpmz1-ezA2QH> zZh!YyH|3_?5;x-xa7*2RZkapCz0Do$4spxfq3$quxI4lfNq$=4-tJbqqunaE+8yJL zb;r5m-8_yS45NR#e~Z&T{Wz75KgG9Cxle&%Mu` z@80j$xeMF}+y~uy_aS$oyU1PaE^GU4(UJ_EwXDAM1a*f4?l*ztw%L z{}J-?UG5&Y$=&Tf>+W}-a}T=Ta}T(G?8p%A3+~JAe{sL>{xfml-3`6ldw2Bibhq~I zChrp$vu^pUn>b_k;Ir4wuD#`gSvPayp0o1~S@(_$?A#p}O#O7WH*wgWhtBqvPdzug z%OQu(R?E*gXWctzE|@-aw!i%9J*Q^tv)4_}))yQ)JGNY1r)Q>bTlb57essY;T(@rX zKL6>0eP*U-#}8RIyXKY)?1Kv~fZq7>UDto$(Ajy*zqO=yKisG8U%!5zSqGW<%fEGi zovNEtJ1pOA(bO@AA3D3^@~Jz-5ON2z?=v8DG=FyL>~+WQ6ZmBJ<$uJ~t3LVU z-Wg9V#G&m~7rzhWQ=86NH*+cIXHMRSU2o=OG+&>y?r~!Hl*>+jyf@WjH#>FN>|U4e zYe=CuxqOzh=!KU~lyw~Kf4yM1l9=O)G`4x6}e;tLbsop@#UgLXf2_l>)M zdH3(_{_DwICXb%HaPq#%CnjIoW8oer?{VuMU)$rydu&;J#NrDV-?I3@#ot)`=Zjz4 z^PoM?+4J^2zq#kX+iR!2mh5%*UK{uN{9aG)_3Ykbdr$3s*523e{e``s-23@`s(lXJ z=e&LH+2_f9UfOrzzN`1WcHgh;`^>(7v)?ZJE#L37{Wk9R`Tf4T-_Q1U`|rE|srz5E z|HJ$L!x&=(GR z=55v6>bKqVwy(S`yzPa9#}3~A;QHX}4}R$2rw;z#pB(BAJ>t;yhu(ka?4iFpY|q0EJM6r}Za(bc!@hsmuMQtO{Gh{6JN%Zz zA36Ll4uAECg-6s!Y&_!2M}#B(=E#Ldo^a$cCa? zs`FP}wd%fA53hP+Rao_tRli*I>T0)o-_-}NUbFhr)i7!N;#Xe*N*c9{-i&XOI8M@qhP@!`^YuJ8pZ&m*4TsJAQS- zo+rHhgf%DJe8K}KJbA*;P8>V&h!Zb9@s<<6bmG@e{Naf&*Splq>x*k&f1!S?eztz) zq_LBhoOJ3*x1IFxNk2I0g_CzW`LL7EIr+YmpFH{5lYeu{9;X~}%K4|riZ9{bL<@7(at+ur%jcRu&d*VY`gX8oFtYre4N z57s=h=D9V$dDq_WTK=xH-*wBozWJ{2zw77kdgZhQryYFS+S4|i_P}XRoc8n6etY`V z=_j9l_UYH2{^03Po&Kw}^VS}ubNi_Sv;BpRwB+2b^)*8P}ij^)tSE z#!t?8>CCY+r_Ma#%ynn_Gao(k`)9uL?x}aLdH2QdzVF>nz5566e&H;4){?VMJ!`{R zUq0*UvtE7APVZU%p0nO_<9i-@&v)PR{Moyoz4q*D&wlXi$IkxY+0UQ-x9^?z-hJPD z-h1zT@00I+>74z~S$odS=RADQ)91W=?k?vZcHA(ef8qJd&tG%?73bf3{_OcbKmVooFL?ja z??3t48ErwdkJu;GFSFL>&LpIz{GA6Wi@3qNrG2Oj^x z3m;tg!MA_#{14vq!Eb)>*$@8h`UUHcT7T;Lv)5m`{?_&PuK(itN7g^J{#WZ?`_R50 zI_yJhK6K89uKAGv(4!xE{6kNF=x;9E>B9XlJo&=2FTCQy`!4+2h2OpK=NG&&(--~hqF-M0n~V3p_~eVPx%mEzXD@#45_ieeB`069@sfux`N^fb zUApGdO_%=Q(qCV8;AIzHcIRc^xa_Bwy}V)X4M%S{Yr_>A9@y~shM#PBsNj6s-Il-+pCvcec08fUcK?^JFkBD>c_5r=IR%&e(jpQ zuUUP~dDm>X=ALUFzUJ|3p1$V!56}DX(I39>!w-J=$q)bP+TE```q~d%d;7ItyY?@x zn|Iw2*Ijkp7q9!lb-%s-`0KB~{%hBV>tDHH;SDF>aPbWf-tg27uWsCb7kouZ+iBo*KS^N z^JzEVdh_Eqzwpt0KYHdz@A>GHAN}nuN8ED7EnmLnFFsa%?C6hO@v$#`?5DTxc58j> z^|wB7>!Y_med`M!U-QMaFZ`?}k&z5UMHzi|8GxBuk!SMJ#Dj)U%4bH_P%Tzbd#cief$ z7w>rFj$ht+^qr^Ox#7-DcRqaQQ+Gapm%D4~uIul5@UB0&>-%>-d)MFko&3T6RKL-G z!9VIB_uuzF_pjc)!`%nmz54F;ci(^aBX@uM?&t1)<(~cTIpLmj?z!-ujrZJt&m;Fd zbI-3ox%(%N`sBJ#Zu;b(fAWP-zIN{c_nvm|#(VF-_o;hd`qWOJI^t8;e`@wqFW+~- zeQWQ#^S)>9`@2u?`RV%88$SKS{Y&n@^ZuXR|Lf1}@R@@@^MTLY`I*_zytL_nO;>Ds zc+)RGd&Fl?`0S;hz5TOa`RtRQ{fp23>a$xOIQW4xAGq*=&p+_+1K)k%nFoIRxdT79 z_H!41?)J}p^K;LC?zaz4J$TxK=RbJOgAYIW)Pp~K@YUZt==awA-Uen9wBC2BnwiKv zW)usUMYyHQ<}X{dY|-?h`HPpWnqJ)JsQ1x_9-4UQq2Bv{|M$Q7(3T%R^o!nij(YC7 ziNAbq%Ne~te(o>-_Amc(%a5vha}c>Wm#M3oRgn449l3JFf+K;7nh!^w*(PJki7G7^WIL2DW|nd zdaVujBIaZ>%(+Yxh8(=MU(|;Ky5T+O!M|d16=y_-h8RG$BY47_=Qb$#pC72x&+ccP|(H zUfsydQ#})07bR)!2IU&OY&{+3=4fD72@Za+t;*hnjpOxV$UpNx$-!Tr8MhOvcl4w= zQ5HcTOb{5Nr_Rxl{d_#BEDqTMx{<2r194#a(X%l_%DI5LA z*j%T-ljKV+f>tat4Tr4`E6VfhS|Jy=Y`0npsA@?>&qc_C4~ms8uD7@vTr}e`&Q-gz zx-iL#!hTVX>Bg6YQhp`tYLDPBu3;RxLy&*51u08jB+@PYRtNIgC<&CJEeEJase;es zjj+)yiC1Ef^piY;s5b3HNoOQ$0GT&=>n)@uAmb!kIOnid(|$gE}{4dK`xykZ1!Kc5Ugrqc^>BdJ$I% zFOC5Pkx<(%oytnu%wyQsAuBWQVK1Vg-9sCnEo9r-GE8HuK6GNyzasL)4OgOp5SLOeG<>zibL&sUb(B@vEyt9c z#|OHrbI}k_n0(J=OlTTS5zWq$F08;UJq+>5w6zy6=@$mZs$ii*y1RLL>BMj2DH*9K zN{c;PX!K9-i!XKCCv8I|AMktsK6j9UaBNCUsJK3i~&ik=Re z7kVLbr3G2LN8H6R-eM0p073dt6MGzN2^Hk{8}?Jnt@8C0gNp{@59~mpS%UO-C>WJq zl8qQ4lv~Gz0(~V#z+HW6So3O^+Y2W&rW~1a#S9t4QUypGl#p(&B?mXxE{a#$bup&P zxXXDs-Y4(WCY#G%y&`9#b(fEgsu484Z(eMR=(W@R6^tWWAkjTsQ4o{V+MRAGyHL ztJ1~>*crtmH`*|D5N;f!7ccL3QQw2I?A2P-@=J=HnMteCWy=)A5V96Zkg#%Za6HSo ze&l#2=m9n%cw<)jHy$aQ%e&3DD+e^VcWtaXE&4WE?)tVoTbnCmwW;eA5Atb?i_|wd zvc(39wWd_6ZIG||r({#swwcb5Zv~0~99tH`Kk+Ow3If)Z?3;1q`9PY3v^7AEIm4H5 z;`SC~BXF7@B+LxEbI1%<`A$i*pcRJ-BZ65%K_l0i)J>`gx^<>iK1GE|9ztSTZ986^ z*_FB>R93-GN@Hw+i>g4-)#5c=dym5_>mlz`zA@Nh(l)}$6+A3A66;I@YeHt5Vc&E)^>|HO)7j0{~D`OT|U$?YkZ}|d!`c}m| z@~TC6%yJs{iHbO8c#jS(E+dA4{1V4Nq-aM$>nd~BBwzVVwx#jQzL&?K?nyi~Qz?StrfW<#Arv}gF}vOencX(JQZrP% zb0%M7hiJ9ob_V&}^JZK!<|WAuysV9R)FVD(kU%hJnYBu)w!1iUOuM-;$bwPTba>Is|V z_+@=4L<`!hcjH`(C{6q{wR(wE9N_>18!y>=HEB-W=cJ*2*|IiXM+&^gW}ehKQW`Tm zSpK17W@crOn>RToTe+z~o2)@hTGn81HmYj`HSsyJ9O==tEuj|v9LG)a-r%Tdbxj^K zgVLA%%V1Itv+=U674ll0We2PmGay~*Y+DWrkiq(#%Bz~B2#S+ursks9qSAG42`Hb22*>f2O6=Z_;X}6aFHX0EagaVna&YN z^I}bqu2Dt2A5Y8Jii1WgdE@+*sw4E*S59ZIv-opJO7S6b^z3j?KyV@=;Z&1*(9d{U zOKWTeJ)<~dYePm?8c2i!#Gr^95d55@p$?w=aY%&>CYN$qx#ny0e&rgY40Xl4MFjge z&u`!7YhYu^Tgg(;d9_2G?lj~BP}_v3oe#yRAyY`MBwLbCqeMR;X=ree)tE?PWt=`J zk2F4&Z70tHXdDO9ARt}1fqE;)?lPWnvNe!a2hy;%tdDHt@?0lBnj(}chZ7}w=%@m0 z^0~Tl&6wM)OiMaGq5+rOuB$4=ORkej%&c9->Ni_U7StR-Xcx0rgA{M~#iYdGs8&$j4sgF1~qh`Nnz14#g8AF>9i6olXJ@*l7Ju^Yq)>c;f9y zt@*e56$)+R=rzr<@Kn_jBeg6X)H8Jel2klO(R|EjF+1w>d_-4A4OF4&N_tNB3o6HX zY~ln~7G}mxs`IXyT)wiMCE;GtIb{b~^g7jIXSwjcZyZnD3_A z%vS7`=ap^j&JM>&9_ZmK1&UNb#CV_sTrYK1YmHe|zcSXeRh?YLWo-1Txlg4jzOLsV z=Y&pkI=dwCcY!P7IKwAGDGAk5zfjrQp!95+5sdaRY`hgX<^wqfqh*G)C^*_|H1KNB z*BUSinPWWAtk3dYX@N2x^Y{SSkhYxu#jI(GU3w2sY>)7DC4C65*7BN+n{8zkFZ^R3 ziDSTcp#$YK4^2q|cOWRdXkbYkmDcz;$Q-(ifAJLT73(fx$iAHKey-qIR;yZ(7B11Z zl{dw+)POWIj7gllGLtxJ=R4RqEhJphHof6rLWfs1*rGk#m3d)1j%RBPpLCV2GpH|R zAz{_$i`lZZ@_X0?ab**0?%#R))1-?}0-vbyX@V8?sP}5=qj(71d1;;#l1*c6VO60} zN_*L|LI5#S5QK~vLwDTcmhxxC7r~M(W3xWx*?ccRG!mU!T?0|7beZo9c&Lfg_R-`+O(k32kJ{WLssgUN`mA5}|z>aOe)gtOc zL-OVOG+sU!;Z)F}fn*!~6`#VC`z!Vb`j3o*7C{-|1r<|s2Z#b8VQKuJBTJ^#q0#}9 z`!_MEnTT0PbDXRTN*hZI=ri5x$*S0P|E!DH-@JOT|0rD)2(kpSNJGWkt4qjo!mutz zP%5Z&g~3hDuD+6mb;l8}8b=qpU)0=jB%=nr$?+1*sW{fZz@NI?7-%_8_fv8-%?#XE z(&z{EQaVMdhluDJ6W(}n(o7bcc^36C1GNWdQ-fcdXUclZMv=VYAuiHij9U4lLIXY? zG8Pvs^GTh0FGaoqnlZm-n!!knBH7VPTV;}GCHEjwo$I0v0P}1k@S5r4F90RIm-NZG zgB*r)ZpLpOYHAtH2v$iq#M|rYTYS^pQQ1xCN07yaT5qZc*1q!muc_8b)Y6SfmG)*) zha{B_EfbCepKEwgMrViuvaLlDr>J&3RnM6@}XP4%vgQMQhy zOg}RjYGt+%G<;%D(2{g8a_zGn8=rX#Pk%Kuq$zDZqhjm8QC%8-y@y6@zIilbtD|MT z2~&QeY{dPjOdUXN))H0zY2iPzLdZ8jFM3L(7a%l>TodOdKAw1^@4y{1A#6P+Q#D*8 z2%FEOF~anjWw%wvVd`bni%Zsn>=L78BokxP=tLfn`D{aiOgo^}5lU&sg@ijk12{cG zC(?-d#DHEPE82d5x}D*CtDCCs`IMjDV(YICKa+B$v^&UH8^|RgEmAlAc zl%#^u7E`YyMp37|$1yrsx3`N^V=sSr3v{R>qrT))(cwiw%7x2 zKt*1ZNTnI7*IVFrd~&;JM^A5P2dSPmf^-@e261EExskWlEjR$$cnmQ?f;@K0AGZs? zIAA{ICFv$a%0mL68rNram{GKt&KTJ8LFRtyCyvVN4Ih(DJ=*JGF!a2}qV%k-Rx9JUk~rdz9F_SY zXQHO&@F>k7i4BdQkPf}qo7~`;?IPKVIB4&mmQso<4rEW=N!;KOL2tcYLMB@e&~3-G zq0zC80^qh;US`(pH?Ee#h1SLTIy9sAXPN=5qe)^HS`)K*ZQ@PTrbBowY;$c|wZ4<* z1w*Lp^0AHk%b3B73V>Kp7H@C= zxOSY)_3#FID#q=3KIW9R#@60B)7+i>J(<1(;LSL^MqJj~;Y<0n(H4W~G+7l^ll`PFfDyEbnLgo}1i!>Z# zZUm)FMPhV>u{hA!Wmpsk(W6G*J8mUIuv%HbB=j8fPDT$>H;;NaovB?F*Zhn&c}_m5 z>P}UnHj93d3Es*?=<(-TOp<9kFg5<&U^0DyU>Fn>X~>(`yDX0muFrwPa8=0ESu<>X zN7!O5G)E;Q0_aDtcp<)|v<4;)1R>#Ir})ydY1n@dN0cmzHMHs`qAi{};h~}?U(Hcl zHkiOMfGScmT*)8eJ!SKRc4nQ|SA)cfYBK4%nu_u@HIaNusM51b#H-d*@Bw)gIHaRm zy3(rE=5et))AeJ^&Z-y);zWxPk?co`8w0iHQTmm%k{-X z&}op)xf=UMA8zlZTOQ5SOXRY4iutlqi8O}==w!Be6I1y1MDmEAft?S7Llqx|W`$hYsz8c7(IMXZ;-u!<6Q+w0gaJC>(yNLx zjeFiJmy}nBBGRc6BSlOkpQw zee{yFBdsPZ>Nl3|5qisHW+i)WjXttVcg{=E0Oza}JjSD=0*PzJMzd48-&oqJ6G;hC zfDFOY>W6oN1|nKhC$+PwZP@MgVqelHpNd@USEth2b31>s6`klEM9I*2r%#gC`mBur zJujN*WN6?saWp=SsQYvuxvl-6Z2C_tw^^RZt*8M*Fjk&d6sP#j&mYHA;+Vz|Y?13! zJrS3*Hm#CaKzz&JGciLDlYlSM}1 zFR?>o2suj0xMN-#uDwU-zl+&ND-R$x183UGF*~>GN76~W-G%^UgOFxF^1M;= zU9Bq=hzHZThkmZn%~Q9j_2j9g?|5UNmjN-nREFow5yz=oUKF3DFQOodP*)f=fAy?_ zkhNwkFh6X@*SJDqGZTA$G*crwrHVjbUu*7EL7Z1_#0*>|fGIC9$FbiMh--7{DRVmO zpKg4rKVRku8Wz-IUth^aj3W^RzJ!g^9EtDl34tu2tl2J%*t7a=t&kr?j2-W5-XBv7oH#~rDUDNIo1%Oh(aZE z!Qas;@n&r-#)Hj>`skweECa;pcSGB9y2joS(q{Ss({S+U5aJDw7#9r>=^SzBV4ULB zgX37iOXHZQ#rxUbXGRiV^0SKQD^+vclZ(%dnIy14yrlAN=}A{d>Y)$<>R_S)6Q42k zhB(DY6)MR!-)8ag`*+Y8FL0}pID2ux4wq9D< znBcw9ifkfuG3_*s`?T&COmj|+Hq1I_?R*s9#4~+Q#;AEC8F=+}sJFCWuf-Q;Qr<8h zlFqEsk`=Q|CJXQ5)8omW(FT#2Xp&x~Oq@u1!=TBo@p}X2oAG{%km3~d_x4@c%G0Ci z<3i&LO*C2$q_Cgz$XBPhO1YM2;G$N@bM{;Xm%}6qZft7BI&oN6QJ-!;5^0qU-lAFg z&A!2H*X*b#lx4rZ(d^sGA~{ZUYk~oLc_Xc?Lf^`%g{mgv;)?uaGnaxvUBM?!pW?L% zJ2^pI&@#x)x%H^4G-}ifx{BonQA83i+Z6CkMGm(NtP zPg=W-V<4lKjl<^SPQ#uH(9!`K4N^Z%*gCPQXAqiLQCwoj4o2%1c-7|FPz@g3eJ;ju ziEa}gHj9Z-zDhQezePP6vPcL@30^11<*JT)GOjrMFlpXuUShBJ!Xz)V_1kI@dH@-Et;sYN7b%x^Q`i@J9qK&Yi+kbH17=oI#_ zRZYccX$h;t6@#Ib77Ba?z7bWvS1U9@WAu_8Qp78Q^_wa73j>Y*HEcr#LFt!#whBEnz;T`?U{8LEY5FMw|Q=6$g<7x7oX=VldM?J4pU;+}Y zgUUlO)2r$R7wfCrPp?F0^vZc>mb3ZBO&6t&(IC&h)uL6j6IT!%fUFbN@|tLY`%T}! zN__v;ri=7HPASOms{Xn&789Fi^L_5#KPVF_Cj8HPdU-oF{p!R@rH&h z?N2iBwu+~7o^@3mr&Q5sq@B!Tm24@hqT#Wi#tNKQ@7O>(s>T{ol?>yx?!G|EanlCj z=KSH3-VJB2e5f}Yyif?D;=Nw6oX>dCXHc+#)5+I#bz`-Eg zq+J;@=9^N#@qSK&SkyBIBN|M#EpV^1wC28jgFn}wwlZTe4z0XbG_GRPzOiB;66Ji& zJ~Bgs5MU94=QRtqJ-_o-#(XKs2(BtFeB_?F8nfcX+i~VQQqE;5n{7iq~Vl{Ice? z*OqOK>o?ZT){dk-yRMyGGpDh*>hdBkhPjQBxZzmH*>bE08a&hQ2fkVRW(^P9*JtYv z3L4@3&g?sYpd)u?H6V6tc2x}BTBxZ@^-XuHUJ%5q`iw*>N7_Mexu1@9)V-Q$AJy~a zcjVR2r_I=ERN=iXEX8`RhE(?M=GSOC^`_Wc=Q?t)cxNr*Xliqv?`^!0P4i>aFS`N zY(`BsD-=0v%{(YTGg@p2X&VQ0DRD_&3~hSWkuVLQiTPMuL(5xTT0e<=sSAX-tV#>b zkZ7#6+&zjHoy@;B{>+wg#no!VFsOTpBKO}edzGGB=3P`9?cb`!aVE9KUf!@ZPp`HJ zhv|aV)19pZn+US|9r%|qXaN_rskNH8dt?4JCtxJcxhPU3rw~mRhuPNnyy8}(d!Y&0 z%7Q~MHuGU3S-Q*fIK?tu=p3~byp-lq5-rIB`*!Vsd9X&Y=9>X=y`r<`XIO+sfMkaN z16D?a%Y~D^G&bH=A=tEv) zywX0;2gJuCLQ-zx6Xyb6jlr7AGIO!qRm`D_)dP(KR&f>DFS%HTz;GdCwTUYy#7t&! z&4XF8PKvoI4Ct-1)}QN-%x6%Nk#aP&v3vX5Tg~%(qkex&d~60Hf?(F{g99@-I^vAI zMmH;SaQ=;wk-s7v#~N9npp9v9Eu#$C)Pn7^zoL^8w|%2!6l(LK0x@I_D6cJEToh_F z8`E06LsVO|m`Yj@Qg0-oms+sEYJIW9X)^>pAvqUT;kA4|h8G{`PL)FXg&2`U$|y%r z(Xh;>*i9q2oI8jZkm5=c*(UJT`%KU3bp7IhJvV1rg_yUs26DC}9Ze6fk+@QqkxRNN z@eQGyD3Q@E7Z->btcXKL_%6q2OQUBywo5wubs6HxS{^OEat`81ziBMfvxJ%x!%g1V zXqf)`m5tgw5;9tHjzy`d+S?+8#vjoE!?-HZl1fugW*R|Vq+tHV){H|{v{THf>Dhyv z;n;&M&!P=ez<*LMjTyPkME>%KPDtiEV9QD>ucO8Q3r?Z7AP%Hu_| znOi^rA1VMk-bmR6KIMTC_`3A)Lpnk$r-XR-KGWK6`)7nUerNkfNW4~gdM0M6HL(*4 zoO=~nt5a!4x)H61!U(vW-Dd5F{&Edn>Y%k%n?-!~+fl%!_mP$TN%wGOCTBM z5Tc+@?7vQ2WW5i1N0ML^eq7{@=|0jfkk6s~#`k@ECT|YK zn8`aj!cykC^NoN5e9RBsN=MF;HM~wrqJ^t2Ey1n(}J$6nJ#MLnPZZ-jO{e$_URCwDJl2 z^Ev{8icdSVeSFdAL#t|bDVLA_Js(AA;!xq>%-bBl>jQngQ$`5-3zavzkTl;<_kCj? z$ew>_^nGbd6qT9mxfR=MPLjt}={53%w(jEwF-doFlV-)vd*e!p?Ch;xY9OTe3+9W$ zkbf7$?kj2LA`Hrg%?g;Ui?=JK4T2g`15~vky=5oQac!ax|4xQ|W*A}}5vm$Z=kHG1 z^K0V2O1xL<=}`SCIvCs1_a__PC>ch)hRha$J)C);011WX9b%XFvbx@D5fUSi`xoNF zCNA29M0-dC`$NhU{lSYc>6Z9OQu&6-&p8M;o)d{FdeWC6cM-nEkaxAe!;d2iVBS zC09P~H1Vy9Jit9fRS7He-5d%FoPsVD8&7BD@j>1$KVbH&>*ZV8+B+%sB#{sFm4J2&$0`OLL7(;K-y;!ua4!HMo= z^N~i!*^iC4ES9XOBdi*;cxS{21b&1Stry=28anLNE|7CX)lEc4oWF{NWD|p4vMj#V z3%VZpU2xuVRO|uQ{^ZO`^;8c!RBT#vRt2d(nMX};@t_CK53E$g%g{=Tyy$mkYR5e) z!&58EPuPDYv1mo$<4|D%yPcLWaKh;68A0(kRjyn$AF>cq^HF)JkZtgD5c?YbMberO zu~{Em{ZV)E>0WEd)AdoI?Zu9ZP1H!+em62y4l*Wx%d`1~OVz%x(G^42WKQU(y-K=? zbmqt3r4(-Z^Mx?b7=J!Tn?f$R_(at4LHspTl1MVArz&&wMadz7!crB>F&*Kz)jJ5g z@wgTzZr&SU(F~n?_Qo-5(vS(f&_OFUC~apyK?%8AO+SRfFMJhD1~)-E;er0>qkv{X zBNVeO z)}>F0UODS!7nIk?GB&2QSD0QZ&?Gu$qtY`AeI~M^jb5V9dHNyEgann1bd68;t9gna zf6!X0bdYq3U90F1tv{m0T5@rTgfiQwYt-8?^RWpIMSYI`(ml?2h4}X)2D=V&!4@B* z@79&B&%P;6yzBT;<3%P(AU#AsNZ0CWXv9&*?u}?ARykB$HG-k}n$Zcql~Y-w)jWM; z63HA59LisY+r9(s`HXYOn`r0XMV_4CsYd-zaC&mLuJd=~M9Uta7q&V2+3K);-vq9L zlRF8}W)u?h*eIUG+Mi6JK`eHzr3Y60fQh9WtTCQP+$okWLa_0hUY^cS(Au1)s!<*D zM$lng)a+NH{?)t@Lp58C?E_!q4O zF@^~UMwC7Ts3~RJ<|0z!120B-RrE)T57Egu&08R%zjkpG%SIj7^s_#LE5Te2_SRD> zhlcSuxG^2sF>|2iZ|9(~U(I;+9FZOw3 zL3;)e`{&x%@^YxHmiM*j%YA~Hxop9MQMLRgW%gCLMKsxG>Z-mZ;^WD6&@2%Gp~zidPXGF*djwjdCF)V#3ts za$7s1##$H{ueu#f$ubRtpQpkYqO1)@S z(tR2|%|X1})3);2b5&!T16a0ALt_CEGMa|8XEa*9ph@y6O<71mE+~3| zck91d-<|b#X`-n>B}9{ZctiKXu2>G=jo;3LynV-ntWB%*lH!o3Xs}xG{4M55(WftK zv=7>b(nfRoy1YTZc9ZXQ)xJhbD?v?E%_o<|bmVlI+x?9q$%|t%WdF!h5(dKt2v9Zz3n4%`S z=b)dmj)#GbA%@gmIMcl8`k=+P{*Pd*L$H?cPOzs-Y?unGY4&VId5G^Ft<_;Wc9=g# zWZjX&h~=ds#)H%j66lM>FJ69ch~_Shv}zhH_33E%oaWS3 z#GCH#i?6h$Y-9eLu)s-o!y#nqvpsgHyy_N>S;71+(PSRh+Q;#KdD!=-B#UUPq5?I> z(3ZUf5*UMA(8fZfu_*sOMo~Q(rGc5ghaLk3+K41IJfE%X>`^TqoAJ|(Brm zHU>0)=#Y$->cpnrux~4r^@a~(W@2>94Z5n~u%e`K-F&ynPmS(=7vIqw6RVE@H8C@h zDgYZt@swarXJ!pGHHo}2KY}Cuq_v?p_WxoeKWk)a^d%dO^i?wuV9*?XTCOt=dbjkzc${BwMF$DE%%_zk{^O8_a<3c$4~~xfBCbRiEE5r zft|y=hE()h%YDe0ueI?_YyNQ^ZVo+a^vPKlTPWHa;uc?$v(=B}WEE3BB5ZxtCzdId zE$JVyBmF%J3fR?LwU&xI>%?7unMvzQNn0kfcG^jnaZCv%n!90h2L4zM0+r33^ zV;A#lNjYa-fB?YEsTGe#Dn>@U{A`=R)mR5Bi@yl!1H zm$oIZje)$OglgU)6%NO%J-v|gmFQ_>^f!--@sDGPYV^He$L4H7=gM#0Tb#&F zgi9O;HMe1wq1c$V`KM8*n@T)T`>HnRRfMfLGds6+YtcXZSf1Y-pbml5AF3SL+J!Wy zSY?LAd?zl61~2d69&$heUNwmhF-8-Ac5&_$@weapmJ*=ODn>@!jE#w>Jc$h0@akN5yWr~E=q^*j1ZdF&ZSz2uxha)-J?4`MJxQ<~h0^SoBms zyVpDLJ0i`~2GgL)k=5r|{%8?a_=TOrxA>!(UPU>+)v1^|%k>zq({Z$XPV$OVd-qT*D_FF@ zk6QGxHSOpVIU8XCrwojag_VC5B=~D`Sk>eZlNEV?hwGoM?0DIvvaPKEDNCt}@Tg3t zsXYrvfiIb$4(Y4WK_{=~eAr0P*e%Jhvpof+^#sf6vX$A*)>dME$~g)Rke|wiAwZ#3 zf9%4sUTnhqhH{lg80m3axeC5s#;ChCDAxx&{_FuOqshT^a}<9GU<0A8?Tw)>e5Q#D zvZ%7Kyw-9;ii+&HEj5e2VUiVu<+r2c{vWZ{+S;4!qV=1&N+{Rxwc;Y>zMQY+ucfXg zkrG*{PwCrAmj&ZlzE)Q)uh(DGPI|3kd{{hMMo9Tq`fRN%AppjP?ebI8OI32WVHvOrZJcXLSjzuLhZUXv+tqk zmw0%FD#jeyJVzX%YGTzD9qAqaEBKBsSYeEOg;f=*jZCHzF>QE`!`BtHVN+-3@hRr? zcn7WUy2izUvc+Q=g(IQZ_1}h?W-eAcVHrjvix@6XqXINyuoy5%g;)jwtxIobt=ehC zQDvhUJm^?ek#4D{SQAI3kkUMW9S6BYkiIN|r?{}S0Mjv)x=#U;XV8%3!$8us26QAF z9RKF1$rmMb07*5ua4_5gEk5TUQaL`;nnbEl<18u9D9mQY-hoJPLJh9?K!J$F{K^T3 z6*VW~n^;avSen2Gi@Ca6MI|ubq5^L~XHWwm|D+($(It2f&7|*yOHZPgna)P~Y5Nsn z`dUCXj^I$1m)?a&K9y+bxiCh_;W{DK2pT;jtvWSNi{puo;&C1y8=6u3(9PLVd+ASp zo6n^YBd#%O)K$KS>JXd08zdwpBNTG&&Gq;IXt5G;1H!eY2LM%%f3B z7M8CK$3c#WF1F5i-&sFw;xx{MnkNvI-|UHN_C@AJ%QPUv$JhGGGis2%M5n4b>Wue& ztH-TglkIE+b1*7;L2L=GQ^8*HymKhlns49c_{uS-xGT>=O5s6{@x0pb#y&MnpAT!Z zpT<~t;U~oyTa5QuGFXB_%sj>!HhRb!`Kff^O9TwbXym!J^^H+ueI_fXFLAuiSYLFL z|KeM8t_@E))+b%#U5Kv`=cTqMMt!X=Wqif49v!B!p1$3&UR_0jS79xAw&Q%uF^xM- zFYz1KOOcbCky$=0`(-UQm3CbRC}d|2dMjI|!0X0sLoJQ-O|(xynXa92-k}kR!=g*D zk+sx?p5?Z&qQw+PcC)O81yF&+o7LLyV@1E$cvXCJ$|BV~GmcrJsSW>Z$1aIQlU#B9 z@_JjNu6a>ss9Q0$9kdk#1?^yjcp0EAvfX%1cQ>THrK(<`lVWv|DGC~1O|)}QJ%oYRfI=anX z%dC*KuYEq>jL-p#w&Vq}Y{}6$I5cH$EQTqK25PD17N|9DM`d1=Ay;)`od1sZ$E zJucC-v1c(?8@@~*nblDntSC(@_AcS6U=E&geQ`3{ZN&PbtjF35e`$z~ahzzh>x+gq zQDMBSRJWoU@|u(n6%C#uqx6$=K_!{Nk=GVW55iV48`Dj!lM`@6|G{;K7(dfcN}7;1 zY+aFkyW$-E3J`UoE!)}K8mt_*EBLx5>O1+IVO5=Eb>XN=Nj4K-WTdM$jwH1&>#oL` zp<9f7sRENFEw^FGJc{NDtQbwBX5avyVo0r=hbj7D3=wVYj6XpKYWi!}6VrI$xwztb zm&V}6o}Fl(w3^;{1_^`>aVjzAuvYg$8t}6JR~cz?MrdhBBlv@ZU^Xg;cmgfqXfiYR zprC+Xs0zccG*5|oK+@U=UQ~JF;k<}iw>FFRiq&?UkwVL4vu7)_Mu8Jkge3^*<^iL$ zea;a^Ek~Q~k;xZ4{LQICjc_5@$S)CjhggXY_83@;F*@{@&4=lT5(JFd`sogK%CaG#zenbzKEM0Wf&QtCCfQc;z|9 z8{p+&7K5OUMI@|TWR-j;@{<3J_MK)l4st#e3+CFIe5Z1>gm-Qq?$Gc)QILAnJ<_Nv z$;@azVOrJf6z{|TrWMx=N}$F&;B1fh!aOhurMvp4n6v(}Cck^4+I6^puxvy$Hr=@riX1m|0r|#^LMh{8; z1?cAMYkX4ITU6vZysk%5FV)mFv`c@`h|_#$ZCw2(iEUovnu|pjXJXAh!FjPnUXf}W z4uJ&%;`yP~^oF0si}=~7thx9wznEc0@<}|Rr`sD8VDX3sWvi-2m!coD`dckm!lnIw zn&MJh&dWfyKddIxqsV|5*r+Bc>P5#oFe@d0cpV`OUN+EP^??TZi);u>&^0INT z%cncyMh*%Wh>BOFA9SW2Xo)sj%KJt$dOzb8D6=1t`)&0NWT`5LCYV77HLo|8wSA1m zMAQ7Iq-i=XXxhE#L7GZcNzpzi(oOaTqpaCk(~x%iiZ?@Rx^=auA0mSax!FmjR`H=w zyJaMOil$*4qG?v!22H4ozBCC7RY+!|IPUu@t^MkYQF-h)i_?RDT<(-vG|I&V>C_J_ zHjmUuOFwCgS7_&cV24>uc%6Kc?n2T_e^?u_BLkMb#VzL>bgO*B^=1pMhF84av<~f% zeXXv__*5N(j`hFe#I+1*I>!sj4WKN-v2zy#T@S_L5Ixb|20es84Kz3G4{@D^kxRZH zzs>QC8t7qhEG;Pg(2z06{>^xiGlZ?8$o6y4P|ub^iKES8DVIY7T@B&E7a@=_>7d3N z252T+1G~zr?Qff`#LK0Rn)er>T8mqlt zUe#VtH{Bqzd7kB%G=H|)a~*FQ&4U&)r&>7P(?4X?YQ1UByjIaLfoEk~8;hitxQpYI zM=fEvD3RE{SzD296|)*!sKU2(mtZu%mX}i*4pW+}68WH4V$fIOLoKT6gYUDO520fu z2{i^(JOHcK{lB^*mDjav{Lk$9`h7JKtTkIH`a%%C_+P^pABuj&Ww5U0zIg=M>@&|g z`)%rnRV|b0o@Ud}1sg)@?CZu0zZ>KgrL=xg%9djkH0db&?I6$L{)sar(;A95-f@0o z{FN>H!VL0qb|aDLW}AVL2$H{M6{pq&H0gdU+RZQx&`qnPuR~W%;vESzTxYYm6bD8( z#(~z4nz?QjnyhM{%2pazHCxW3iNF)R5UpGq_(SEQYHLRu*9&>XVf*~scFG0=_ZeGy z5MxLbMLDCqBHnZ4K+a^CtGcRnH!hC%dU`BB0jaen00~S8BtRXCSm4hJ25MUzEBaI% zALDO^*!)}Lf-;qA#WbV0F|+->5F<4BJ0U_w!+LS22ohH8X0pe))|#nt(X8eT|8`}f zm3*K>>ka+RgUOFQ0``#n&965NXPe($mW%3uU(|G_SzKb+P9u)^%Z`U_d%r9Li8x=OC5Nf%J+%P&!b=E+1~&^(Jy%q z@C$y3UGgij$=q7sk{#1}l(a>ju}p3=b;ej})Z?^@09A~{M9eYmgItv_eO0ze^j6U~ zugc6x4{&~PAyQ_}ao^(O{`6c3WsediP)Fkw(w26M720#59YK2(W>HS#|A5a#Z%Jd7 z^f_R$oUOpjf??cRJ7t=}+WN&Cph+f+HGWHz0;S0-agG2gThqkiIYZkjBu1Buf{NOX z!?>22`dcp9&i+o$qgq0G%5f~)N~Y}TOPcCSV^G`m(owOEUg?^-Q-{|%jcEdh`(0rK5SNT#{5%r zovOrbm4M|JXc^{_G`(mtSYvgd>V|gZL~_VCqb}w|DzERL6)gi5AQ%hO}Wi^@2$0_Wn8cA)yM2}z%HoxS+>$?Lk+PNy$2UszY{_+Z|eT!99 zlMey+LrRv2%Uq*K@8(&}Awd(hB&LD61``LN@cQX~m*UHEP0oMjwX&2+B5C-eb5gR7 zKy7tjeA)p83wP~mj+Ark*Jc#$To_~?ec-4K`4tZxg4%wMhL3qy+4*4S=_{WGcN%fx zCF-%&yL^q^--@-MuH>2^Ce|{ac&+gXd;oRvk2K(N+>G?fCD#~(9B}* zkS%aU%%^TKpGi7xUIcu>J>}o(N9a^Bv6EZic5{2Uy`esiV8^j8uyST1b(h0Q;Oa-4 zZ!$$m5Gyfyhu;u_s%z2rmJ&mduAqa@fCVi~NPNtrl9*u9Y2bnee`r1|*nb+|Ph5l3fnF;e#Js<^xOq7&(K9{g$j%>2KXG$61L zQSjqiJpMEX7W_$2z%kAyJQ<4y$e@_7gX9;f8h$xvOS)v+p3RHoSo7Idtyno@=`fiW zg;?z>GY4;SM4jl_Iw{YhJnK%VP2R*O*~R8H_?&L@tPmG_B7d?EFE4PuHrQA!xs#MA z$dIdvJs%z6RF z0YuRN(UR(){q-L6GkWv8b-GzMBPx2P#bbT?)xCSRd{29$VEr2V+H)(bu-7UgVuk(k z99j()wRGl>j7}ygvF7+|diCk6axnFTjTl?sU|dbshNt|NbsbeGOVQ6_Hv6qUY%CSW z=H-mMh&?)QeQmrL^N`hS2c_s~nO?wrA&62U$O*5vs`W8fE&V~WgURYsWfg6j-6;0u z$3`tB0B>~SSV4XX-bb4^G3iFci?@htWytpC`6JCG17I>&8VC(pCx+abg@fz?% zUk}OGxki#;N*n9=+)|0tI8-;|5TAl2_2qm3iscwZdhToA$b)=g^Q<78Oj&wB=N@Vn0}$Yt7TA;v$Rw{r zPjybepr_t-3Mt58Qh(x!c_5^X?7apl40zUE7q zRo;~8gd|HOR!ccbNF~8mwpLU7kX4gc>X!r+5m`oM8KYt^UawGVB{Y4texLI@t5X`d z<7g#ILf-TrA&Cy!*MBIjLQ{&jh8aiYAz`wZS(QdAy6B%}O5oJ|Q`*&7+xE}$vF6XX zIT`&=Sd`5SM?mKU5dK)J1xVmc&75?=pU{$QH4PeNG3Tt|X(2?1^_;P0E@ni*Z2zxM6=R@#|H>_0F?mQu*@Xr) z1Wrp6{L4WmQ`?PA5rq{6erkdDNFy6JiHeU@!)*15m$aPc()P^7I3v62gQci^u@Vn^ z-E~!P|HK_wg^s_VN?I_b}RuoKBaEk-Ptw8+bt(7leO2CTkA6fQzC7!v*}H#nQ=tOVfRlI8RlU*2XV9l2;Kem|F#z-@?W4q?Zv`_(kZ_ zvaJAgiKYWgx1ZvwQ=O+At{#TG9mgy6e7);6f9m>!{m<1Yi$A#E}v z$36{9%qBdlPP7v5H%rUgB&KV-b{%FChp>T`(RyGV#)fK&9K(|O8q^nvEoa|erLw03Sy&SWOmPRz{1UU;`i6-~5;<}jL7 zyusMjN?=?OZ+$80KhBDhd>XOk1WPqwNY22&V1<@sMqU%sWCYhg+=eBvXhYZd1@Sn; zT8D-&o+k+C9yFFVWyK~YY+Q_EidAUi168rvOz6#jbc29^_C=8$`AU{VWofF0rHT1i zyE!Y1Bu0cA}PFf>yW-I2(%IVT@bU7P9jD(xV>MVq+>?dhP*B4q)YI#+A&G6M9tf_&6 zvY~2EY(^V-mT1^2m!7fWcgZ!wsT?f9Z09APTf`{K{{qQUu}mS%_L;D-elLxwdbcO5 z47qtyWGTLm*3zRYjr34MoKG3m$P+0&>*w&CRHjO+9kD;Ptk%})zgUi5jRcWI3ednuo) zPP0zDSXf8@&NfwUXs)x_H`fPs@Oq)CQ|bzGwNOPFq+OM=aKX|@VAtc%45(U;F=(C+ z(ikmNt?a4RwowTA`_N!KTY*TKaplmQholvOngzRI{*rL7UUcnzCm?{<5Fi76@CR~w zmRG+7+WN3B#*fz-lZqLUt&P?hsUTt?gGx|2k%X;-p8c+V0Clu?lRZt(X7%aGapFQ+ zAQD803W-$0zS0ZL^UsIqe~40BjK$yYublEAua)cFW#@@?CNDOVhvW)H`CWfn*C$pU zh|q|bJlD2}v;v^fkz1~;;>{=zYg;CVp!aWx^b1lEd`LD$%&zD9o6tCMfx%S;l}lT%zd zX?Lc;Y2%|&Mq>uZ`Y2xI#aesk+WC|EUst^6LN@v^RjPY;c|WpGEGiaHYF$9rkLW|Z zG}tr}*!+^J%Q7)5-bh17Z@oiZIkOU!#E-r%B)MW=u0}db{Hs>R|7C^V#?}E>a}joe z+uyo1$(J61>sina(B&VJh7e4zjZLH-^TtCMmbB&ov6@CbKx^%Q)R;r@t5roZ zNK{Rx=`5uF-@>dC#|B(6zK6Q6vy84wM=! z+2SdY2B_$78?Tm9RJ9u+d-0>uHtU8}4dqVO7=>4(d#0Q4vWwuhjhFne4gCtQ__cWD zUBl`{^sHZLV!QBWx-H(sWf(Bz-8{WC_KWo$ULh)Z9u$2+)y~99-9`1-Of=y@rRF%o zyS#a&zMG;kgEx@HXqp#Q(zVTClp!^O>rW+x^dMqfA1PFf7EGpH2$hTeI@Xz6Ez&cU zMy1J%AWc;mbV2}?umnKX{A-G@){j&p#Aiu6IQ0KqVUZA_AV-Nr#G1K`$s511- zSfd{**0q_hCnW|EjDKE7NtGN}i~H@CLzE>2RaHR4A`yH&LJE)BZA~yemmfW{`L@Nj zWe^w3TOAq1$wQZa(H%Ij7WdmNgD7j*NnQd85t!Q)Id%tZF*Dk0N=rl@(St(fGp7tP zyXumZC*|Mv?Y;DEDvPv!8r0%Y4XCe2B3=Gpuw9$jD*LwW5;YfX zBd>;PP@qB^9R_V)hrIA_l#%$<>OJKX(`UO<9P@$2a+_63j^Yq%<0<-Z=-AA7ioVH7 zzHM?=`@0v@b7h?&KFHJv2B$DmCiGcJsV0l9&Xu+zE25(mDfBPw;sPXd&TjlFM#sop z)OwX-qtttlDnQ7;q;S1~KJE4J$v<-x_>MB^|0eNClO;asZW{ng{ZWaQ`qLCHq3bG- zu__>%OLNyke{bD(Ob*B69MC*jmzL~mzs6D`TcqrPoN;y>ErOh81S)`H$njW#PlDNE zFdyb@__l4K&i6dChnBjvA_n!sm>2#Hc>o{P$|;?Aqb?XVf@cmMNrFK0BBC9~NUWs$ zf%szN8$F_T=t8uKtHG!eFR2}$XlOHcidft4ihxg#52DmTPhKWW zmM6J4YV}(DB^|FYp-4AGqExG?5Ynsyy@8|g0rSlk11lofKf?&*C0jMu7z?v=$2Qp_*vonsMzk)%`6h)w%M^tnrKwA-$#M0;O?4NLupCs>PZ; zv#B4uh=+Z$!QQ&$kOU3KHnvn-$BHQ-=brzh;wpc0$0Al8qrEkyGF|JGEGK=NZkSso zTV;J`8Hg_FL{Zw6B5O%|Z7GhkM+6bJKe6c8LMM{Di0yXC(}#nkN$XKj9)uvZ8M#Ir zZ$|kC$|G4M6I8qMm>a^v(k%FerfRgebV#j%!<%hxSS>^~x=M-VnOfSJDs|m@mHQBDo$2qU2xyanvwTv$4 z4%!al{G$!al%6T_)_qY!i|B5W)7$qs_4PhMqhe}PqQ1BggK9!S$BzrX1w{vG?$m=Pxhetn} zg)54TDQdA>h|O)pOsnTXk=GCMc>B7voaVsItsv1V{5H*_l_N_`c~oj~fvf{dNHeWM z#`JB;?Vwe_GFoX3!~9n=rqN*#nY{;Xp2H=Cpi53%!W3aPL6uJo$Q{>q?7NJETuT3K zM~AG9Ad2<}v#uOs#wTj9djm2lXUn@aV`ybc(ck(5q$LsmAphoNYDb8!Op)L3N~Wkq zNma@OcGJ+1P@G1l?pV>*I?Ud-ZKV#M5E2)0n?oKyWS=zW9^hjO$%Aul)0W&qyOwIRr2RJr0oHSVaqONOnnPd!TGkCF^OHlhvNK!6uVr+2!4fSs1 zw&Rm8vQ&T|@AcA(ZR>@+SHp~%SEYQMbC$*=W@&lcc;mcA1eIB;QE#NH!12IR$!C@@#|8Tmr>B*tHLp|J>FoVmaF;%xRk($Dw1FVfuwt=B7U^ zg3o!ji(j5>&*9E3MH@Xu+-7;OcoViZ+z&Zi^oBTYb!D52cOl_+^Bxvhd}+?cFKVUC zJC4bD{XkI^*Xt(B$nrT)Bssn09N~h>y`tUJH0e&uKyDE2Myk1J4~Ssvs7;L*bI(xD zX=o*Y6*nxjk*%!WO}MxdYlx7PJL0@zH2&xmbSnnI$Qs4EZOnD77ZhHR>(F4i#yZ!T zoedV^soJDcuG~>iKj_is$S>8fk!i~^-klj0S81i8w9SNU#n#DqxEngCDW6SpuXN{& zBoyn!V3_h+af{3B(xnTjL^H!gSNfJvuumNA4dU%&8u?QUn9+dW0NzN~P(_+^0ZufF zafNRX6qc11?6>Ha+RUWMzUZMebD`VpE3IC+|I}E!&}z(3hS6vZL-e^;3I)D6!p_Wv za*nJ3-nvakqx35v%{W`hPqR4DyPyG<=2yvI{Klp!cD?GZ?{*?(yOnS?cfY z-+)i2HPD=E+WU_=${M}nvHYp|x{Ywo%8#@Bb<%fR$EDe3lsK=C^;gE)4SdkE*oP2< zz2;&>8i7Hxv0p;Rd{~=q^ZX~}T2a&;*Zd0vnX>sCI72YwiH%UhAo!X)u&6<2>LJ>( za%nP~=$Tiiy{C`1gjoej2eamV*oQy-Am&%RSa~v2DbY%yx+|V=a-ib^WzGv-y(-d> zc@SG#18DIf`v5#!R7e<0eL^wUWF@|wVUadsgreuL3$k+P#L>1rj~s!xTyv3re2d*8 zJ9tKsu7ZpQyc9eS{gRXeW-!q^2z<2u%9^k>)UIkx6HpDr7qHNiC+6SO*u>5Upzu{r{2 z>#D|^Vzk>+Zqd)nouy7??e9Z9&RjxkwM zf5{`BafN1}zZcuKyYgPb-|&~L>aUydLID^w)ltk~)32aSYRvOo z%2qk`iQm_5uSLq3sgO|IxL&-tKK9a1&v?-@)E+UTUpbE-nEwzX!RrHPy1;oa?@{$4 zItUm7FX?lKc4TY&jR8n=T0S7j?Y#U>DA)|MuHb6BNd1(*nk&*(V6&#nB8Hk>!iKH> zztXM)PL8VnzjC)nZj(R=0aVHckRmC!?Fx0T%t2_0gwRC7-reSI$=&U-yGIfbSfm#b zMT*#^+dG1yBZ4B>Q4~?Cph&ZzSU~>YUzy!aa-jc@bGP%}n|bfo%bPc|vpF2+YbTd; z0z9 zOD@OqW*^|WXpWx|6P>j{)L>2f5FbsxBKl(qjv{1zY-HBiD<1d`O%J!=XhGd71=Rw z$N-2OPV7x1H^tj#{zN`Bi1fpf;SwX^>6AkdK`4hr*MJx=Z_%X@@}VdxQ$w~3v$6fo zZ{m$9}jnM~QF`utBB=SpKMc*2A2k08a zMO34Aofh>Z;+MW{L~)1YOWzk^N-!LQfMydO*s6#J8c7j^uu&b@pbiL2htp_$SZ+;t z=_zI%O&OTJ7@G^}JVv4lqJa{&gCliXMOS?9YE$V&cGN7%5TrD?V4%8tEna9eXD5_( zNF=rzF%z;Rb0JEi4T^ycgpR+V%yP#ll7b+SX$QJeznc0ZYcM3wmv|!M$w&AM;e_I8 zD0d-UQI2z@HO@se-$It)U)SbNA^OS$kzj7tpEN?(Ut%@jWTWnEUl9$+6(m*i1mt7z zoYPxrpaF4>7zV$QKl7C?(VHfU@QLgf2*8303A|$qF;4O*StqTOsElgty68diM&LK1 z1?63WUyn6)Ot3C6!XyG4fJ7U~aT7XqJ>^&QyW*jDO%ki=*9E#{&I5MfB1kj=m*QNU)p#A09tkzXk)=~shygv2>XDFh6R zb@lP=S9FGOvq_SgaGFNoyH7RPrTP_JEp6migk~eZ+PFW>w442Eioq|i61#9@{twry8Y8v1eF?=I?*^EnjotHAf8MRUDzn<(MEf;u+v$Zsju%S z4#`D)f0(i#!J?1Hhb+#DUVuq(K^HZK%IIL|A%M%Kd6Iub14lwxvck#% zER1x4PnK{46AcQ5E53^a#JEDC)n{5{BKv5D$1`tGKsts-NNNOUVbgClu_!P$Cw!o+ z5ws|#jh-U$*w;v!A=xxW z)SO{u#0<8&|IvH3_{q;^&6D=&Hz)AY6XucLnE=_~52Bz6706=#a=g{R8!BPQ@Fiw8 z_**Faku`}`iRMXqA~lj6!)HkW5N*K4PG2KlLmUWELuF$190Vh5y{_2`!0Ly?yz$%okhTpSgv1Jf?+OX^q(gFg-Q$|qR)R!nnY$xw>AzDa& z9QPMQIWx)}usoa-8PH4!uPqA!7ZhvBGUWp`5eKo~I7k#BA7g9MC0(8KwRr=#tbkBE zy1yiRZj7D1MhvZ{95>+t0oo$1bBhlU3DG*Ci|m>Bmp92Qmb@)80Dk3IvKUc8G?7iv z5z1OjfNHKrctSx|LxbAb@kTYS6aHp=TP565d^aek=N+q16hgRaFZvPaK8WbU#@M{QxuZCa(^n}R(ZI1R#M~*kpf)m0i zos4QYX|O?Gn0<#XvKOKWyw3xEu?(Sw>hnPhtc2zRdWBzO3;-%+v?pCb0>}WI#}z(D zhxt*gZ-S-j#t^z9%jKGfn@0YP)JBPBa1~zZ^j3qM^%? z#o}Es_`^I$#_~||J@~JV*fMPYI0+{4kk@wLIg_Q!(HT+?7 zMiEftgsJLAal=Gw2szLw*K`ykHQR_kM=?mC!41P)GYADUGo6-Rpa)JNG0^3k=p?9^ zCWRCc2_oo3<@HHb95v7??|I(I>;mO9H1Rr!SUn*eb2D80NbroJ5q#bNpWKU}xrX3l z?U6oNqcihHn#2YH&A=gHeND{#ye8{hvVZh!(%uMtd^HT;Al<_TEMyvaI@v&E2jn?5 zmT3-_fPY9gw~J;fEZFvNVEvz8dnK~Yl4HQ zNPgNXZ>^>)C-RdpK6?|y5I{JAhda=VR zAz%=1P#oqi$Ru&ashCv-bM-{Kp}0M=7@K_=Zvek1@kCp(CQG-y6}~ptG!a= zYfJ6Z{BA?cmTApS=I-Qj^od+<4@4lgK*bEA>DwGc`#XwydRO7 zoY8ipwciLUl>jE-2q41+&Y73XEuM+2@r zT|}_dpIP%DleU_c{1}MgLk64pW{qV-^lcQofsgcsEhJvZ)$((4*pL)=v$(>#_S zQt3HjG`y7J0O;gc7uJE*>NN_(+~OS>CF7{k{72T$H-VLdlt@X~)wjCeH_^r`cJ#hP z;*qGMvTj!Z0K8zg23U1CT%qe#^7WZu5u6A{1ec~>0230x&4Cdpd_uZHg+8%%MZbBC z=BtFoxTbS$m{%anwxLH@%aMZutajvF$T=sn3F42}5N6VAvwmwiVi3Wh9-C^qF}qp^ z9b$Jvr`jE9G_nyYb0jjb8e%99B+F~?2fNj~^g5Vk*Bf6cz#_Ja}_s6q)Vv!ub`CcYbis-~cYq%^Wi*N=MHjm}sGl<553eLz2 z^?4%v0pb-m3N4=D^q)TAyR~Y}-=nbIp>r ztF%7f?6t)kpc~slv)Fy`k-9azo8z_a@M|$wY2D-aXSCj8I?W%n-fCj@0IkQ&ZuStZ zx0$2uIa=SsEOKpHpJjG%hiSdt-s?7KJ#H>;-COIk&6%yQXnl@Z5xYa{bIl2D-CCb- zj%>RdYZoOmWj2_yDVk9;W~wG(hSAz!DyZkpxLL>hvMHd)B+jNVV@}dDBK@VQ4dvqK zST!+R-cYIL$Jgb@%Z1`(BGr+^=@o#y);w({P|~;nwE`d$%n`I#pk6|^(%Q==N|T9T zqEOC{k0e%EUXkSV`m))pCBMFp)2>O_bIo8Yhd+8VWXa8vrDi9D{S0Fa9YJVGR%DdVL!AFFh>De0j-Gl1h7?bgp8Grl~KOF z5iLTG23-MDU1jXz0Mmd$?pDB=NnCm+uy9C@Re(;ZMMnu+zdGbNcrAkB1n8K?xGAQh z3ZqMyBVc03pzOs_a^7*&#Wg#br^C4~@iPp*$Zqne2{mF_BP@{-EMX=|NK5~CzUA~eSnl6r46YGlAu_lXaA1G9cqmzYlVx&+RE*Gb&#nR+H ziT$PyIBKkrAfrl5R|+Gjl@pslUL>%Be5G0_BW6sOOH+mXA9?O^W!vF){7#S&y?3*a8pTc+` z-W0-qOQ#M1oBtmlnc0KH;wq3_1<*>cm8GT|p)`ek5+9N%1l*QK zW)Ey~NJzA@v@%v&x3oK*>P~hqohq$ejmWdQAYpd(6ouLUog?y!jrmD~k1mrn8RqA8 zL&AULqEuEU(%oIzO!v}FVhH?%yeH4Td@5?>ITZUO2$Plf;v7Y=BFB!%=M#vv;uAEc zm%VH)#+&|fAzv+?T1fPlCLpa!qPJQt7l)?FLK7=tF@;3$cyTRyMxv8&No+O~A|W^; zV+p(BDAX(NT!5QXU?IT`!POU|mPb9!*<`7x7x8sisX>G$VwRN3qf24fg~>{xvUF&} z;!1vLdPxWR4}w48e+acv@N^2|1^N40v?uwJJrfbWBt?=kn9ADQo)ADACvp0vi?Z+>ikVm=FX{n5M! zbKRerpF?5KnCHwzP*)MkoPf$E%}1fSQ#ce*TBt(x>oA*KZ%%_EPd8_ncbKo5kD0T~ znWkpWHqV;p&F!|;#%!D2!p^eo<`wgA8@IFV96Q&%ikbPAb}Kv2TD!HKZ?~}v?6!70 zyS;sbeWQJo-NEi?{%-zZcd|R%gk5NNvAf#c?3?ZGb`QI!-OIkk?rj&@#pa*p7j}tV zYCCMwrfk|~Y}R(#F57K;>^^p1yPtX7JYn~@2UxuD*Y?^z+iwSKU;dD;BdJZX=%$Jk@-arSuoHhY47yFJmawkO%V z9kRoA#1`xtJ8H*l(VlGAny=Y$J7Fhn$vkC#XQ%8b=0;n#6bd!9YtUSKb@7uk#LCH7MLE_<1Mx4qoH$G+EIVc%!3v{%`y z?KSrO_FDS^`$795`(gVL`%(Kb`*C}nz24qnZ#2I&kJ+2-&Gr`akomRwjd|2OXdbbj zu%EP_vY)n}v7fb{v!AzLuwS%avR}4evA5c<+S}~y_G|X*_73|Ei{EXu-?DewZ`-@< z-S#{7yY?RYJ$tXc&wk(j!2Zzw$o|;=#QxOYZ+~WgZXd9}u)nkq+F#j+?62)_?8EjE z`>1`)K5n0|zqL==r|j?S@9op}5B3@RtbNXWz&>wZuz$3FvVXRJu`k-c+P~SC?BDG_ z>_6?l?928Q`)~Uy?hIJxoOdm*)x}(!+rrIq?Jn+SyE$&I+tO|2=DDrie7B8T;I?(! zx$WH>+#B7S+zxI>x0Bo1CEP-{i`&)h=HBdfcYC-!-Cpi3Zg029Ep|)XQrF>?*G6rroJ-om=lVxYOL}?j7z7ccweb)!fMOm-R{2TzV7aD-*De_q5GD* z(|z0B-7v ze&Zf?kGMzOWA1VHg!`>~(mmyV=YH>=c7JfsxM$sS?s@lu`=k4l`?LFtd(r*X{ms4P z{_g(a{^|bZUUsjzf4f(`@zy)_cle}F z`LxgYtnc()zT5Zsef++DKfk{}z#r&yzSsBpem~#?Kj;tg2m59I5PzsY%pdNL@XP%Q z|5ksbU+ItXtNhXa7=Nrk&L8jJ=1=f%_b2+*{v@CGLw?wg_<~>KNBx*D`jh=yKkg^| zq%Zj?e~K^rim&=S*ZU3rG=I8(hd;xg>Cf^tf3|<8KgXZz&-3T|3;c!tB7d>J z#9!*)lb&L3;b37YJZLSq<_Ep zj=$D_z<#DCO$!+g_!%zWK{++XLf_c!<({Z0O6e~bTw|D^wv|Fr*%|E&L< z|GfW#|Dykr|FZvzztw-$-{x=kU-Mu0cldAkZ~D-G%irn0?eFq;`|tSg`g{EM{Js7@ z|9$@h|3m*H|6~6X|5Jaz|C#@}f5893|I%D)KH?wrzw!_HU;E$qhy5e|QU92K+&|%e z>!0*b`QQ29`=|XM{4@Sp|D1o`zu^Dq|75N)H=AqC`^^jf&;Bpw2J>F?LI0xvtGUVl z&Ai*Z&s^zW@_#qC`G5F-`hWSC{VV?8ZPSy*j*h;RmT4_}+IlDQ!{yRsTVBdo?@+mL zY9W^AqOG?yTAD1ZZOcm;?;kFfho>jjj2G6&ha2_w{*h8OKa6=$wSBl=jSURv0XjA! z#Q^Z+t8IZI#Dy;*js=3Sz(spdAKhN4SK9(5qabB05QqgX;s-SdiH|nw?FZEXw2#)S z%y=>-f=H&)@q-&9;$w|^%fUnWa?2QYv1Qfb_(&mEdrp5|Bm>l(m$>099O}}~On)Y*6HPb(5 zy1CW1N)ebfq(&T7Gxa=NmT|QPS zPqvnLzl!(Mw3qXJs>42&XkV8c?;p@I&~i}9fetN`TB;NW#PgE_89lCW2Ndpr!X4<= z^F3PjYN-}D&@b&k;RM;(L86^eF7&KI2^318Py&S#DAj>dJ*dzIv+cd4ArW4_UTy0Q z_)uHEz-4@SWjtRQlX|I9XY8q@U{56ldnzf|Q%S*|N(-;4te)=?8KwF}b}5aPsXmce zN@bqvQ@H&)u3zEyE8IY2o(dFRke)SK&YxPqMdQ#crj=`KtyG&cRw}K{50#|HY}m(8 zVZ5}CPBA0tE)h!_KMO%&Jlz$vR!ftm%AAp6p^TNA3b)#O$EU{fT%DDlEL98Rg<^ho zFjXnS&iL>aK~>K$L%8NcvzJd4DGWSv?;!p<8jIZUn^FSs`(Z|*m`JwYATPoHZe4kcZW~ABc|Q4MMNB?z^y3yBgaau zD~qEOd4E)Xx=pF^E5?exAD#4=@1*sbcl^*IwT-uI>bpb9b)vE4l(*nhZudPLySJtAps%N zApt4VA+DY25ZBHm6@OCkCl!BE@h263Qt>Ahe^T+QGiTJ9GwRG4b>>V`@h263Qt>Ah ze@gME6u-^@GAYHMQv4~!pHloO#h+6CQ_6oz`A;eTDaD^s{3*qsQv7MfpH}>7qxdt5Kco0{vXIFr z{~6^!qxdt5Kco0Fia(?HGm1Z>_%n(>tN62uKdbohBQDHuR`F+*f1NsHvWh>e_%&E( zvWh>e__K;XtN62uKdbn48jA&vq$;qQGR+U^bROIoxo&t0+Sg~_&Q<93@Cj%LCFlL{00=>fWp({oC#EZI+4i) z3NKK41En`mdUYz32~Vz<>ddjMvvZ|-7>M5)G$?AkKJ0SFARi0UuXIABzRe5H0LYUS3kyZI- zRleB)(SLS8^q*CEXI0)=m3LOMwRnFN#v6Jke=ryZy4>a!un)d?Ddx7S?K=WRpc`wks7iiuKH17qP_X5p(f#$tH z^Io8NFVMUfXxTG@k{U#{$h`f#fkg+j-Xfg2zrH&K=KCqv0tO)IEPaB3nY)AEqn$Nmy&_x5tNEw@(9|BU*Z+oieKUt z+KOM|71|tEP%8dD#owp+`;_lK#V>g$8A#qisrdUvUqQd(mpp{?ieK^&+KOND5Za1g zF6Piy{F0Z@R{WBel7Ylklq!FTt7t2JiK}R<{3WiUt@4++inhvM;wsw8zr zI5FT2T!@uQ^u<}_eo(OwD)*8Fah|!yvjChW=$U{yr%dA#mewN6c=CXt;VAQ*>PV&M zVpS5?$yh5Z;1Qbg-0HegQ&W0N1WR?Kld*~7BrT;?a0x!Cuq5u_EK`P(DMQJWp=9}| zI>et-9bHOU7wZ{qmMcmf-=*WbbbJ@f73Y<+E|x3W3b%{piZ<&NCCe41^4P8Tx)onH z>owKU&31)S@pmi!ZpGiN_`4NSS}ox)MFQz`0H3OYsd$xh)g*(sV&c8cVaox*9dQzVz{ z6wN0)2WREW<%)=|T+kwg__RU{YKkM)e!J)Q>eIK4F@OYA-6;Bq#@l-(! zwRSvP5U0O+^nAhWo#S}MU=G@I@z;W94YtJd2V3E<1y3BzLwyH4cR;^2vLos{;V+Ko z4R+o-j%N-Ms4p}NcZ%c5gI!QRz#M?%2jVY|Ckk>n-jC-B;&?*fL>ym@zc`*0I0?sx z@Vr1A_wALqv{;(Xt}6%SyPfsr0fiQ~Tgsn~D8pT|A>(^0<~e{tMVy4A#S zS9%@xm)Y~MzuaDK;<#IQnX$N|_Y~^SyQi^#!M$Kwao6r86UUvawb-BI&oZsJYjrN_ z7x)Y2#c}uQ!ky!|gLM&(TnOO5we_{*@r5_hm#aM$W89KYIMjZxS5 zYcT3Me=W}5;IGHo8~u$qdy~HjXK(g5O%)V0wn3wY8Zp(; zVoPnrsAyx2HYnPtRHFu)R%)j$^9ZAo9;TL`OXF)B}um?(y`TQ^XnCE|G zeiyvO3!q>66|a2Rb;sZQQQwr(6D~;U+-H5&wcqrWKXUAR=;z*(7H_-qnpeE+ilgTT|TdFAytq|e;X=({DQ4|&bCU;7m=8~)et(Z2MB(64>v z%Wk;t$g9%l(f?HBkAKa}Uipd(|MKtstF-u&b5nZk+pqiDZ@4~vb^6#e9Jycpc#3FB zfA$^1_L_luu`f64Hva;gmVh{(sJ!A97aBk5F01g-N>8(&g(9C?{I zoL`ilWQ?J8jA&TAdU10)n)U|NrgW-0rv0($9*MaxDczQazYA$Di2s>ibk!H8oR0tL zFl6^3mE()Wsoy+>V`a#Q9H;fC;0hGlpdqVw1o5rEPI#>?{zNuU+EadS&-CPR>@aB` zq+?_M+?Q^1MLspVfeZAsEqV=DezNyjdyUMZ^{*S>d5J?~L5tejq6N^xU4Kq_$naoA zpV6i9gmZ%g77cX$;t4AAT1E)Dby#xi_$xeguJ9&fRjb?jH}!3@pIBdBKc{M{r|PEh z!9QL5F@761-HQK!9Bk9EwJ-Ft+9en9j@WandDe2@>WBJ~BY&Q=Mn_#3rhlD3yW}wH z%EsrWXBltMzs}|dnx`12u*rVpepCNiem!7rHsmF88h)+l&Xf7Vf1AT49D9J)OdlUI zg$8`d7*<IwlT|??id#~jh3AHmgSde z$u#KW3%oN8x$P&yspV5PD(&3*isQaFRPSH$1$przpC%98vD(X!;R(c_wxPqHI-`V?5d%w5$-}nA#?~jjO_@STrwC{M@k38*E~Uw8a<$KP=LCyxKj@t;5b=HvhT_`kS#aq+Q>pLFq4 zEym|4Pi+3;n+v35+pA1iJ@%-BGw&C6* zsl{_W@w|q3UU%fjh$roR`QF#>edpc}?A^BaQ+q$N_ltXV?^pMJbMN2m-Me?+-if{U z?mf8o(9w?|ocuy=c%Dc+=k<85B%YUFcH3odZ}BWnojUcvsegIumrwodsef|nwo~7C>bp<9`qVd^ z`nprEJoVM5zTniypL+7icb)vHlRtU#hfjX<$yc9z)yb<*o_%s}|J44;{r&y-??1SI z&;Hx@-?D%E{!i?`asRgcAKAZk|Caq9*nj>0_wL`k|GNE~_P=xgwflc(|0^H-jCUXX z&%ghl-+SWUpLpkqe|zHJoOt_*x1D(FiF)D}PyE1%*Pr%wPMacv`dOVteM>Ftf1|H48qZxQK10U23JbEYi z=$+uBcY+_(CHScEM>Ftf1|H48qZxQK10VDZ+<*syJAn7_RFU^uhL`e9aOPc*X8|t) zZUx?(($SX#$Q|Xqm}9)_a_kD=u9VK=y_K`B=2exmfjfEUg>jF)iRX^+A9p=3>VO~r zCg9yEoqYwsn6uxp_0xHF{-&jwx#pz~=j2kuVkBJ5st8*nd=8Lt2sbDS|3zZiHuK>HHfm)s6K z$U8xI@X8QAJe_e*e@9A}y(pz;VDA~Xr1Z=S0O7Z!^sE;Ew*u(}$%avzJ_<@8;S?aOZh z?oH|AuHcoXr>FD@@LzCkN}q_mPkK3ku1{vn75H`qa-Rb4&tCxG%couryeXy3xO_)S zf8kjGIry}v0&h&|(~O9AwJM&bP+WIprVDSg)UDP4JGN}rAFXTLtB&p8KR z?B_D(bMH>+MOOg$@E7sr^Y(x{Q~LZHQhG6RFNXgG*!@Drf8j$ZebMbHU4`6VV(ecM zeoIP!`8aT0N?-gU;5{k5gt1@p7~qbS{t7aG<^3uBRr;<*_tn<}`1IGV0q#xdOK$}3 zOX>wLH>LCyR{`jF`CTc!g7II8p09j;N?-Njl&(1&xHYA( zz5uv6rN4d-a9v7&108<@UDrMfcx_6rMAs|9UjzOcbbc)|*S#R6uS4$Z(EIh!-+-=f zxFe(EfVZUd%~t~V zrgY;qz(Xnht@~5@mN%yKtychy{nmH$1q1DJJ%HS|Ve8wF{Wk1;J3f5-OMv&M^tZ1~ z={4s7C8h6p0f6qmb2(6XQ|+k$x?W5FYi~>G?>!fIZ%W^JdrE)*0^t6XZhB)%-$nbo zXul4f-;K<7Gxlc2eb0?4eecUt`aahQ7Dm0ua~R=zsfj0Cc?lwZMB*`i-XoHv;#kbPw%& zUJTp`K>y|*a0PHZa1Vg3-^##UzLD%&H=6i-jmY5 zJr}qMxHqMDUIU=}-#rIF{@-5?+>z3McouM5O7|jrFZ_Gq{SLZ*=N8}{DgEw?fcK{K zA8ShYy#RP?O7FstcfE(VA?f>1$o{970?6MF|Nfg&df);8`owdAdsF()uLr>Ielc); zO8?KPoDPD=kZ0XL-d-s8Y^Dg8e5 z@81YKl+u5@Go?QuuYYh~O7FW0xF@Cm{&WET|F{~sJ*7Wn+#fRT53%_mvJc*!(jTGw zf8x{sd@!Z|m4Vw*LS1?YI}g1vr9a*S=>HRZ`V-{;^a=nQ@4p4OH>G|0_HRw;Bx6p# z0JxdAHqQoL2)rdN($#6PcrI`w@Yb{#&IPUq-kBC$!xu+xNeiyyi@iJ3;^>QjyVBy= zl|TjFn-*umJL?^3@fgND_5$GbY4NzrfqT;8@n-|C0q8#)-r3gzcc;aNp!Y*=0N$4t zA9{0IJmKZQ9l-n3;v96Jb9-7m@i728pNO6h!^aPMPg*?bsILfDcE}I3xT`S;=C6C==_M=(&Bu0=VR-Fa{%y#$X|$^r#&5@|02d7 zNB;O-X>l?5;y0$nCCFZKB~XBOrp2Y$dirC4niiMcm=@1K?iuKR=51;5tPI?r79V*- zT6`4aKI%H)9cl4w^gkQka~SuWThijA&joHyi$8ZcaBo_C47#3sD}bKoT@5hi`S|*L zWS)OtT6}B*9!!hNk^8vk0Oe z7FXcIr_lbC_ol_4uW9kAujk9(w$`CM`bwT;Qg(c;Oy^+-KaF7N2=G zaBEt8RtDak7FS*kyfZC6``Wbl9OOQSzRzL&=R$w(d(z@Xcc#T(yb5@KT6`XTpZ`LD zaW6*i7vS3$+@2O+_#EI(Y4Jrb0O-34TU?(PfB8xPU0-~6TD;^nY4IhD`719>i@*99 z;KsDL8XvAk|6jWSVBD9Y|I6rq>E*yX)8b_XxIZnvoc=GT|0`ab7B9!vD;WQZJJRAS zZ%d1>!p1e&y#~3jzB?`cI%EC@daliB@yZJT#=P>LwD=mveGN9h=ApFs+Uo)Eb))Riuc~SBH-T?N<_7fN@L*be6Ed&9HZ8vSS-`z%aU;6_)^%y| zEiVBW^R2h>Mf@IcHGs`;doDm6-_Dr7eGYJYTD&F$^nJ&zY4LYn3cN2Z{x1H!_5uKV zfA7V>{b})?H>SnkKNomUTHJIOa9>({R|1|6Tn*d?ygMykhcB-~|LbUfH~jC$-pwxp zZb^&pIUB%-@3{uJ3Aj5gz89U}`+~Iiz83=6_&#jC{yG5Pz90GTzcnp>;Hkjt)8Zdg zZvU?UXy5WsTKpioe(+}C!L;~?*8=GKA@C1j`&RmHy$Zn2t@owH4?hP$?+?E-Eq>%U za7SAFqn7}8rNwO*0O-8!y=n0V@Q|h0q;wTH^Tezrvm8x3H1B~ zWB&15(&Bdfzx_>V@sk<2CoO)8@jvy(wD@VpalKx=3Hg75{LcVC`<%461OM)Le_H(9 zYt!QA8S@KQrUhH|;?C>S;?3B5GyGq?J1y>l{%7~6#V;ZAOSh%PTabCnOM#je|Ke)k zooVsQX9I5q-kTQx@)`hJ6@3+d|JAhs{{6~v;J&o@*SDv|-4_6FN{e5;0zlWV-jWu- zb`@|}TKqb;e;vQydL4i~*YBfj?HC_&8hAV7ap^uO%c*M+_+l;q zUzL9F)K%%0lkb3csg$7U`RR90J%b+r{KxdWCqEb7n@>IC)Me?vL8nufo%~JAr2UE< z)^y|{^One2qeU@OMz}ieR9V0|DK9DaREM&DoW=?kWkKT<&@kg<)W%&j+FKK-vofPg zgUZm>PHJbMG)!A^)~|MT8mn;mB}G~pAqgbK7mg8zNB$8c*klLKkJ7u#p+T8ERmW;~ z?;5XXmcAa9B;HR^voS&Azo~`)jawa zo|wM{K7AIaaBXdH_aMV7fUvb9BNkglvN+$gs9vDqc?Eg9$1Yhk)7!@B&8Bgl9-p2- z@joSq{I++5wKp!J?}F z-eg(dBKI}*q`7~wyi6w=0!y#y1{<%@i`?uOSN38=@D+o_p*qGc`CHCO)757_Xqzq5 z*Ept&b{fnZ&C-$d*l7gxVa3jFwU{KF?wdH!D)B{9CeYF65=tGMyUz(LveRYEPjNjX zTs!oZ^APi`+F!d*B^}w$TQQc4>6TsD*o1i`C~Y&_E0)b-MUO-?P#qhsc0`-owS|kS zt}&dmA|oAOe%hCpG7Yy$B>8E&?X2;lPJ$SJ7RY@(iiQtK+W*c=7sA_mXp~!?_}+NY zw<7+AtW2BH=Fb81VScdpR?P$B!4b)1H)?gyeTnb4-4moE+lD(;ZT(s8YA>~M z+K2i`--c{_wzJn^!4IH~&MkXj<77N7CZT%Gc3m6yizVI7LTzI~t^=G!a1=W`&V5eu zXw~L!PJ1qju3|~HyH)AJKd(a(rD+VjyU#va=h4TEX24WrY{>Pv>l-8%-Xo`Cl8~?p^sz>H-tSwVojzvF>DG(Yq*GSeMf54n z{cYH4iMC}9Z1Yo-$5OzpP0M&OUUKqO_rlip5fvWXSydpx%Bk8>`<*lGHa}JklK0Bs zOdoXM&^N&G`B5%BV)B|Oo!au>aNg57s2#yv|qkTvhpK&yRTnC4A)8*Ah1 zdm7`UrFr5xts@WZMUkIMLpI-S26abE9rc}2HIP0)cFl8atBTC-9=7N6x)QNPSn*$? z8K{npRy*Qvdj|U8PVSlpiL}~Tx$}woSl)qr|4{EK{u^@3pXdreC-=P!s>#RO=eiZ& zCv~V1fU<8TAIhwY3Jh( zvR*HE9EnrqDo3hZpSJ<2&~1CvbPegVa~~@mk~E8LR8(=-k)=<10?)25@mYMPbLyLU zQe2za%J>Os?b>kL4~V6-C>$3>Wi#F|70uDs%){P?h_%SxsXS!tSR1+~_YPWhyf2z7 zakm>W$FtP6u}2O9Yy33ko`dXsn>*Tp56soZoN1)3fzq@&Ue4|OJ|SXS8;i1JS{vT% zo3A&{ZZmLcHSLv_(Z&_0{MR*Ve>^H19c6-h!-@CQkbgsj-`&Ud#p31jnZ0XqGvybp> zTbx5qW_@26pLsH#huDV?el}VemGJ@;S)0bfDp7CsUaP%LwDMeSj#$acGh=MdY?SLz zkI#jFcJK3H@`VA}GBU&6hunM#-0vZfIATvaXZn1u(P`QYSF~qZVABO^?IOGGsb)pJ zf|H5}OwRy^cn0Nm>owiR)~O|>f@^wZtl3AKWn5TuCcPHJ_O+T3tbmT}yJy%EwmGcy zS+B*C;a0T6qPLoq&W)c2IyT0nK1te9(U56@jh7=PQ8v|J^=kd3Jz$JUPoUS#jLmsQ zumU>rhM3e>b&|wNT($8FeFaZk#xH6f+xpMdCeK)~#t5_K{4i{_oMAwzN15dFZdhR6 zus6=z_NOX2Sq!nxq|2;=r+JMz#9lUwx#6k|@U$U!f1ytns>bD}@#@}ip6|qgoUXUR zt_w_loI8N!hsT(Pq$3Z$k91vPc6(U__VEI7D8}w_W)21)w?6MkT1JIid#ZCK@^)`M z)msZCwly_)gXi*h6e}RkLYxqES9m$~d0P@13T4XHOPlWzO!c*Xq*Tb>Dm+ z>hs=HnGvbj2U%pag`;X4RpXf#`e2>XSyHx#_BFG054CZO>6Kc%#pfbvtN^`%#oVly z0cOAJsy^Cs)Ag!XUIw=NYZ=3Pa;}Wb@}VA+&+dn{ig2#>Jm(Qq`pta^FNV+EB+S=LIg88rf`X}6@^g@}A$nVwCWp=`I-+pKEKT3{$PE6 z!N;l3&G#Nd&);@AyMO5GS#!DRsT6u_ET$);9Wz=E3zHN%Ww2?~~u4xR6wrsYu zd&_w}ZQ)3IkZoH#Nk^7PL**JqEA8$d!WwJG1Z&;9&W`ETIy=xm=105nq}EqfI6B>G z0RuKWtu3oPdiMdV=1WaM4~o{l4Osb(Zzfs%nIGb4#rtDAv;S0br<#2-?_6B$S)A_W zt8FZs4ppt`$_*FO>wV%i+VWSS^VqNK8C4q2CS4SX#XMdwt7jlaGcv~X`}QF-%l_WT zN`6QGlaAc9(?pZe;<bTJ%MG zC!f6=w)yv1&)ocD!TbVEqcnd@^E=Z=Tm5~W8@(b|UC#C!wIeIpA`&NCgh2<6dCzTr zg1?N@()fXWn%!(z&LvhP88F1Sz}&}b9s=fBX*e6VIyK3pOILc4 zoUH$HfV>jV`Xra!5j3x1X~wmB(N;Lq!jtticX7HbePsIR^t?Vkg5Lp4yIhF8sBPBZ zPd$%M>EhXXS3jUgW6Wt+VapNMER=|7&LCnXFR&)mFg<0#B7vChu%K#k!8zAu___sgPz03 zuCo$Fgp+jS2BlkAscNfo`aQc585T=xhgYTz{z&WkstS zs>6F#LNRH2ROhTVAu46at53<52^&?D>62)ut-8Xp@IF#z%T#NIslUk@k&L*mL!x-Y z7-W|3Eec#6*<^ICv>l^@|Lb78RMel%b>hfPJ6LUj+l(1D2|5%<+M_o=%yk-mWy@AU zD-e0c%9*;ppx7cpyTy>V)3MJ++{-jN&|q0`yh;~~V>uqTURTC5hs=K-H0Rne^zO~+ z-RYNPHaO35Y5rvAoA2-z<2iHBonLvzw@sD1PfnkD`uU5rM_y#hJkQ|bZObd|$Z3zP^($(7 z+MH7#(`B1X)L`h0>z?g*8W(vhEq`M^>E*uIyxLleCWju!*)hHq^L$Aky(ZWD`Lj5Z zn>_#U-=uAaxUg+(0hzyH|By7D`vAV63sw$ ztQoNkLy9NbtYmNj(g4K$p3Pm=2)Nm#OT)S0GwBN!q}7h1^8g&e7w*=V@^b$6`j@;} ztd3W*lUskoy>@r?%(UAKqQP=~42|=-+v&|qjoRvI^cR=wt0+6Yb1qjsMgyL=c^>K+ zsJZYd@L{t#o#%L3ws8gYeT+nG%wTu|j{V+bMVI8`wt6XPtDed#YVC$|r@6gq`}KiO z-)(K8XJCX@K+sa5{lwRB^qS(_JXpeQBz31fY|uSNykC9Zw$&l8iNy}fLG`xyeMb`y zx_cP0Tg=u|#(XT0Ui(fv8`*44tes1jnb^vVff@7DD?SbM*j&u;F!N3QWP)R|j~>ph z24|f(s!l`R(e56?Q@aPeMaF&im|@x$wN%g!h%6lFg^eKktdZcBgCW-S>E$3iNrP*( z`W)w==RT|-BKN96MQ2$62W|dmr#aJ7eYL3KJ>Jsj9Q=rvu>IP(G2XBGZ*+B0Ym+VIVvvMn!dkE`Cc zdWYA%Z}&33oA^^w^_Wy8KH6jD>GN{SyWSm&tHq`8b~qHjS|7hLEjIH(|s?Z zPyP5-RK=5b-=Xc!7py+T&2Q^Vw*ZZWyhPi*Xpd)?r%mY5?qoZ3Ynx8k#;4Ed3@(6E zSoZv4tJW{zklocejuc25<=f!?CtWQybEYHVD*IUuK|Wla^fIUjWO z7=7QE;RF56?vcr<-RQ+e&10C?Ul;$liJ@vIb1w=W2>!zXBXe!OQN=V)y#W*3dixs5xVUd^F9511Z*>oMf!b6|U~V`Yyz zoNX+aU&2MFuyLU6wpNzb`C97-uI=o0&30++gFNZ|&Eqx?v$ea{8_8SkfTC?Wg3gWK zEetT$%tQ1?P3NUt9JDtVw-fVkzT`Faftd9P7oF^mpkI5PixKsjKKO zV4(G9vzWVAPPeSA`PFk@KZEiSe|2S>Ux@>)c2uloT41vXGM|FZ&;`iuq0K{ZY{cYu z3T4UXX}->sU-CzwqX4Jl*D(!l^%H(wxNRnFE@8(uiJ(QEZ(pg4}zF^Pnpbx*w4%>AnO+3GoJA)Ub*Mf1c*wGZ#-ycYcL6;=d`wER#Mt8fD-M7bEmC7hP)``*6;UJ9hdDAg++o5)rRyL2C2PY z9WU2S$lBhBNYHI;c8^!QEQ%CqL3U7NCRfzgcY5dfdyS>tH-505iQyn|)|F_E(Gh#d zSv&uwtIVJZ=*aVHl3B10PIZz)nD;%msti&lj*YQk@q*dC!EiOztcT$ z%b@eyi@i*~KV}D-x#3jQ8$ixRS zPiRl$?Qu7L>#fLCMv3cKoETY51N?^b5)OXUx8`G~{aI^Bx^ypY>xs7I8{8wAv?Yrq zXB$ymn!AqLJPJ?p65XG}4P&hw>Q|D=is=MdhJwz}1?ZM74oJtGvw2w>&J9m(iEPF9{&W&;$S zn_kg%!Y|Mn8i*=1`R>gDIqh<$rJ@B*)b%l1RPPXb$&T+OZ1fkc=2zgblXvt_`*EH; zhnvlHM6N7o#ZB&6MOFIeI6imQcHj9#djdAoxvrFw4m5Rp#k4yw_;hPuu=)X;JgDK^ z{3((^2HG;i)j0B4Jq;gg`eVQGZ`00f4zMO451oU^h4vuT=7Ha5%SxYt z>T$v`o@E2x`2K}uT#PD75}m=7j5!{EU#8snGoKB4L5GbtT$^36X%drmx1X^9=TI4b9+N^@Z3&W6Y9*ZRL3>+g3XOF>Vd3tZ#?$K~5p6eW9MPtLPtYk<0 z+I-C-IEtM!+=@qz6L;C`-UlkJzsseEbuSr~ABLZ(U`FNn)c;jK&%k%OJ9~>S=1+0l zDEXMp%Y(A(qeuDX`;eDv;i7Hp)~|K4XcY)j`@l>iV_p`=jT(KiwX-GTK6;cth6e35 zb>{nql|6ocu2Eb4E#B|vLsV|~GhbJaq(8X0C0`$ix@@_!ohp0AtS*-4>Y%#JRvFbb zkn;~jU6y98Ve>n~TlF2*mOL-}-tLoae~nMT8>?V?ccymS14KpxckiWzMhbfzb>f4`(UkJVRF#Xb?Bkzz8WJbT#+Bq-9W3Vx? z77kw*VLuJD*5<_dk7Qkx)aK<-@w;CXO8boZ#C3Di>p;?&=`>A6+pg$Jqkk^4b#c)1I@GV-8}evZHanyN zYD-Pz@UY?eef9Xw!In(LVltaG`GM5~GG#ulT)~`M(S-*2y(M#{$#mI9Pw#EU?+A8% zdjY#P=OEeFezGC3ZC)Ye*Ji4Zw)Hs<+T|*}&bQ~#{n^dzj-_hBVbnoyYSz z{nelYVli*fuLE6&nh&IN^Srb+^u~EW#w;LXvE%Pb{x3Sn-!qbTc~J4)@xl3?d*1Rq z)JdMt+vfM7a*QpLKU{7-lJS|T-jJjI6VqE7 zO_W{<16Y!(7T&Skc;lzQ)PUQQ@vvzn*K6m>Sx27 zt@2r;bS0F(W(QjJX?d=0OMbePsx_oEVhU&(a~v5ypRrpGv~_E;VOERVGCQ<|E%&W$ zeB*{UmTO6E%eVC>zH5|p|C>|p=UN|ooQ&M=3s?W?dn@j7Weg)_t>b~V=6uU%#K`?L z8@7(6$24t;dN6ARGuktHR53EFfW-t7~c+mDcv}j1;p`VTgG; zHINx5u=A>qeW%gJfebUubZY52t+UTMjXhCA9hCdC?6-2gR#p~kMHGl@iT$0aVtF76 z$I7ePqkrToi{h^~sAX3~y;WtdnWzboe>?GrM0s=(A)-T^aMD zwrOfu?J2#IADgSmcg`+d8N*2ONZqgT=ls+7SDWRDjyytSspH!EKxV(;=8ILPwM*lK zmm4ruk*C$L<=>w=Yi*ntQyT{!vSBds*6bGNlf_wo|0J4Qf06CEr%?1x>bTD7wSUk3 zDEg>n+Y5GhIx<-^=3n?3YhM=4Y5`Yo`+3zb{(fP>-`x7uF6CDKwH0;l4Y~bwNLXq_@39joOza~T8K35m&luqd+ge$%*a_zvg7Vgg)U?i ztMY+=?*QyRu@BNVegO{vvpZz23>&V?t^=vuAPuyX14Xd%JwP55=lpt!oM{0G%TIuo z{?5+;j(nvd>8Qg}uKK=~=>_Ja_p6U<-$AUuE2utFHQF;z+r4N$6{onW6z_?uwrG*I zk#gUCT+UcXfTbyP)2cvmE9L=htM#MsQ@j4`_DPzNrZ+q8HtIaC<&}if1&K2Gof`C( z->T=Xc@InYIQYEkt&d*Alg&{=8I5&}QY`W8C_3xD!6cxpdURceM>{vnJ-htu&-?v1 zvg<5%<7NXbc7S7kV?XMe>)2!%ePUIu?;d{^8oGkkl^fX7>8O*DQ zKdU$V#XXCeZwyYR6 z%})5S?AMN!d!5x|}NOO@Dn>)m#G1Z?AmJX1|h z{x)>k~GqtvM(y2N_Je=ORDC{$OftUhn~b-EZ3Qe#ay~{tntj z0iG(LScrqPXCcc>snD$Az5_Hk$6-IVTQXhpfLVc`rE$9&U? zog?r}Nhb<2pd-(BP|I?4AI;AuBV?5jUgYLg^;w_n>IhoKD~(J$52eY*nD`Dl4Sp}L zCkJcS8X8PT8}IERosAE2uVSA!%VejGC`*>f-Ryo%mM>m!d%pCg$3PaN4L(dRk=2OG zc|G1CmnrV&Qmf(3xx}S6G&^K&O%?jsqi9Q~^S9GySwzSdjLvGt zDPT*~>K&uVJ$mQ+=&`@_Y|Y2b-<3JNcYaE;!+foHB`O2eai3AhwfyicTW_|ggSYGK z+FsM24b0=%d0#jCMQ*mt*EP{(ItzSZpwCckG|1{YB3K_)W@KgjdYqQ{AMCYQh)JF{ zthT|jpS#XhS&A?8{MlNIE!rKTCLf2G%}vp##D#KlPq*fc1%H@^Btt07tWH-8Y2WgA_ZhPYqOgsNxX zoA3EF0_c&| z3hSPU5sWesyUwheSIHo6KGTk{o9If0Xvh!M8?n!lNMlRo5YlaW~>)wb`chvGEY}>9|8c+Q09dTQIJ05cPKbvRE zbbq#sE>*R@N>==m zK6$a}YD+H?(mv4CH$9Gr-1xB`R4=+-E?HnelB6A#b8fb*y(OyVJL=u{O`nI+x6yy~ z1-Rw9CXQn9d4ItgN4t-E?D}s@B(XN8-1vsq##Uyw&S;IV+(|TVVime?Yi6=%#^$Du za-BW~PdQO`#(ScrnVuHB%sWr8oyKiSShiliN{49WQ&(%P4&1&fV z-M`#sJaU*HP7V+BFK@}=X5Xm!cE8}WyJlvU0ozk*!-^|$fVKWC-V%9|WP>`-RD$gH z*Xyoes4|^JaN%dytHt~!B<06JolT%)JI z_lUbO|2IB&&wEKp+5&nE)p?z6GCl8hM`Pde6wkTO17}9so@kicCPuFDo&95SuN>dv zUqs9Y9OkKXCyt5ksa@l@ev_A6f#F!^O51VOtsTGG94i}o%iCSL^W~Ub-*>r3Dh$e_ zA8H#7vUXjfbK{?Grn{f4Nq>jviFPNqOO7!upFA_+$12tws8(YxAZ^Y0S+fq5UK!bF z%QLf4dInT8(dT%2e;5BoyS`mGpgNKcG;H!)b!q-qJfUqKfaIZ{yU)4#I>F6H#kAR9 zeY@+s`N4P1vWfmp-iCKQ%g8ofG4BqW-J^_G-9|ke^h-dA=gt1jvRPb~Gp$2raooqI zx!e8sUKrixXUmyYsm*?N`PdPxU%a{Rg*aN35>K91u}07*u42s%r-oyH$gLJm<1gKA zs)=Den=d4Kfd~oTe0XZThe4l(OF&_r|K>Z71{E$C${O*XNql~ z-jTO`TmxvUjzXu}wr1&)3`ds4qB`cCI`OLZZpZeQ`DtrpViWAwo6S!x5NvyaUoQ%D zcAjOKW~&3Z=*_yjm`cc&y*cRpT4@~Z?+oaD7F{Ve?qa414SheQZM^JJ7+<^sWWdhm zNA+vVg7kj zk++PLmI>-Xb}bJZ-p-SR=@qW6on2G6m$jozpAdvC`)m3IX=R$j>R*3nY1{luFe3|6 z_T=--a)vJ6C%YWQr7WYg%`fqSUy#er7cUI}z7Y4(El&?6lvn+j?mp^o>#dR_1KXUkEnlfkg0?M-G&)X(v z{E}k5WTr5FcX2-cZ#fuaT4kkc&$M+^EERAbvDyY{pb=|EpK;w!xOvq-s|@E)@0H^3 ztsUZCNrv2#W3v{#LL;y7HTx>U3wqTyOnc4WV#}tXx{Ud~N1Ga$&hA}_{)SV>&Cel!fc;EF1Ng2N*9d0a{mtD!r+d9G<#TflG0rL+BwcEo zPPKu}85|H;^R#K%qoasXorCfg&*?UJBT4f?xO#hIEoW8y^xCqe#*U#2A0Bp2q$ZDG zzExKUo4}Qs|yD?(beyCa?&h@FWry4B`3>1w0d63$wB*f#&q?HH0!i2 zCQx`|bZK<(!+IXzOsXdMfwPUSoHJG`!foz`I?2^}WN+ia7}I9Bdb?j6bEAu6&MteH z%#FT{pUvib*G1d>bH^3mVKTY%I<$B!b65a}w}GQS_Op3jFFeR|yfzJntG730cg5$^ z%W}3jJ|OklJ&r8pfe`=f)NA+e^fKxDP8qZp$6xW;vXWG(QKzKAuLY&8OR)M>Y5f}j z+QtK^wmGs%*d38+f;L|9k&0(g(V%b@BHqsX~z#8=6l(k-Kd4Y-80W{g=Vz* znKx#6Io=pMe|JjxAKwH#pkmhVZ|i2le8a^)EBLt_%?>kC$&u)tc+ zfBEs5>5y;2>0ZpVmnB@C=Is3qcTZ~L=Z1@8^&5n*xs7PJ-}N|+U_{StX7bD6Fq-m}g_ zKJZSev^7;5&fvrWRt1^`P^-q$IMt=`uli2a*HZZ1eV_(o#cx<6T9IgS8^Za}J}^jL zYewuFCTXY;%kLv=z52ht^^rTz@?^hXn;C3bw!mgnG>7?FGUBRMsv}FBokV4KgpvmaB+l1v-kIbMr-%+Q5cu zbXoEnf0yQD*Kb_0Ve1ambh=m7?OQaNw)%qyZAX#SB~PkLO|IMZfUAV>k%iAgKJfeu?;;5ARZvPP|`8`HLi->w6L?)g8& z{?_H`>=5{39t&6OXUFCXP3sd6u)fwdx-@>nP+oP}cE(~p2gfwTGcy}qE!q<8hVurT zh>~N-Ape%>fSp0o(iKq+G^Z7(Jy%yci>g3mKu6Ijr2Dn9x};X1MhLs+p=jh}er)d^ z@{sXZd)X8E&i&E5SD$Zd=2J~Q4qDdUuZ|-&;i;_Rj97!;uj=&8_68d{@!xt`gR_~l zcr#kzvn#}yz-3(y7L~d39K^q4edxP0yf7OPh54270WhB0l{OGQnlG?=R`O((ieGeU z9yc5j9s1w6E=^DF`h1_SgU`SJ`$ea>^OWW#cp6*t-*}df>T;YdnvFAEG?HI~o6oB3 zsARO~X|`l-azA47CX<^d)9dwohdp3bPU;$6IxMp4xX&uI-1~|=9+OKI(eLVM!HNNfJzBZEa-9}_U zHqZom2PZ^C3EN8TBhO4YB=fT!oTQ!lJsJjVmd0*82 z_js1`j>)iTql(jv$XKmTfpe#s$1VC8HPxfGpz9m;z%~7(b6^h`v=>`VBpubbxyc|f zvDqs}f@VRutg*6b9I_f`)wXUNJ~ex1xldY!jB#t(F%o5xBij0q2sdMP8cTL=lcjE{ z**Yj*zxU|z#9FtQ7-j1Yyi7A*y;y44BqlU|Ft#v~3U#ftP0B`CpRy0gpW41J+T>4+ zEv_T-CS3x>Y`T{H2>NtwNPhvq2ih1~9Tm%K&uNSgpAkdbBv+fpVOElMlu0$_HlCP< z)V@ES-xl%C-1PtI+I@0iG+Jz zO%iGAsgt^%*_OEJt&XiaGPP%l>xk%6SR{8Ba$#~dH20l2Xjz; z9vdG0IXZdrvQjfq(ZUwBY%M|m6oUoxG5QO11$;!A*jF2mGdruQtdXW0(qoeX)rn?o&9kB- zuwB1uZbQBG*{%rZmJ^z030`3Oj6&Y<_*cXw5x!Y;rqbe~>U|ayhpiVi(vW=wKJP)g zkK0#;#4=DF_thaDV0*UOUW;l~xT1uE&QQgqM<3VRk9JSnSQAb|W@O_`aa-G&{_%b7 zmD!AHPC9b)v$cgRx(T*<&0?6x)ohpMeX*D;=VH+qZ9Hg8LDbdE5Bl6rbsSN&8G)%t zC%a-?bMl~lYx8@;nPC-AKo$q>=HXJsHm>biF)OySF=F<9^49rjQF}!dYzfTbnw_`* z?qitly*qukY))j$u*Xx}id8v$JHS0|wJTgf1|kbN;&zN)dXa4WjowWrrYdpidBv=!gia6ZLgp`vpjDE|XJb~RBWd|SD5wq)h?*9)GpFp}Sp zr2z?xTXb!f{9Y&z4|~6~dB|ujEtX^ll-a7xiP}16O1{AzUv*D>P??M54hR+eIJ4MWFw)5CP%@^1z&#qS*sbj&RE>d(%XRr9~$?mz6A zhpV|{gpGDwOiSgw%%9WqeQEQxFmYrDWh2_nN~c*PNbxpnpB1Mgm(h=gvChW(<&8N+ zzT24EUcOVF?;aL=W@PR2S;HU4oawiSjv%Mnt6M*ZN~pLtY3LajAG(9JRGqH| zyR6P;cU*4=|EjPsC^!4yu-ogYI#y_j`ub^|fMcd9hgrDmrvz-@%P1x=g&N&{=+P)Mgi&gR}YO zIOe&m@aJ-+)kD;ed|&fSrpH^CvywPeOZK^~m=Cn69koju=*G9fbJFu5X7kK2I?61Y zQCG{k>>8FcC|Vko6+7}Iiv%MA5j7HIwKN=bWY;gu#EFNIJwx4*wzX*zU8w)Z|s$-k+(%N9f zYCeeP`5_X7#sdzU%aZ(%R<&xw&~_|g&9mjO%4+uwF4Rb?H=`?qb)MkX zw)sgz;%IA%?{_B-t0Q~<%pPq?Hcm0Uwi8jY>Pdt4kInZ58KGicnO=(>o~p!Zmu}-( zuY6`Lcy)s}e?~@5`+<2b74jLXFb2>%m)E_94)~3`BDMUKhHJwh)0`V!z}4G+o@5!{ z6&Gu-v?ldTovpIm<{?-Y!=UZS_um zmovB*cqXeWHj(#HMRK&Ow1Kn3se%HV^)_!MdSs7`FXwx6Jrvi-*|cxk$69a2PMP?W z@yuxasPJ=IeScfFcYPZVU9I}u8%WbDoOR-;*b&Y^b=+5M5!Y&iQF|z%*sHUk4>{t zTD%!ywE@d{g^sMxW+UP?f7j1PX?n6_pNAM{^Kpn;ksBJ@Km8P^{j7R5e<6Lgp)z)L z7ZP)>`PlgHjWEzg^nCkh=M~cw8kM062MNdI09y7UXX^6`(dXwi_2BP2_u7pbUWd^< z_blk_eB0e<2bLceJILC#(IsZK;bKzJTcL_+*~IUaG0W-ZeG#)}KYK2sJ+*4Q;xo6D zMFz(jiewndc)~7KJC6MLEW>ucIOj1!`J9f} zxeT;yrU=XJJ{2>cfBs7(=yO-SnqRDy??n1L$39PR7F?u{Ke|nP>ta|HMF!3?ec2`Y{-rs z+3n40K^^b|%-iZTZF;Dh@HA*~;BURF)XL$Q{4IsG)$THAQ=@`&Ta^Wxwq;lCG-TOy z&WMNbDK}rc-QmDzV!F<(*i9VpJEQ?>v={cfU6WLQi?qr%;rM; zzUDKdi_@H;rt=%mXJT#-uH|)ld(3FJ7&qAIGjIK>hHlquc{vX0L#peiJ?gib%{I4b zpkw_0YRH51ZCDwSm6rYL*wRQlsz=r=W7%iI?3e}XX_E53Sv7^t=El3mWvkLHFWg4M zAoHSI9x@!bv2)9+B!q)!Zg591DCRYH?OO4&q^p{ZqupbrJ!%Rcr&h3Tzgt=QyBdf7 z6`o~TmJ0@_xH`?*`3;*)g%D)MfrRZScFq=0ZjudSXU5<7J$sqUp|R~lTu#Qn-#S}Y z(>o`%ZGV+Dzo%^UyruzNNb{{hu zE7m|=OQv1nHEeuX!W}|7`>65z-Ix*0D7$&KSSmzTOkg^6|1hlXOZ3M4hZfNQ=OOzV z9+Iz3lN6Dft~Ir-UfJ#1D%wQrs5(hOte`dTOO5*1uN|q#zIK+c;>~82MgEmxq-N{J zX^N@&ulxHAJCZUfD^WA52=QWm0wY6@y^(78484yv&es7Mwz&%5%mch(rgg?+IOaL+ zeRy8uU<+?;J*X2&tIev&ns6Fut;uGufBOCN(xLxCW>ktiRnDh*kNj`tk8u^nP1J_D zl7bC#!_by7;Tr9bN9HWRYujV-JD>C6Z8^+0Y17~gRL4Dskn8wOs?wL;AFn0OPB)Cl zm3~!P$W3eb48NPRdGAa8JweR-etb}73S;CmAl&`0E#BbK75&1LXF;O)n=6|=8j8tP zA5EUt&ENki@0g8ia`p`fu~}}^9X0Em0kmKHJ>Aa=Tv}{a(ITM95~f zIyNntb{aCVfHhPRSZuV@o=&ze#rj)Wd7*LBYC}P`Ol8*f^o{J=_@lp6ODt^@Gi>uC<(9+~&BBIXU(q=;PJsonR>>U8WkjZ(ZwDeImI?qr6(YEBPWH+J4q|O6StsJlk|8l zXPeKw!gu+QqYO0OKFchvZ`H?g4azpPgN}igYGnX~?HV8=*3gnR^_b_i$$~}m#F)k@ zEZM>p2bz^>vL7*k1Ql@Zf*vFYyymo}0S(qLL@YnO(zlBzeJhAexgyL%-b*)kW} z9=&xRG%MBhw7N8^X-`c%S}o@JGbi2EZ2RVoQI-?XERs62XTF>5kjgF{dm&deMuex6 zf17;yTD0Ap(19xH>Z^iwc-QAuyx_KL)R+-@)88^kL&)uE$mF_s9@NG|vKCRpRz(@2 zwv0DyG<;h~VJSC%YpaAl$~1;Fm`c!HAV>R7XH(rtBII)NG`v~tXX0~YF?*JQ{(K!fW?xwSV4p6o zkVP5sB7kdb+hf&Zl2qZKaczFq>^r?87@@cho@HVp07z-=p7K z?gCv~n`PhC3OWGOQCm-|oypo-S^YNml4p3dO}pr-Q?u3fJMsf-@(uRr@APvLGRmV> zH5jV6Z!Noa^V|2|t@lc1PdaLgs(6bhATYy1;FWsRezFp^t_?j}w5Z+py6W`iJ;O7A z7mUJgZrD@fH8;&gK?<;ZfV~xC%qsKLbK%d)w|X3NrI@9sXQt6ugFEr8y2tDZ1!?+3 zb1y}W%kH4oBlnrH%@-ys_A=gs!|%Y_uVM}Z$9FlC`i=JmFC~T@4(!YGs8wjz9*RtzGW6IMbca3EcM#;iD_wl_mzQ$o*`+IR$o6A3`aF%`}>eM+TOdQ zO+68T$<|KOVitNW$QHYXO2Tz*(v%UH!irDkTR{q4Eygu>RuREW*F^97cw^K`-0Ljh zg0yS4Z7vE8z-C!(i=C@)l~$94D@jR!kHC$*Zn}3z(ne;-)~>BxI**pwF+8*3*(+&4 zcC@uz)#}Xiy=gL=WwZ8lR6{4%}(Rb;iJ|sKQt$&`}w}06PVRdh4yU7W_G`#UM#kPyz_g1{I;+8 zGrq5S(Ylp1zf4P)RRJj4reFMmePO^nRl5S`3L(CQegDUf~M82e8JOr&u2wSBH7Pn5j3WL%f0-L2mt( zwo}-$?7%Ufi}Otz?a4D2)ESvYKj1Ul-)DGx#AYK*!Q2WFqV87*L{XT`4u|myrx1R^azy8x?NuI4t zo@iSAHJ);yIwRX^)yaF3aK*EIskTwrYS)*CVGE7uHCw2+SfI^Tffi4+T*WV{mphzT zQ^wONyQJ^`Oua|X!uKLlANCR)>H=oIb zGyv}9kUf^uc-OPVN6vJu)82Fui9F!I_L#})?M-nl$7A${IPjuqo|$oQfacwSJhrx~ zO>1Mwv<~;FR2pisIrr?*&Q1$kwbh2#@y$n*lvIiJm0V-)bafe%P~A5gx#m03o@w9Z zZ#gaBHA<18fvn_+Z-sW>%Mi~!^ps2gipzmDLrJ-A_mH;fE)7@5oSolr_oFucnEf%M z&0DG?Prcvx3yZPl{n^cdzoA;|d3c0lvKydE?R(!kZ>xK`^%GlSHFC7inFsqP;oNps zaQNbuoVHr8mY?n)J>q2F*ZV%(q-G-ua@xh4eE0|F$ieR#X#YD+|6~`yj$y0rN_z>6 zpP8rZeTg~l`O_*p-S4VHUqRhc8xGuY)kOW-lO&hc2!lm!vdu5c+au?fY*zertQIu$ z2ex)Gzwy0i4Ci%MIApE?t7|!%K98j)(-Ae^^c6A-LAh@Gci^;JJ=nR+STcP%_`;(Jk@^^A{1CVv~wD|6_7 ztE3F7pV7c5f$c|MJ;_uh6X)$g1E(F1yY({)UQkJ|D4OpxtY!1HG z=2eG~yzDEcDr4|`*1$uC1GeZPX)<%e;dEuUz^Hz2f<>G1_Pw8Zm2@EER>_wAjVsyX zHrw}l?GVp~bBIIwOlx)AW6d6w`j+Q3=)fk!aMOdlVVh&ib3sFid&fh6y06puL*Fs< zM~-!E)#r*!cZ*pg&9~}!U91tqp<-*XQV9jk4HUZrQ|axx6w6?lDTC@|{d2gQBAZQ@ z(^eeWX9gSI$^+P*XMs`e_pQ~X#L#|ZYwHZyLQh{8x+7OEl{3qm+scFDTDLdlWUG&J zuXx3v6)8MMB+Xi1L&LUDlopHM<2*e5=+=r>G@2{T4;9*SHOT)>`%pzX102%O>!reg zwm4{SkHz+KN!75qb?-y+kTw~x5}(_X%{}?ou2KF>f9u{cnIDfYH-Equf72ZCFXw>R ziY!k4iaqqnvguoh@XvDcf$%T8$IEX$Z~A5-_Gt5OX^)2IPe{qJ?N_zm$!*NBBrSK< zv2yCy8|Y|f+5u^R(E8B2u$5mm0&WIZvh6tNqljtm)b&E|2_En~LDJG-M{T}DlnW@HezWS#9R8(GyhtG<(cf~x$@2+-_35~WIrKq*dis*_p;S5yA zM$@+X&(5=cld}B{uB}#=3ZEM$qB*~qHFzCIOxlm*Ig##XAAU?3E9GIuq|pT(Xqv@3 zr=x$2X)kfK_2%zoZQM!aq3O?#dtPeGYvadEu&Rr^T7HWGd+gn3nhz;?t81DL!O7S~?Y@0opH4O6leaOf=g98@%fk7K^hC!Q@w1#OrnXlnT z!XHe^Yt`4}-Ku4Oco<;$$<*0_za{JQQW=!d3$~0v+CCUu%ZBwJvG!Wu?6MlptX&+j zipq+bqkyM(JImMi_{G)68HjSW*{)4%9<8)p`_R_`A~!9(7lGE$KKVNYt>61Brtm## z=gjzCupr1SwhE?g9tHN^3J(sxs0Bu@BP10+Ec6EEI zMRH&i^8uFa!1jb^dVSZfyQJB#Zhhu8x`F1_Gz;ejwPidHaN<3FlY~~EV%x8@j{>R1 z6Zc)~ZD#~0uv&es-=xBpvZ>k8bL3?lwg=6z+rD&t+;MFWY|)`HG8nZ!qYM3l>@e|Y z^ssoIkSNSlVZ7r;<8{WPn)(Q7iOTI*{cZk0ezjkl!|p!=!mlYd?Pv=e{#(r!4I1)T z<&x_22w$3fqj4cBS5AULWtHEtPTD8qIS40f;J zX809)PtyI`vF$lrjlcR}q>rzT z<-d6Y*{pg@MT`O15_t(v@Rc0&`cQRj`{lPpvrZg!WAvjRSyAuuor|=NzEBSSx$VO3 zbvpia0eNFOz&$JI>~YfU?Mk}lr&bwr~$5qjnEzZ(#(Ea<7Gd~Y_oy;1Rtu`t+4Zxx1p_t!3 zNA2I-`QXfhyr0HHTfUX8Yrfcs+D2BW{DGc_UO#U6Ost1?f3`Fammai&{W3%@ERTjO z*~qlOmKo62wSb&ebwTc7=ie+!(;krezb~}ouP`)58oKY*V~JHqkN0QydE&Qq+1NDD zUb7yG`Bs^`(jrESjQ~p%S*tDs|8ao(zq~(vWcujcW@WrZkS?<%T+PxLn8bpoR zT#8o!u+gpEWT77BnsLn)eX>wf(DuAb*LbVT@w>UL_dkkz<}EsxXB5tuiA=jekGeEZ z%0TCu*$fXuF6i9Gv7Y)p(f@AlYI-C&j%#l1aF(Pb%7y^J1Vy`P!;%G)J6x_L(iANv zK~V%OOCTgcf&l}>>~3>+v>!8?nH5PHFcQAl@WDU9@X0qDKKkH8o^`Z;f=|BcPRZ}R zh{(#W{+OOwQdBLnA~P~F;zeX+R!voRue~Sd@5em1&CD%f2ZD3YTHvb6wu|j(bUoJ4 zx+ek@$tcyR=P?>MiWSXgS{ug-scf^3+}cH}&-?A8#(|k992NSqQRzmt@yPemavLm< zdf~hQF(ptB$?l%7D5BN+kS~(eh-_Y@0ez1UH^r+!1&fre%)g4x{0YS^Y{UM?6t~r% zC4@Q6NHxkxX%5kBv$Zy2{5ztJ@aInLdiV5szS~K2EuSjt8Ja;F4{hND<&M&3G%I&5 zkYBO@O$mToZf*06R20uB)oAk{O`*!Dl&;P;kId76vNHDyIJ~_MsI$9HJ+s-*$kw(4 zb-G$cgV*{qY@i^Uc&@1oV62=Q@9$?7qmb{D>(R5D5w5s~!s6Dm5IBL9mK8H)8u7Di z*iqqH!@BBoxX?%qjLjudGy|8`c-SsOMsGFAj(RNc^v|CCobhzO%~eUVgR^^Jl(vNj zL`Q|ihCw|Q=WJMF7gD=W5*+Ir5)Qv-UAzCS^w`E*eAD19#%J_;5TdX;dG^g`Cue0W zw8>KtYH6Qxuh!NqpsJZDW?7L7@3MQT=Q+d`Sh@%l@`4{2vE_~5R@%6GVC>3_XjqGD z&!AqH#WdtTR;a6OF>6>WWL>V&#s4$bKKpvU=LpcabzC7Tyu~O{F8blP8qN*bR+Tj?{lt1V^iI+8)#yHDr)Oq==YNbT6CEGZyfn+%u&bR0woqDcwfd^v zMKZu=M%zz6dpgBFzVDTi+tSSjTDuk>Z}9-M{kPBJVHHcTZ4~@CZvqokza#6$vBh zhet9yy~vfw{v@<@^{F-AEhN^$2r!3@s?B;$mOb(Y?+``Fr6)hUJ2>Z+=YopU-an)+z%*wOJ2;NZVig=nfwia6?9iQJm2`*{z2x zd=-8d_n6XNq%FfX+KR^F{qy3UbpG#X(n?+SOM5B#?C(mOo8T4eUw^lJ!L=f*j8*d+ z7UjR+f3DaM&df#aq|`&y^#WcOM-y~1lkILJ`jg9Nr_+1qaWDHMupO)&Ri(&yBIjl_h-+#-Z5h5%JpiQ zaPfF+jtZ+wM?~u{{I$&0>TD zTSR(?@o1lk=l$K%y+Id;m2pEnvmzHX53tQ|LOI37gf=`5%ipvr5Y3Q5A=OcF&K#$6 zP4yC5zp9uYP}SS#wf$PP?im{7SIsu9DU%BN%@%YIsE){oOlv|=Kw*fs2j<)pD=6i+-N9GAfMafna6|XO0TZ&ZR!Y@B*Wv;;WF~%--!nU)? z3wV$RIP=NIZVB5(V&`+(<=xZEJHeQBn!}3hfcH^fIj@vK(H?THm{A+%82b~~!w|W4 zj_+igiD&CLITm{^QM(v@B)4*){H+@%f20 zXc`t+P&7c+?T!sc-6CS~_hS>BkV89iu0=4cS9?-~ewSps`*nzr z+oewn_eVszHj<$&a7DC;MR-6|uih@=BEt+1&V?4yYjhGtzE=Sr{PUQ7b|ab$Jr#u% zC9bl8&SnOE)%shMB;C!`nrLAZ;)7`9gS(Oj*rN(}3EC)jr<`igB=3z~%oh z)5eYO${SLQgCs`MW5j6mVbn4GTtLE+d2(-ybY!)%dcaXeK?ij5-xfL`xkiWkGSvZT zjes@niFE-7TF_|ovBMti)sOk7T)UWb_pVSR{CSbK9jF+8A!3}<<>+PcM!UR@eP-=1 zen7447Nd-vGLUVQsKajb7*$&mXom)jhFxBp(DRYlZ8IYqu_jjA^*C>~+o2_Dt=%&E zDb2N;&CxEnZC1xR7Li|zr=Ja>RupP%@j?C9WdLJ=Q|XygQGag@kF&rVaAd^*WVq>S8V|>XrrB zS1@4R@?}wbNew${Xhl3hp;6CrY}_>V>?|mGLZ@2Bw#6ZX^jJB*Qj6As{x%u%U^eYj zUw@kR#+XEG&Z5vNY}|9HnOP_cIT>b9wTrhaWWW;=(&}|k%C)h@_&B46C}pGQXAgN* zeAT55Y`U&r24sKnyFRNM8m~)B@CS^8?2r+!Raud6WXa%+k=YULkax!a2kv7zo<+0n z^#z^QJL;BKklvMzG8ne_TchRHbaoPtyj zb!5>6jut24WT{4{nbO+PpIxpP)mZS>%2i6pN29W~qFr3h`yo*=4sl=PqqR=YV~TGb z`t`jsHfU?_-HP)IGzm+?6}wnx%7|L>uRU1{f7$V*q1p2%+YRLGE9 zSjXN=s%MJ4vmZvECYt?jpVB!Qdemu8ZPcNQteWvrW)@R;5PW*{Y6#kBK%l?@0{gN}?*s6g*3&)FLhsHR^wcA5{|SNIO2 z`QI-pViQIX;dJV=#FqM7Y_g;(xG3QfC^4|jy9xXoEaq$TCGp5Q%PV?^QlO*Yj2`I9 zl3?BL#74bwjeBObSK*aAZ$oz z8wel+8U<3$XYekeHSoKI3o~uw#xc=`bg!w(Kl(@qc&}sE1?d7WalX5&8w|O<<@+H> z3O`543bYotds&*wVEM5EtK-d^}$GK_`hyAeKJPk}0*bF6Vk}B3FdgT%L$Q~Rmk5<{B zK;;QX#z+vY5Y*+raclFH*8~owB*|1O(5%_it_B8LxRso&Oj$^YvA5YTj1%j;c%lZb2`rj|lptj*)L?UL28+1}{UxL3NSIi4#I(=bW zE%F)E>pgX}wzl$MvD>tlyN-r1rx6wUT%VywyNhQglEwd_j9NZKAxe$RpaTlyL@dv2 zj+4C2qld~QZ?BAx_r+Zae#_H2v@Rr|LQ^bWil5h^(XnCt6Kn5}p+M2WDEU;4_A@~~ zJS&D=^Wk;%pdJyyQv>B;`@8q)O%cI3M9!btg6b%pCl#DL7SA1UP^;!EYLqA_q+M}o zK8v&Ke;zDf&h!83lw`U=sJq=!uwIQ9_8Aw$WK{Cm>o&TA2aZM-^q}4f+KOzoexWZh z^W8YRcgTbJV*CHrZ@{$1vRM+6l7yi7f~NRRg|0$A8>+}ym#Pg{)~MRnkGJuzn%34JZ6z!#5wJgUxF7IOGN2}Q>E9xdSWcG^L|<%3-pH=qg2?R{;< z(7cGQ#R+`$vA0@0HpPg~<{>v=jA7y54GS|BnHCwyh_a4z!6};^{ra-icfsxb`dxj; zd_m`mMBtzdU1bHW&PQL|7gLteP-Fr{b~Gd92uPt6_CKM8+H!8yzTxPkrbo zjZ#wktkz(2X`lDK$gh$@C+)WNmd#FJK=0cAz|-Sy-a-!!oW&`i1ueAYcU7Al_E)2l zNRpwPm$2e9q9~z0y=L-F-^JeZ71%R#~fzM68c< zMWTKGdx&+-{rF@sd9N30CRmz5?o<@!^4~2qqtebR^cZQjD%rns_8${6pl|MTP2R?~Wu@Bg9 zP_0>a93dY$0QJbn@LQPB*<^6jr3Of&LhNf)f^NUM~PgRqK-JWe9e$& zDMtBig{X&@(no$C`(kGZ*{Qv-6YIf7Z!r(DzG0q(B4!*k6V-AXoWgm*d2@f_H}lS% zFV5D*b3Wz?h!jiGZ1tW-%+}4#hZSR3a#xD&B0Z0D!8ITH+8B*gtRS&5#%J~CRWXOj z7+0;IwotP3w~O_kr9Xe>A0cC2~_2RWY+Lz*d#Qo$$9YOz+-QZKbfk)xaScO$1-puLWIX)Dl(qJ4(`>Nr6g z(mZMbub{_E(~Wl2&(PmXx)y3{qsRE<)S?!>q-hrgSJq*?Zl3iE*BJvUY=Foo)Jasb=8m$k|vU=(-37}-8~QBY^Z#e>ieieZa)=PP$KO7%z@vTS^18|!A3wMzWJ zKpJgYE@&5a@cKAp{`DSPVZy;XAVrU~3>A0ss0{@ah0;Eb-&crl9R9(mBXmtE#>#_; zj=XVR^o18a6&?tHR~#9AV4deIUS6WnKaK&!glOmv^s?baTTDbHo9;B;ciQt&-Y2y9 z^geOyDj!zxi(?I%9dope3JM@*c--Nm4RRfyzP~!;Va(jKfHjYP2EsHj#F?OcuNe zK6-1Cc{Rlx7aJvOkd+*G)ZP~@T>4Vh?pYWuOdO38*h3>NDZ;|i+ml3Hv?oow(2^G7 z$GKk{L6dgt1(XcZ49dcz=o~c5YHkgXafm7Di22s6E z+S%L1D9)$>9sgM_MLQc}X{$I-`>nl`#<;B8m!L_bNxd$uQKxoBXW54F88k#0v7f$O zwEjh{mC_4#z=`-L;zgbh*`ESGGtN`YS5Ftbt{95Kc?^Ebck(j&Kq!NwpyaV|R4WDh zINR(uD#YmD1;_A>d+@e9S;o-n7`#pdOFJ6GgtZzCl=3Ts7rhgVz>MCI>hFYm4f3%_ zZlEeXC*}QRTMuJ=BZ?6#t)%L4;b7Dm4V+jCdQkKiINHCeFaRn;XqR%rA1 zYh^CmqHe}d>S60c)N%abrGNGTiT{5{490%_tk;KhCB5i%ep=vVukYY3%3pZ>v2;1j zy}k>XJ6^wnw{Cyq^~cla)4zKCiS$(ZFRwqD{x|)?>z_(*Uivq$e>#2h(l5RKne->a zHLpLFt`2|Y_0Od*4}WiUMEvpLKY-)X4!;Ti>hPalzm&c>{Ln@4UVXnSb#5mGq;_|L*n2)AKt!UVkEeVdq7!KY8gFJE7-O=^uAL z@BB}vzuo;8uYV@JedYJP{#5$wD-XQ>x%B-jzpy%d`JKnV@W$cM2ge7u@0`wV9)ECh zx_|Gz{d>prg9o#hUc7ew?ZdnObpP&9na@?pP%hN zxHWqNLA`hH;O6Y);nC6I@#)ps_ul;R?DZpX8QHJBI{WV7@qBjk&i?WKO{jtYw{FdE zeSJ26{|Mt@c&g#Gy`yie4nr3e_;9j!a_8{9y;rWk^vbnY_KptkzJnQjXKpk2&e8GV znMw|iUB~rTUjFJ=UfEj=^Bc$W{nLY=&1Y{M-iPLs+3TmL#|JkaGJdlkz@Pc-^?L_* z=QM*EyXoSXE=pfLJifh$xXd4%%un`ieDK1_{@(SgFYbMt8gCrjp8fRU{>{4w4{pzX zK0mti0pjwLL(JX%4=|DM&u+|by>a{SJMaGJ&Iz7ZAEu-9K{`$c={EL)(=@e5x{kXR=@^1RSbRU%bH&L5oB*)xB`)$+@)Azx zkM?$Bwv(DobYDwXfoyT?3lmf?II3?8^J)44d^`raoAUbrKF@%2Chk7Gn8PRHz6;)A zdKc$aBd=ooAtV{SQ$+J5y^3!T?m?cBIaMUXO2}@A@$3r|FV@6%_AKylc8K~0yf{FV zIe+h=b_z7B(LR~J1;0)--w!lnGkJQVc|QRDQ}hw}tI+To+TTb>j?a;n57%PQc+j zlXncd82KsY=OHoTdQ+%RW!-Y;A#^Yk=jeHmDE$^VbNsuj7P++chUe-%_<8`uGvIiL zaYsUNiX51w?_t(|4Ej2_%6=9Tp6)CUu-5$pz#FS4iUq>^a@trOE_Ocp(U=vcYGbb1HAn@d`B@j z{0NY6mKSkchy9n+SJPL7V?#z`);O2@%B-KE#(Ct5vAu&ae;s{XiwDZ&$iNv^sLc?^ zyskOFi}A72Y@N|^uiaK-=)S7izKuv(#>|mP%w76&1KIQfYWt{P*Z#4G6@HKm;| Date: Mon, 21 Oct 2024 02:29:21 -0700 Subject: [PATCH 72/75] Replace duplicate code with `getDoubleBattleChance()` (#4690) --- src/battle-scene.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 096a84dde3d..a21e1b09342 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1191,10 +1191,7 @@ export default class BattleScene extends SceneBase { if (trainerConfigs[trainerType].doubleOnly) { doubleTrainer = true; } else if (trainerConfigs[trainerType].hasDouble) { - const doubleChance = new Utils.IntegerHolder(newWaveIndex % 10 === 0 ? 32 : 8); - this.applyModifiers(DoubleBattleChanceBoosterModifier, true, doubleChance); - playerField.forEach(p => applyAbAttrs(DoubleBattleChanceAbAttr, p, null, false, doubleChance)); - doubleTrainer = !Utils.randSeedInt(doubleChance.value); + doubleTrainer = !Utils.randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField)); // Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance if (trainerConfigs[trainerType].trainerTypeDouble && ![ TrainerType.TATE, TrainerType.LIZA ].includes(trainerType)) { doubleTrainer = false; From f51814467a3c32a1f0653b57ae1cd7e734c58ed2 Mon Sep 17 00:00:00 2001 From: innerthunder <168692175+innerthunder@users.noreply.github.com> Date: Mon, 21 Oct 2024 07:59:23 -0700 Subject: [PATCH 73/75] [P3] Fix mistimed sound effect in `LearnMovePhase` (#4698) --- src/phases/learn-move-phase.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index eb7cfbb65ef..fefda384092 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -170,13 +170,16 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { pokemon.setMove(index, this.moveId); initMoveAnim(this.scene, this.moveId).then(() => { loadMoveAnimAssets(this.scene, [ this.moveId ], true); - this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is }); this.scene.ui.setMode(this.messageMode); const learnMoveText = i18next.t("battle:learnMove", { pokemonName: getPokemonNameWithAffix(pokemon), moveName: move.name }); - textMessage = textMessage ? textMessage + "$" + learnMoveText : learnMoveText; - await this.scene.ui.showTextPromise(textMessage, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); - this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); - this.end(); + if (textMessage) { + await this.scene.ui.showTextPromise(textMessage); + } + this.scene.playSound("level_up_fanfare"); // Sound loaded into game as is + this.scene.ui.showText(learnMoveText, null, () => { + this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeMoveLearnedTrigger, true); + this.end(); + }, this.messageMode === Mode.EVOLUTION_SCENE ? 1000 : undefined, true); } } From 966b07f62b3e2ca71719357918b681ddbbaf4f66 Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:00:58 -0700 Subject: [PATCH 74/75] [Misc] Shiny overrides can now force Pokemon to not be shiny (#4699) Also fixes random shinies breaking tests --- src/field/pokemon.ts | 15 +++++-- src/overrides.ts | 8 ++-- src/test/utils/helpers/challengeModeHelper.ts | 6 ++- src/test/utils/helpers/classicModeHelper.ts | 10 +++-- src/test/utils/helpers/dailyModeHelper.ts | 6 ++- src/test/utils/helpers/overridesHelper.ts | 42 ++++++++++++++++--- 6 files changed, 69 insertions(+), 18 deletions(-) diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 7f2b4ec015d..0ee879ebf97 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3982,10 +3982,14 @@ export class PlayerPokemon extends Pokemon { if (Overrides.SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); - if (Overrides.VARIANT_OVERRIDE) { - this.variant = Overrides.VARIANT_OVERRIDE; - } + } else if (Overrides.SHINY_OVERRIDE === false) { + this.shiny = false; } + + if (Overrides.VARIANT_OVERRIDE !== null && this.shiny) { + this.variant = Overrides.VARIANT_OVERRIDE; + } + if (!dataSource) { if (this.scene.gameMode.isDaily) { this.generateAndPopulateMoveset(); @@ -4474,10 +4478,13 @@ export class EnemyPokemon extends Pokemon { if (Overrides.OPP_SHINY_OVERRIDE) { this.shiny = true; this.initShinySparkle(); + } else if (Overrides.OPP_SHINY_OVERRIDE === false) { + this.shiny = false; } + if (this.shiny) { this.variant = this.generateVariant(); - if (Overrides.OPP_VARIANT_OVERRIDE) { + if (Overrides.OPP_VARIANT_OVERRIDE !== null) { this.variant = Overrides.OPP_VARIANT_OVERRIDE; } } diff --git a/src/overrides.ts b/src/overrides.ts index 211d430a835..e1bfbd240f0 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -113,8 +113,8 @@ class DefaultOverrides { readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly GENDER_OVERRIDE: Gender | null = null; readonly MOVESET_OVERRIDE: Moves | Array = []; - readonly SHINY_OVERRIDE: boolean = false; - readonly VARIANT_OVERRIDE: Variant = 0; + readonly SHINY_OVERRIDE: boolean | null = null; + readonly VARIANT_OVERRIDE: Variant | null = null; // -------------------------- // OPPONENT / ENEMY OVERRIDES @@ -134,8 +134,8 @@ class DefaultOverrides { readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE; readonly OPP_GENDER_OVERRIDE: Gender | null = null; readonly OPP_MOVESET_OVERRIDE: Moves | Array = []; - readonly OPP_SHINY_OVERRIDE: boolean = false; - readonly OPP_VARIANT_OVERRIDE: Variant = 0; + readonly OPP_SHINY_OVERRIDE: boolean | null = null; + readonly OPP_VARIANT_OVERRIDE: Variant | null = null; readonly OPP_IVS_OVERRIDE: number | number[] = []; readonly OPP_FORM_OVERRIDES: Partial> = {}; /** diff --git a/src/test/utils/helpers/challengeModeHelper.ts b/src/test/utils/helpers/challengeModeHelper.ts index 184f11f505c..5210d942d5a 100644 --- a/src/test/utils/helpers/challengeModeHelper.ts +++ b/src/test/utils/helpers/challengeModeHelper.ts @@ -38,6 +38,10 @@ export class ChallengeModeHelper extends GameManagerHelper { async runToSummon(species?: Species[]) { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode.challenges = this.challenges; const starters = generateStarter(this.game.scene, species); @@ -47,7 +51,7 @@ export class ChallengeModeHelper extends GameManagerHelper { }); await this.game.phaseInterceptor.run(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } diff --git a/src/test/utils/helpers/classicModeHelper.ts b/src/test/utils/helpers/classicModeHelper.ts index 55e995fc9dc..80d0b86de7b 100644 --- a/src/test/utils/helpers/classicModeHelper.ts +++ b/src/test/utils/helpers/classicModeHelper.ts @@ -20,9 +20,13 @@ export class ClassicModeHelper extends GameManagerHelper { * @param species - Optional array of species to summon. * @returns A promise that resolves when the summon phase is reached. */ - async runToSummon(species?: Species[]) { + async runToSummon(species?: Species[]): Promise { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { this.game.scene.gameMode = getGameMode(GameModes.CLASSIC); const starters = generateStarter(this.game.scene, species); @@ -32,7 +36,7 @@ export class ClassicModeHelper extends GameManagerHelper { }); await this.game.phaseInterceptor.run(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } @@ -42,7 +46,7 @@ export class ClassicModeHelper extends GameManagerHelper { * @param species - Optional array of species to start the battle with. * @returns A promise that resolves when the battle is started. */ - async startBattle(species?: Species[]) { + async startBattle(species?: Species[]): Promise { await this.runToSummon(species); if (this.game.scene.battleStyle === BattleStyle.SWITCH) { diff --git a/src/test/utils/helpers/dailyModeHelper.ts b/src/test/utils/helpers/dailyModeHelper.ts index e40fada8ac7..813544f85df 100644 --- a/src/test/utils/helpers/dailyModeHelper.ts +++ b/src/test/utils/helpers/dailyModeHelper.ts @@ -21,6 +21,10 @@ export class DailyModeHelper extends GameManagerHelper { async runToSummon() { await this.game.runToTitle(); + if (this.game.override.disableShinies) { + this.game.override.shiny(false).enemyShiny(false); + } + this.game.onNextPrompt("TitlePhase", Mode.TITLE, () => { const titlePhase = new TitlePhase(this.game.scene); titlePhase.initDailyRun(); @@ -33,7 +37,7 @@ export class DailyModeHelper extends GameManagerHelper { await this.game.phaseInterceptor.to(EncounterPhase); - if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0) { + if (overrides.OPP_HELD_ITEMS_OVERRIDE.length === 0 && this.game.override.removeEnemyStartingItems) { this.game.removeEnemyHeldItems(); } } diff --git a/src/test/utils/helpers/overridesHelper.ts b/src/test/utils/helpers/overridesHelper.ts index 27fd9552fe4..ec4d8dbbe4c 100644 --- a/src/test/utils/helpers/overridesHelper.ts +++ b/src/test/utils/helpers/overridesHelper.ts @@ -19,6 +19,11 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; * Helper to handle overrides in tests */ export class OverridesHelper extends GameManagerHelper { + /** If `true`, removes the starting items from enemies at the start of each test; default `true` */ + public removeEnemyStartingItems: boolean = true; + /** If `true`, sets the shiny overrides to disable shinies at the start of each test; default `true` */ + public disableShinies: boolean = true; + /** * Override the starting biome * @warning Any event listeners that are attached to [NewArenaEvent](events\battle-scene.ts) may need to be handled down the line @@ -368,23 +373,50 @@ export class OverridesHelper extends GameManagerHelper { /** * Override player shininess - * @param shininess Whether the player's Pokemon should be shiny. + * @param shininess - `true` or `false` to force the player's pokemon to be shiny or not shiny, + * `null` to disable the override and re-enable RNG shinies. */ - shinyLevel(shininess: boolean): this { + shiny(shininess: boolean | null): this { vi.spyOn(Overrides, "SHINY_OVERRIDE", "get").mockReturnValue(shininess); - this.log(`Set player Pokemon as ${shininess ? "" : "not "}shiny!`); + if (shininess === null) { + this.log("Disabled player Pokemon shiny override!"); + } else { + this.log(`Set player Pokemon to be ${shininess ? "" : "not "}shiny!`); + } return this; } + /** * Override player shiny variant - * @param variant The player's shiny variant. + * @param variant - The player's shiny variant. */ - variantLevel(variant: Variant): this { + shinyVariant(variant: Variant): this { vi.spyOn(Overrides, "VARIANT_OVERRIDE", "get").mockReturnValue(variant); this.log(`Set player Pokemon's shiny variant to ${variant}!`); return this; } + /** + * Override enemy shininess + * @param shininess - `true` or `false` to force the enemy's pokemon to be shiny or not shiny, + * `null` to disable the override and re-enable RNG shinies. + * @param variant - (Optional) The enemy's shiny {@linkcode Variant}. + */ + enemyShiny(shininess: boolean | null, variant?: Variant): this { + vi.spyOn(Overrides, "OPP_SHINY_OVERRIDE", "get").mockReturnValue(shininess); + if (shininess === null) { + this.log("Disabled enemy Pokemon shiny override!"); + } else { + this.log(`Set enemy Pokemon to be ${shininess ? "" : "not "}shiny!`); + } + + if (variant !== undefined) { + vi.spyOn(Overrides, "OPP_VARIANT_OVERRIDE", "get").mockReturnValue(variant); + this.log(`Set enemy shiny variant to be ${variant}!`); + } + return this; + } + /** * Override the enemy (Pokemon) to have the given amount of health segments * @param healthSegments the number of segments to give From f7797603a1511177b36c57a8c9c7d413c2fb22aa Mon Sep 17 00:00:00 2001 From: NightKev <34855794+DayKev@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:03:12 -0700 Subject: [PATCH 75/75] [P2] Fix oversight where hazards cannnot affect Pokemon that set it (#4693) Fixes #935 --- src/data/arena-tag.ts | 2 +- src/test/moves/toxic_spikes.test.ts | 55 +++++++++++++++-------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 6507d34dcfd..ed4c2789165 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -626,7 +626,7 @@ export class ArenaTrapTag extends ArenaTag { * @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()) { + if ((this.side === ArenaTagSide.PLAYER) !== pokemon.isPlayer()) { return false; } diff --git a/src/test/moves/toxic_spikes.test.ts b/src/test/moves/toxic_spikes.test.ts index bac1ccdccd8..a5c63a2652f 100644 --- a/src/test/moves/toxic_spikes.test.ts +++ b/src/test/moves/toxic_spikes.test.ts @@ -9,8 +9,6 @@ import GameManager from "#test/utils/gameManager"; import Phaser from "phaser"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; -const TIMEOUT = 20 * 1000; - describe("Moves - Toxic Spikes", () => { let phaserGame: Phaser.Game; let game: GameManager; @@ -34,10 +32,10 @@ describe("Moves - Toxic Spikes", () => { .enemyAbility(Abilities.BALL_FETCH) .ability(Abilities.BALL_FETCH) .enemyMoveset(Moves.SPLASH) - .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]); + .moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR, Moves.COURT_CHANGE ]); }); - it("should not affect the opponent if they do not switch", async() => { + it("should not affect the opponent if they do not switch", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); const enemy = game.scene.getEnemyField()[0]; @@ -51,9 +49,9 @@ describe("Moves - Toxic Spikes", () => { expect(enemy.hp).toBe(enemy.getMaxHp()); expect(enemy.status?.effect).toBeUndefined(); - }, TIMEOUT); + }); - it("should poison the opponent if they switch into 1 layer", async() => { + it("should poison the opponent if they switch into 1 layer", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA ]); game.move.select(Moves.TOXIC_SPIKES); @@ -65,9 +63,9 @@ describe("Moves - Toxic Spikes", () => { expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); expect(enemy.status?.effect).toBe(StatusEffect.POISON); - }, TIMEOUT); + }); - it("should badly poison the opponent if they switch into 2 layers", async() => { + it("should badly poison the opponent if they switch into 2 layers", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA ]); game.move.select(Moves.TOXIC_SPIKES); @@ -80,27 +78,32 @@ describe("Moves - Toxic Spikes", () => { const enemy = game.scene.getEnemyField()[0]; expect(enemy.hp).toBeLessThan(enemy.getMaxHp()); expect(enemy.status?.effect).toBe(StatusEffect.TOXIC); - }, TIMEOUT); + }); - it("should be removed if a grounded poison pokemon switches in", async() => { - game.override.enemySpecies(Species.GRIMER); - await game.classicMode.runToSummon([ Species.MIGHTYENA ]); + it("should be removed if a grounded poison pokemon switches in", async () => { + await game.classicMode.runToSummon([ Species.MUK, Species.PIDGEY ]); + + const muk = game.scene.getPlayerPokemon()!; game.move.select(Moves.TOXIC_SPIKES); - await game.phaseInterceptor.to("TurnEndPhase"); - game.move.select(Moves.TOXIC_SPIKES); - await game.phaseInterceptor.to("TurnEndPhase"); - game.move.select(Moves.ROAR); - await game.phaseInterceptor.to("TurnEndPhase"); - - const enemy = game.scene.getEnemyField()[0]; - expect(enemy.hp).toBe(enemy.getMaxHp()); - expect(enemy.status?.effect).toBeUndefined(); + await game.toNextTurn(); + // also make sure the toxic spikes are removed even if the pokemon + // that set them up is the one switching in (https://github.com/pagefaultgames/pokerogue/issues/935) + game.move.select(Moves.COURT_CHANGE); + await game.toNextTurn(); + game.doSwitchPokemon(1); + await game.toNextTurn(); + game.doSwitchPokemon(1); + await game.toNextTurn(); + game.move.select(Moves.SPLASH); + await game.toNextTurn(); + expect(muk.isFullHp()).toBe(true); + expect(muk.status?.effect).toBeUndefined(); expect(game.scene.arena.tags.length).toBe(0); - }, TIMEOUT); + }); - it("shouldn't create multiple layers per use in doubles", async() => { + it("shouldn't create multiple layers per use in doubles", async () => { await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]); game.move.select(Moves.TOXIC_SPIKES); @@ -109,9 +112,9 @@ describe("Moves - Toxic Spikes", () => { const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag); expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES); expect(arenaTags.layers).toBe(1); - }, TIMEOUT); + }); - it("should persist through reload", async() => { + it("should persist through reload", async () => { game.override.startingWave(1); const scene = game.scene; const gameData = new GameData(scene); @@ -132,5 +135,5 @@ describe("Moves - Toxic Spikes", () => { expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags); localStorage.removeItem("sessionTestData"); - }, TIMEOUT); + }); });

v^J8bg(f%?PGJxL)) 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 literal 10686 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%0Dy!50Qvv` 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 diff --git a/public/images/pokemon/variant/6706_3.json b/public/images/pokemon/variant/6706_3.json deleted file mode 100644 index 615ca90e004..00000000000 --- a/public/images/pokemon/variant/6706_3.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 82, - "h": 82 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 82, - "h": 72 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 82, - "h": 72 - }, - "frame": { - "x": 0, - "y": 0, - "w": 82, - "h": 72 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:02eb46aa66ac70df612e129b7801a85c:a77cca14b23f4f3aece64d1a82449a0f:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/6706_3.png b/public/images/pokemon/variant/6706_3.png deleted file mode 100644 index 001cab641f1c1c4009d6264baa14114a1a1b140c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5177 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%0Dy!50Qvv` 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%0Dy!50Qvv` z0D$NK0Cg|`0P0`>06Lfe02gqax=}m;000SaNLh0L01FZT01FZU(%pXi0000RbVXQn zQ*UN;cVTj607GSLb9r+hQ*?D?X>TA@Z*OeDr{R1600*W?L_t(|obB9iXjOF_$MN?K zD=OR8kcC>aS;8JjB}ud_gUaHL{!tVo3R^vppmISyh*C<89u#OM#2%E11YYTsvf;vLAetUqL~Fcd^Ycz6(bPl%hM_?`I5oZNc7y_2;Z+k(QY;oY zaBR2ljr7dxa)Qvn3|IVNg6-RPyVD!qe8#ukc@Hk*?+*@y0!HH10-Qt#)Zrf8|C(q0 z$Ij`byE_j++qFwPXzF&ug;Btd7zG%Lmo=PvNfMQyv$p`MaDsMx`h@2V_RQ;Y1P|br zcb;bFiWdT3bGR^StisrMP0bC?t?kbRzL0I=fCWW|EEWrFSiTaNfz#2z40n9`M6Rj1 z!2tphg8_#2VCY#1Jx0TbahH8(g*oA0HiWeJm$6V@NODut$|rDcf` zFAf}JjCX7Mb1q!azmN>9RGRY5yXc~Mv44)l1Q-_&{D86Xr0V3C3ImKAK{G*xEwjnk z^q>or1P;7-iB1?Q9{2&1rc|1;g0WpskKiSRgJ?Vx9E9Lyk+>j1>n}fk3V`pL&NXhg z{P-!7z?r%)K~=Y~q0x7toO2w9rsf9crLJAP|KY2tjzMaYRFY{M9Y+5HCxKI2Q)-3= zMp(0PwP)V?wOi@#&NDPH;^Ij^(1Qo?(NM4Tn+;0RT~Ob4Y zNzGSpKlL*?<=6gM7TvLMJz>lX!nxYaFZp&0(E>pa$;Ur_z zgT8s{u`diS3=X34)MMcKwOaw)GjCnsMI{I?3QiIQc;v*N0RHQ|tnkuamZUjWn(%Dl zB%gfL&0Y6x_d$kslBTv0?*l2PbPTPf`fEbcWC@506&hu=;AeA_zD242737U#s2>fo-N#=@uU2F z`OWV1$vrFG-S+DCS6MaCL&u`~dH90X@LP}nH@rQiD~`h(t^@E-+ozu80;lJ! z$Fqe4*qop1+HgoQwUr4TR30!%)xjCOw|hV4_UjkA>wW(15uV-sSN!pLYD)@gAI}yJ%zZvN z?#b>en3>=~ZST5c(}SKtJYflHlByL5#D;?m@t`t*W7C7|T=4>}?F%B;4eKtecuC-( z&hX;EL3C?=bAGOq6b`hzXSEDiDowFr`ATazm}%XcZW(5k%<&+5z*1?7_a5nry#W!lI4{$PzIY9@IYI(7*_HJaD6LzUi(Qq(clG&|S}fGBm?x zctzAxK4D3O2el0-t@gwH+kj}qpz(K634IPD3J~lNFFidoT;lgRcH{rj;Hmd0HuZJ7 z-4}p`4UNWKI__XdN$3+maR9-58FK0AL9Up8o;&@`A9>F*Fry{~G~vOS4l=;aZyob& zlLQVj!jqT~gYd#oAs(KbBviPKKW*~dd(b<`Nt&N}A|Kp~3n&rD#pmzO3@_+R3Nt5( z%N}~lomQ(3ye+7-ttg4rBcn+H0Sdc1U8G-+{msFXD}Z2BiHf(UbR~!Tw?#HT1yHX# ziFA-FiU-L7b-a$wUB>x>D!JuUqoG^jVK55k!a&sl&m{#jg4|XBc25;h+q7YE(!F#|(l4 z^Oq^;h=<0H27XyXxl&dehVPBA-LfGI{y6T_?@q#KS z46qt-P-Q&uqO`Uz2t4Qq;Z+L`Y7h_3+=B?)b%zV1y!Y>}s=+}G;K7*_i9P~KtwJ5I zniVIh2|S5@Hdb*=6`J(kLsqFNys((?!2U_^a-?qXAb9tN>QrG};Ymyv+q0EdXPL!I oky*SHnZ-+yS-cdP#Y>U@0GLZqmFWb_C;$Ke07*qoM6N<$g0>MU1ONa4 diff --git a/public/images/pokemon/variant/back/6706_3.json b/public/images/pokemon/variant/back/6706_3.json deleted file mode 100644 index 9a97ce27059..00000000000 --- a/public/images/pokemon/variant/back/6706_3.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 79, - "h": 79 - }, - "scale": 1, - "frames": [ - { - "filename": "0001.png", - "rotated": false, - "trimmed": false, - "sourceSize": { - "w": 79, - "h": 73 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 73 - }, - "frame": { - "x": 0, - "y": 0, - "w": 79, - "h": 73 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:64f7e6dfa489012922487e45ba53d557:4d24652b372939abe499497c4b6647b0:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/back/6706_3.png b/public/images/pokemon/variant/back/6706_3.png deleted file mode 100644 index fb780d01499a23a4f4ab714732d1d56d7e094f2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4765 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%0Dy!50Qvv` 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 diff --git a/public/images/pokemon/variant/exp/6706.json b/public/images/pokemon/variant/exp/6706.json new file mode 100644 index 00000000000..2a5f8e112ee --- /dev/null +++ b/public/images/pokemon/variant/exp/6706.json @@ -0,0 +1,40 @@ +{ + "1": { + "566678": "0e6296", + "e0e4f4": "513981", + "625287": "4e4094", + "536273": "1f1233", + "988b98": "b24c86", + "36404c": "0c5474", + "c4cce1": "3b235c", + "e6d3e9": "f1a4c5", + "bfacc1": "da75a5", + "515f70": "197497", + "c5cee3": "63cee1", + "b791f2": "c7a1e5", + "8b93a6": "3aa8c4", + "80737f": "8a2166", + "4b454f": "6f1357", + "9170b9": "8b69c3", + "8e96aa": "301848" + }, + "2": { + "566678": "8e480b", + "e0e4f4": "176463", + "625287": "274159", + "536273": "02262c", + "988b98": "2a6563", + "36404c": "842401", + "c4cce1": "0d484a", + "e6d3e9": "9cead8", + "bfacc1": "5db6a9", + "515f70": "a34205", + "c5cee3": "f7af58", + "b791f2": "4a9699", + "8b93a6": "d27e26", + "80737f": "2b736f", + "4b454f": "194f51", + "9170b9": "2f667c", + "8e96aa": "073338" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/6706_2.json b/public/images/pokemon/variant/exp/6706_2.json deleted file mode 100644 index cb2ddfb1a12..00000000000 --- a/public/images/pokemon/variant/exp/6706_2.json +++ /dev/null @@ -1,2015 +0,0 @@ -{ - "textures": [ - { - "image": "6706_2.png", - "format": "RGBA8888", - "size": { - "w": 508, - "h": 508 - }, - "scale": 1, - "frames": [ - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 26, - "w": 59, - "h": 61 - }, - "frame": { - "x": 0, - "y": 0, - "w": 59, - "h": 61 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 18, - "w": 87, - "h": 71 - }, - "frame": { - "x": 405, - "y": 68, - "w": 87, - "h": 71 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 19, - "w": 86, - "h": 72 - }, - "frame": { - "x": 0, - "y": 131, - "w": 86, - "h": 72 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 72 - }, - "frame": { - "x": 0, - "y": 203, - "w": 85, - "h": 72 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 13, - "w": 83, - "h": 72 - }, - "frame": { - "x": 85, - "y": 206, - "w": 83, - "h": 72 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 18, - "w": 84, - "h": 73 - }, - "frame": { - "x": 168, - "y": 206, - "w": 84, - "h": 73 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 252, - "y": 210, - "w": 85, - "h": 73 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 85, - "h": 75 - }, - "frame": { - "x": 157, - "y": 428, - "w": 85, - "h": 75 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:62a4a665074efb5def1545546995dc5b:de2788ebeab6b42f331926f332da5125:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/6706_2.png b/public/images/pokemon/variant/exp/6706_2.png deleted file mode 100644 index 7e7dfa8e05a910d741fea8e3952947d1e304738f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55978 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 diff --git a/public/images/pokemon/variant/exp/6706_3.json b/public/images/pokemon/variant/exp/6706_3.json deleted file mode 100644 index 8c9b16b80ab..00000000000 --- a/public/images/pokemon/variant/exp/6706_3.json +++ /dev/null @@ -1,2015 +0,0 @@ -{ - "textures": [ - { - "image": "6706_3.png", - "format": "RGBA8888", - "size": { - "w": 508, - "h": 508 - }, - "scale": 1, - "frames": [ - { - "filename": "0074.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 26, - "w": 59, - "h": 61 - }, - "frame": { - "x": 0, - "y": 0, - "w": 59, - "h": 61 - } - }, - { - "filename": "0073.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0075.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 26, - "y": 25, - "w": 56, - "h": 63 - }, - "frame": { - "x": 59, - "y": 0, - "w": 56, - "h": 63 - } - }, - { - "filename": "0064.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0084.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 115, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0065.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0083.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 29, - "y": 21, - "w": 54, - "h": 65 - }, - "frame": { - "x": 168, - "y": 0, - "w": 54, - "h": 65 - } - }, - { - "filename": "0066.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0082.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 28, - "y": 21, - "w": 53, - "h": 65 - }, - "frame": { - "x": 222, - "y": 0, - "w": 53, - "h": 65 - } - }, - { - "filename": "0067.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0081.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 55, - "h": 66 - }, - "frame": { - "x": 275, - "y": 0, - "w": 55, - "h": 66 - } - }, - { - "filename": "0072.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0076.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 23, - "w": 54, - "h": 66 - }, - "frame": { - "x": 330, - "y": 0, - "w": 54, - "h": 66 - } - }, - { - "filename": "0063.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0085.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 25, - "y": 21, - "w": 54, - "h": 67 - }, - "frame": { - "x": 384, - "y": 0, - "w": 54, - "h": 67 - } - }, - { - "filename": "0062.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0086.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 23, - "y": 21, - "w": 52, - "h": 68 - }, - "frame": { - "x": 438, - "y": 0, - "w": 52, - "h": 68 - } - }, - { - "filename": "0068.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0080.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 68 - }, - "frame": { - "x": 0, - "y": 61, - "w": 53, - "h": 68 - } - }, - { - "filename": "0071.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0077.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 18, - "y": 22, - "w": 52, - "h": 68 - }, - "frame": { - "x": 53, - "y": 63, - "w": 52, - "h": 68 - } - }, - { - "filename": "0060.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0088.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 19, - "y": 21, - "w": 55, - "h": 69 - }, - "frame": { - "x": 105, - "y": 65, - "w": 55, - "h": 69 - } - }, - { - "filename": "0061.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0087.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 21, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 160, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0069.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0079.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 16, - "y": 21, - "w": 53, - "h": 69 - }, - "frame": { - "x": 213, - "y": 65, - "w": 53, - "h": 69 - } - }, - { - "filename": "0070.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0078.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 15, - "y": 21, - "w": 52, - "h": 70 - }, - "frame": { - "x": 266, - "y": 66, - "w": 52, - "h": 70 - } - }, - { - "filename": "0006.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0022.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0038.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 71 - }, - "frame": { - "x": 318, - "y": 67, - "w": 87, - "h": 71 - } - }, - { - "filename": "0051.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 18, - "w": 87, - "h": 71 - }, - "frame": { - "x": 405, - "y": 68, - "w": 87, - "h": 71 - } - }, - { - "filename": "0003.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 19, - "w": 86, - "h": 72 - }, - "frame": { - "x": 0, - "y": 131, - "w": 86, - "h": 72 - } - }, - { - "filename": "0004.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0020.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0036.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 86, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0005.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 19, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0021.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0037.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 173, - "y": 134, - "w": 87, - "h": 72 - } - }, - { - "filename": "0007.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0023.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0039.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 260, - "y": 138, - "w": 87, - "h": 72 - } - }, - { - "filename": "0008.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0024.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0040.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 18, - "w": 87, - "h": 72 - }, - "frame": { - "x": 347, - "y": 139, - "w": 87, - "h": 72 - } - }, - { - "filename": "0059.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0089.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 74, - "h": 80 - }, - "frame": { - "x": 434, - "y": 139, - "w": 74, - "h": 80 - } - }, - { - "filename": "0050.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 72 - }, - "frame": { - "x": 0, - "y": 203, - "w": 85, - "h": 72 - } - }, - { - "filename": "0052.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 13, - "w": 83, - "h": 72 - }, - "frame": { - "x": 85, - "y": 206, - "w": 83, - "h": 72 - } - }, - { - "filename": "0001.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 18, - "w": 84, - "h": 73 - }, - "frame": { - "x": 168, - "y": 206, - "w": 84, - "h": 73 - } - }, - { - "filename": "0002.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 252, - "y": 210, - "w": 85, - "h": 73 - } - }, - { - "filename": "0009.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0025.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0041.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 337, - "y": 211, - "w": 86, - "h": 73 - } - }, - { - "filename": "0018.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0034.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 85, - "h": 73 - }, - "frame": { - "x": 423, - "y": 219, - "w": 85, - "h": 73 - } - }, - { - "filename": "0011.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0027.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0043.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 1, - "y": 16, - "w": 85, - "h": 74 - }, - "frame": { - "x": 0, - "y": 275, - "w": 85, - "h": 74 - } - }, - { - "filename": "0010.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0026.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0042.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 85, - "y": 279, - "w": 86, - "h": 73 - } - }, - { - "filename": "0053.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0095.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 4, - "w": 81, - "h": 74 - }, - "frame": { - "x": 171, - "y": 279, - "w": 81, - "h": 74 - } - }, - { - "filename": "0017.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0033.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 3, - "y": 16, - "w": 84, - "h": 74 - }, - "frame": { - "x": 252, - "y": 283, - "w": 84, - "h": 74 - } - }, - { - "filename": "0019.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0035.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 17, - "w": 86, - "h": 73 - }, - "frame": { - "x": 336, - "y": 284, - "w": 86, - "h": 73 - } - }, - { - "filename": "0054.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0094.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 422, - "y": 292, - "w": 80, - "h": 74 - } - }, - { - "filename": "0055.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0093.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 0, - "w": 79, - "h": 74 - }, - "frame": { - "x": 0, - "y": 349, - "w": 79, - "h": 74 - } - }, - { - "filename": "0056.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0092.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 80, - "h": 74 - }, - "frame": { - "x": 79, - "y": 352, - "w": 80, - "h": 74 - } - }, - { - "filename": "0012.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0028.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0044.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 83, - "h": 75 - }, - "frame": { - "x": 159, - "y": 353, - "w": 83, - "h": 75 - } - }, - { - "filename": "0013.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0029.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0045.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 242, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0015.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0031.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0047.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 81, - "h": 75 - }, - "frame": { - "x": 323, - "y": 357, - "w": 81, - "h": 75 - } - }, - { - "filename": "0016.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0032.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0048.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 4, - "y": 15, - "w": 82, - "h": 75 - }, - "frame": { - "x": 404, - "y": 366, - "w": 82, - "h": 75 - } - }, - { - "filename": "0057.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0091.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 79, - "h": 75 - }, - "frame": { - "x": 0, - "y": 423, - "w": 79, - "h": 75 - } - }, - { - "filename": "0058.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0090.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 0, - "y": 0, - "w": 78, - "h": 75 - }, - "frame": { - "x": 79, - "y": 426, - "w": 78, - "h": 75 - } - }, - { - "filename": "0049.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 2, - "y": 15, - "w": 85, - "h": 75 - }, - "frame": { - "x": 157, - "y": 428, - "w": 85, - "h": 75 - } - }, - { - "filename": "0014.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0030.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - }, - { - "filename": "0046.png", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 88, - "h": 91 - }, - "spriteSourceSize": { - "x": 5, - "y": 14, - "w": 79, - "h": 76 - }, - "frame": { - "x": 242, - "y": 432, - "w": 79, - "h": 76 - } - } - ] - } - ], - "meta": { - "app": "https://www.codeandweb.com/texturepacker", - "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:62a4a665074efb5def1545546995dc5b:de2788ebeab6b42f331926f332da5125:d60cc2e5ae2bd18de8ee3ab0649593ee$" - } -} \ No newline at end of file diff --git a/public/images/pokemon/variant/exp/6706_3.png b/public/images/pokemon/variant/exp/6706_3.png deleted file mode 100644 index 3ad44f4bbf52818265762c20047ff43d445b9c85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56190 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