diff --git a/public/images/items.json b/public/images/items.json index 779823d1293..3c9cff7a35a 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 }, @@ -6583,7 +6583,7 @@ } }, { - "filename": "rb", + "filename": "pb_silver", "rotated": false, "trimmed": true, "sourceSize": { @@ -6666,6 +6666,27 @@ "h": 19 } }, + { + "filename": "rb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 20, + "h": 20 + }, + "frame": { + "x": 254, + "y": 320, + "w": 20, + "h": 20 + } + }, { "filename": "smooth_meteorite", "rotated": false, @@ -6680,27 +6701,6 @@ "w": 20, "h": 20 }, - "frame": { - "x": 254, - "y": 320, - "w": 20, - "h": 20 - } - }, - { - "filename": "strange_ball", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 6, - "w": 20, - "h": 20 - }, "frame": { "x": 274, "y": 325, @@ -6709,7 +6709,7 @@ } }, { - "filename": "ub", + "filename": "strange_ball", "rotated": false, "trimmed": true, "sourceSize": { @@ -6751,7 +6751,7 @@ } }, { - "filename": "apicot_berry", + "filename": "ub", "rotated": false, "trimmed": true, "sourceSize": { @@ -6761,13 +6761,13 @@ "spriteSourceSize": { "x": 6, "y": 6, - "w": 19, + "w": 20, "h": 20 }, "frame": { "x": 221, "y": 337, - "w": 19, + "w": 20, "h": 20 } }, @@ -6793,7 +6793,7 @@ } }, { - "filename": "big_mushroom", + "filename": "apicot_berry", "rotated": false, "trimmed": true, "sourceSize": { @@ -6804,13 +6804,13 @@ "x": 6, "y": 6, "w": 19, - "h": 19 + "h": 20 }, "frame": { "x": 343, "y": 287, "w": 19, - "h": 19 + "h": 20 } }, { @@ -6834,6 +6834,27 @@ "h": 20 } }, + { + "filename": "big_mushroom", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 19, + "h": 19 + }, + "frame": { + "x": 318, + "y": 306, + "w": 19, + "h": 19 + } + }, { "filename": "hard_stone", "rotated": false, @@ -6849,33 +6870,12 @@ "h": 20 }, "frame": { - "x": 318, - "y": 306, + "x": 314, + "y": 325, "w": 19, "h": 20 } }, - { - "filename": "miracle_seed", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 7, - "w": 19, - "h": 19 - }, - "frame": { - "x": 337, - "y": 306, - "w": 19, - "h": 19 - } - }, { "filename": "wl_ability_urge", "rotated": false, @@ -6891,12 +6891,33 @@ "h": 18 }, "frame": { - "x": 314, - "y": 326, + "x": 337, + "y": 307, "w": 20, "h": 18 } }, + { + "filename": "miracle_seed", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 7, + "w": 19, + "h": 19 + }, + "frame": { + "x": 333, + "y": 325, + "w": 19, + "h": 19 + } + }, { "filename": "wl_antidote", "rotated": false, @@ -6912,12 +6933,54 @@ "h": 18 }, "frame": { - "x": 356, + "x": 357, "y": 307, "w": 20, "h": 18 } }, + { + "filename": "wl_awakening", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 8, + "w": 20, + "h": 18 + }, + "frame": { + "x": 352, + "y": 325, + "w": 20, + "h": 18 + } + }, + { + "filename": "wl_burn_heal", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 8, + "w": 20, + "h": 18 + }, + "frame": { + "x": 241, + "y": 340, + "w": 20, + "h": 18 + } + }, { "filename": "golden_egg", "rotated": false, @@ -6933,33 +6996,12 @@ "h": 20 }, "frame": { - "x": 376, - "y": 307, + "x": 372, + "y": 325, "w": 17, "h": 20 } }, - { - "filename": "wl_awakening", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 8, - "w": 20, - "h": 18 - }, - "frame": { - "x": 393, - "y": 241, - "w": 20, - "h": 18 - } - }, { "filename": "toxic_orb", "rotated": false, @@ -6975,96 +7017,12 @@ "h": 18 }, "frame": { - "x": 413, - "y": 258, + "x": 377, + "y": 307, "w": 18, "h": 18 } }, - { - "filename": "wl_burn_heal", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 8, - "w": 20, - "h": 18 - }, - "frame": { - "x": 393, - "y": 259, - "w": 20, - "h": 18 - } - }, - { - "filename": "ampharosite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 377, - "y": 243, - "w": 16, - "h": 16 - } - }, - { - "filename": "audinite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 377, - "y": 259, - "w": 16, - "h": 16 - } - }, - { - "filename": "relic_gold", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 9, - "y": 11, - "w": 15, - "h": 11 - }, - "frame": { - "x": 377, - "y": 275, - "w": 15, - "h": 11 - } - }, { "filename": "lucky_egg", "rotated": false, @@ -7080,8 +7038,8 @@ "h": 20 }, "frame": { - "x": 381, - "y": 286, + "x": 389, + "y": 325, "w": 17, "h": 20 } @@ -7101,8 +7059,8 @@ "h": 18 }, "frame": { - "x": 398, - "y": 277, + "x": 352, + "y": 343, "w": 20, "h": 18 } @@ -7122,8 +7080,8 @@ "h": 18 }, "frame": { - "x": 398, - "y": 295, + "x": 372, + "y": 345, "w": 20, "h": 18 } @@ -7143,8 +7101,8 @@ "h": 18 }, "frame": { - "x": 393, - "y": 313, + "x": 392, + "y": 345, "w": 20, "h": 18 } @@ -7164,14 +7122,14 @@ "h": 18 }, "frame": { - "x": 240, - "y": 340, + "x": 378, + "y": 241, "w": 20, "h": 18 } }, { - "filename": "banettite", + "filename": "relic_gold", "rotated": false, "trimmed": true, "sourceSize": { @@ -7179,16 +7137,16 @@ "h": 32 }, "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 + "x": 9, + "y": 11, + "w": 15, + "h": 11 }, "frame": { - "x": 413, - "y": 313, - "w": 16, - "h": 16 + "x": 398, + "y": 241, + "w": 15, + "h": 11 } }, { @@ -7206,8 +7164,8 @@ "h": 18 }, "frame": { - "x": 202, - "y": 358, + "x": 377, + "y": 259, "w": 20, "h": 18 } @@ -7227,8 +7185,8 @@ "h": 18 }, "frame": { - "x": 201, - "y": 376, + "x": 381, + "y": 277, "w": 20, "h": 18 } @@ -7248,8 +7206,8 @@ "h": 18 }, "frame": { - "x": 201, - "y": 394, + "x": 397, + "y": 259, "w": 20, "h": 18 } @@ -7269,33 +7227,12 @@ "h": 18 }, "frame": { - "x": 201, - "y": 412, + "x": 401, + "y": 277, "w": 20, "h": 18 } }, - { - "filename": "beedrillite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 222, - "y": 357, - "w": 16, - "h": 16 - } - }, { "filename": "wl_ice_heal", "rotated": false, @@ -7311,14 +7248,14 @@ "h": 18 }, "frame": { - "x": 238, - "y": 358, + "x": 395, + "y": 295, "w": 20, "h": 18 } }, { - "filename": "blastoisinite", + "filename": "ampharosite", "rotated": false, "trimmed": true, "sourceSize": { @@ -7332,8 +7269,29 @@ "h": 16 }, "frame": { - "x": 222, - "y": 373, + "x": 415, + "y": 295, + "w": 16, + "h": 16 + } + }, + { + "filename": "audinite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 415, + "y": 311, "w": 16, "h": 16 } @@ -7353,12 +7311,33 @@ "h": 18 }, "frame": { - "x": 221, - "y": 389, + "x": 406, + "y": 327, "w": 20, "h": 18 } }, + { + "filename": "banettite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 412, + "y": 345, + "w": 16, + "h": 16 + } + }, { "filename": "wl_item_urge", "rotated": false, @@ -7375,7 +7354,7 @@ }, "frame": { "x": 221, - "y": 407, + "y": 357, "w": 20, "h": 18 } @@ -7396,7 +7375,7 @@ }, "frame": { "x": 241, - "y": 376, + "y": 358, "w": 20, "h": 18 } @@ -7416,8 +7395,8 @@ "h": 18 }, "frame": { - "x": 241, - "y": 394, + "x": 201, + "y": 360, "w": 20, "h": 18 } @@ -7437,8 +7416,8 @@ "h": 18 }, "frame": { - "x": 241, - "y": 412, + "x": 201, + "y": 378, "w": 20, "h": 18 } @@ -7458,8 +7437,8 @@ "h": 18 }, "frame": { - "x": 258, - "y": 358, + "x": 221, + "y": 375, "w": 20, "h": 18 } @@ -7479,8 +7458,8 @@ "h": 18 }, "frame": { - "x": 261, - "y": 376, + "x": 201, + "y": 396, "w": 20, "h": 18 } @@ -7500,8 +7479,8 @@ "h": 18 }, "frame": { - "x": 261, - "y": 394, + "x": 221, + "y": 393, "w": 20, "h": 18 } @@ -7521,8 +7500,8 @@ "h": 18 }, "frame": { - "x": 261, - "y": 412, + "x": 241, + "y": 376, "w": 20, "h": 18 } @@ -7542,12 +7521,33 @@ "h": 18 }, "frame": { - "x": 278, - "y": 345, + "x": 241, + "y": 394, "w": 20, "h": 18 } }, + { + "filename": "beedrillite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 201, + "y": 414, + "w": 16, + "h": 16 + } + }, { "filename": "wl_super_potion", "rotated": false, @@ -7563,12 +7563,33 @@ "h": 18 }, "frame": { - "x": 298, + "x": 261, "y": 345, "w": 20, "h": 18 } }, + { + "filename": "blastoisinite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 261, + "y": 363, + "w": 16, + "h": 16 + } + }, { "filename": "blazikenite", "rotated": false, @@ -7584,8 +7605,8 @@ "h": 16 }, "frame": { - "x": 318, - "y": 344, + "x": 281, + "y": 345, "w": 16, "h": 16 } @@ -7605,8 +7626,8 @@ "h": 16 }, "frame": { - "x": 334, - "y": 326, + "x": 261, + "y": 379, "w": 16, "h": 16 } @@ -7626,8 +7647,8 @@ "h": 16 }, "frame": { - "x": 334, - "y": 342, + "x": 297, + "y": 345, "w": 16, "h": 16 } @@ -7647,8 +7668,8 @@ "h": 16 }, "frame": { - "x": 350, - "y": 325, + "x": 261, + "y": 395, "w": 16, "h": 16 } @@ -7668,8 +7689,8 @@ "h": 16 }, "frame": { - "x": 350, - "y": 341, + "x": 313, + "y": 345, "w": 16, "h": 16 } @@ -7689,8 +7710,8 @@ "h": 16 }, "frame": { - "x": 366, - "y": 327, + "x": 217, + "y": 414, "w": 16, "h": 16 } @@ -7710,8 +7731,8 @@ "h": 16 }, "frame": { - "x": 366, - "y": 343, + "x": 233, + "y": 412, "w": 16, "h": 16 } @@ -7731,8 +7752,8 @@ "h": 16 }, "frame": { - "x": 382, - "y": 331, + "x": 249, + "y": 412, "w": 16, "h": 16 } @@ -7752,8 +7773,8 @@ "h": 16 }, "frame": { - "x": 398, - "y": 331, + "x": 265, + "y": 411, "w": 16, "h": 16 } @@ -7773,8 +7794,8 @@ "h": 16 }, "frame": { - "x": 414, - "y": 329, + "x": 329, + "y": 345, "w": 16, "h": 16 } @@ -7794,8 +7815,8 @@ "h": 16 }, "frame": { - "x": 382, - "y": 347, + "x": 277, + "y": 363, "w": 16, "h": 16 } @@ -7815,8 +7836,8 @@ "h": 16 }, "frame": { - "x": 398, - "y": 347, + "x": 277, + "y": 379, "w": 16, "h": 16 } @@ -7836,8 +7857,8 @@ "h": 16 }, "frame": { - "x": 414, - "y": 345, + "x": 277, + "y": 395, "w": 16, "h": 16 } @@ -7857,8 +7878,8 @@ "h": 16 }, "frame": { - "x": 281, - "y": 363, + "x": 293, + "y": 361, "w": 16, "h": 16 } @@ -7878,8 +7899,8 @@ "h": 16 }, "frame": { - "x": 281, - "y": 379, + "x": 309, + "y": 361, "w": 16, "h": 16 } @@ -7899,8 +7920,8 @@ "h": 16 }, "frame": { - "x": 297, - "y": 363, + "x": 293, + "y": 377, "w": 16, "h": 16 } @@ -7920,8 +7941,8 @@ "h": 16 }, "frame": { - "x": 281, - "y": 395, + "x": 325, + "y": 361, "w": 16, "h": 16 } @@ -7941,8 +7962,8 @@ "h": 16 }, "frame": { - "x": 297, - "y": 379, + "x": 293, + "y": 393, "w": 16, "h": 16 } @@ -7961,6 +7982,90 @@ "w": 16, "h": 16 }, + "frame": { + "x": 309, + "y": 377, + "w": 16, + "h": 16 + } + }, + { + "filename": "mawilite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 309, + "y": 393, + "w": 16, + "h": 16 + } + }, + { + "filename": "medichamite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 325, + "y": 377, + "w": 16, + "h": 16 + } + }, + { + "filename": "metagrossite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 325, + "y": 393, + "w": 16, + "h": 16 + } + }, + { + "filename": "mewtwonite_x", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, "frame": { "x": 281, "y": 411, @@ -7968,90 +8073,6 @@ "h": 16 } }, - { - "filename": "mawilite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 297, - "y": 395, - "w": 16, - "h": 16 - } - }, - { - "filename": "medichamite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 297, - "y": 411, - "w": 16, - "h": 16 - } - }, - { - "filename": "metagrossite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 313, - "y": 363, - "w": 16, - "h": 16 - } - }, - { - "filename": "mewtwonite_x", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 313, - "y": 379, - "w": 16, - "h": 16 - } - }, { "filename": "mewtwonite_y", "rotated": false, @@ -8067,8 +8088,8 @@ "h": 16 }, "frame": { - "x": 313, - "y": 395, + "x": 297, + "y": 409, "w": 16, "h": 16 } @@ -8089,7 +8110,7 @@ }, "frame": { "x": 313, - "y": 411, + "y": 409, "w": 16, "h": 16 } @@ -8109,8 +8130,8 @@ "h": 16 }, "frame": { - "x": 350, - "y": 357, + "x": 329, + "y": 409, "w": 16, "h": 16 } @@ -8130,8 +8151,8 @@ "h": 16 }, "frame": { - "x": 366, - "y": 359, + "x": 341, + "y": 361, "w": 16, "h": 16 } @@ -8151,8 +8172,8 @@ "h": 16 }, "frame": { - "x": 334, - "y": 358, + "x": 341, + "y": 377, "w": 16, "h": 16 } @@ -8172,8 +8193,8 @@ "h": 16 }, "frame": { - "x": 382, - "y": 363, + "x": 341, + "y": 393, "w": 16, "h": 16 } @@ -8193,8 +8214,8 @@ "h": 16 }, "frame": { - "x": 398, - "y": 363, + "x": 345, + "y": 409, "w": 16, "h": 16 } @@ -8214,7 +8235,7 @@ "h": 16 }, "frame": { - "x": 414, + "x": 412, "y": 361, "w": 16, "h": 16 @@ -8235,8 +8256,8 @@ "h": 16 }, "frame": { - "x": 329, - "y": 374, + "x": 357, + "y": 363, "w": 16, "h": 16 } @@ -8256,8 +8277,8 @@ "h": 16 }, "frame": { - "x": 329, - "y": 390, + "x": 357, + "y": 379, "w": 16, "h": 16 } @@ -8277,8 +8298,8 @@ "h": 16 }, "frame": { - "x": 329, - "y": 406, + "x": 373, + "y": 363, "w": 16, "h": 16 } @@ -8298,8 +8319,8 @@ "h": 16 }, "frame": { - "x": 345, - "y": 374, + "x": 373, + "y": 379, "w": 16, "h": 16 } @@ -8319,8 +8340,8 @@ "h": 16 }, "frame": { - "x": 345, - "y": 390, + "x": 389, + "y": 363, "w": 16, "h": 16 } @@ -8340,8 +8361,8 @@ "h": 16 }, "frame": { - "x": 345, - "y": 406, + "x": 389, + "y": 379, "w": 16, "h": 16 } @@ -8361,8 +8382,8 @@ "h": 16 }, "frame": { - "x": 361, - "y": 375, + "x": 405, + "y": 377, "w": 16, "h": 16 } @@ -8383,7 +8404,7 @@ }, "frame": { "x": 361, - "y": 391, + "y": 395, "w": 16, "h": 16 } @@ -8403,8 +8424,8 @@ "h": 16 }, "frame": { - "x": 361, - "y": 407, + "x": 377, + "y": 395, "w": 16, "h": 16 } @@ -8415,6 +8436,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:934ea4080bad980d4fea720cc771f133:ed564bc47b79b15a763de57045178e88:110e074689c9edd2c54833ce2e4d9270$" + "smartupdate": "$TexturePacker:SmartUpdate:875c6d67e72590dfc6d319101aa31cfa:dd2bb865ecbc5ac7b975ddf70b993334:110e074689c9edd2c54833ce2e4d9270$" } } diff --git a/public/images/items.png b/public/images/items.png index 5f032b30cfb..1bd7b3af9c3 100644 Binary files a/public/images/items.png and b/public/images/items.png differ diff --git a/public/images/items/black_sludge.png b/public/images/items/black_sludge.png index 39684a40310..37aa31de43e 100644 Binary files a/public/images/items/black_sludge.png and b/public/images/items/black_sludge.png differ diff --git a/public/images/items/pb_silver.png b/public/images/items/pb_silver.png new file mode 100644 index 00000000000..f60a8348a94 Binary files /dev/null and b/public/images/items/pb_silver.png differ diff --git a/public/images/trainer/future_self_f.json b/public/images/trainer/future_self_f.json new file mode 100644 index 00000000000..0831828c2bc --- /dev/null +++ b/public/images/trainer/future_self_f.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "future_self_f.png", + "format": "RGBA8888", + "size": { + "w": 29, + "h": 69 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 69, + "h": 69 + }, + "spriteSourceSize": { + "x": 21, + "y": 0, + "w": 29, + "h": 69 + }, + "frame": { + "x": 0, + "y": 0, + "w": 29, + "h": 69 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:4eb16332c2e77886e4e621b62269f05e:26f1bc53c853efdbe228d67604b95b54:d25525a5db42bd57d2afe4b6e3081ee1$" + } +} diff --git a/public/images/trainer/future_self_f.png b/public/images/trainer/future_self_f.png new file mode 100644 index 00000000000..e75c5e5e65b Binary files /dev/null and b/public/images/trainer/future_self_f.png differ diff --git a/public/images/trainer/future_self_m.json b/public/images/trainer/future_self_m.json new file mode 100644 index 00000000000..f5d60ff5add --- /dev/null +++ b/public/images/trainer/future_self_m.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "future_self_m.png", + "format": "RGBA8888", + "size": { + "w": 36, + "h": 73 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 73, + "h": 73 + }, + "spriteSourceSize": { + "x": 18, + "y": 0, + "w": 36, + "h": 73 + }, + "frame": { + "x": 0, + "y": 0, + "w": 36, + "h": 73 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0d8d68d06ae75bc93d72b54183a9df02:d3d509801da9ff5c0bd4793a05ece391:83c4b8c2ed25ea7d9795bec5c40c8602$" + } +} diff --git a/public/images/trainer/future_self_m.png b/public/images/trainer/future_self_m.png new file mode 100644 index 00000000000..edd15cb6ea3 Binary files /dev/null and b/public/images/trainer/future_self_m.png differ diff --git a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts b/src/data/mystery-encounters/custom-pokemon-data.ts similarity index 68% rename from src/data/mystery-encounters/mystery-encounter-pokemon-data.ts rename to src/data/mystery-encounters/custom-pokemon-data.ts index fc6ce313d41..36ecd133a4e 100644 --- a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts +++ b/src/data/mystery-encounters/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 1de83374db2..6c475070dc6 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 @@ -1,4 +1,4 @@ -import { leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, leaveEncounterWithoutBattle, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; @@ -13,6 +13,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import i18next from "i18next"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounters/anOfferYouCantRefuse"; @@ -96,6 +97,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = } } + const silverPokeball = generateModifierType(scene, modifierTypes.SILVER_POKEBALL); + encounter.setDialogueToken("itemName", silverPokeball?.name ?? i18next.t("modifierType:ModifierType.SILVER_POKEBALL.name")); encounter.setDialogueToken("liepardName", getPokemonSpecies(Species.LIEPARD).getName()); return true; @@ -121,8 +124,8 @@ export const AnOfferYouCantRefuseEncounter: MysteryEncounter = return true; }) .withOptionPhase(async (scene: BattleScene) => { - // Give the player a Shiny charm - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SHINY_CHARM)); + // Give the player a Silver Pokeball + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.SILVER_POKEBALL)); leaveEncounterWithoutBattle(scene, true); }) .build() diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index e2bf3bf79fe..79f29662ef0 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -11,7 +11,7 @@ import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Abilities } from "#enums/abilities"; -import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { applyAbilityOverrideToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { Type } from "#app/data/type"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -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/mystery-encounters/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)] }), + mysteryEncounterPokemonData: new CustomPokemonData({ ability: ability, types: [randSeedInt(18), randSeedInt(18)] }), isBoss: true, moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN] }, @@ -352,15 +352,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; } } }) @@ -424,17 +424,8 @@ function onYesAbilitySwap(scene: BattleScene, resolve) { const onPokemonSelected = (pokemon: PlayerPokemon) => { // Do ability swap const encounter = scene.currentBattle.mysteryEncounter!; - if (pokemon.isFusion()) { - if (!pokemon.fusionMysteryEncounterPokemonData) { - pokemon.fusionMysteryEncounterPokemonData = new MysteryEncounterPokemonData(); - } - pokemon.fusionMysteryEncounterPokemonData.ability = encounter.misc.ability; - } else { - if (!pokemon.mysteryEncounterPokemonData) { - pokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); - } - pokemon.mysteryEncounterPokemonData.ability = encounter.misc.ability; - } + + applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability); encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); scene.ui.setMode(Mode.MESSAGE).then(() => resolve(true)); }; diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index c511add0b28..1678c82649b 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -14,6 +14,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase"; import { PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Challenges } from "#enums/challenges"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/darkDeal"; @@ -140,6 +141,7 @@ export const DarkDealEncounter: MysteryEncounter = // Removes random pokemon (including fainted) from party and adds name to dialogue data tokens // Will never return last battle able mon and instead pick fainted/unable to battle const removedPokemon = getRandomPlayerPokemon(scene, true, false, true); + // Get all the pokemon's held items const modifiers = removedPokemon.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); scene.removePokemonFromPlayerParty(removedPokemon); @@ -159,7 +161,13 @@ export const DarkDealEncounter: MysteryEncounter = scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.ROGUE_BALL)); // Start encounter with random legendary (7-10 starter strength) that has level additive - const bossTypes: Type[] = encounter.misc.removedTypes; + // If this is a mono-type challenge, always ensure the required type is filtered for + let bossTypes: Type[] = encounter.misc.removedTypes; + const singleTypeChallenges = scene.gameMode.challenges.filter(c => c.id === Challenges.SINGLE_TYPE); + if (scene.gameMode.isChallenge && singleTypeChallenges.length > 0) { + bossTypes = singleTypeChallenges.map(c => c.value as Type); + } + const bossModifiers: PokemonHeldItemModifier[] = encounter.misc.modifiers; // Starter egg tier, 35/50/10/5 %odds for tiers 6/7/8/9+ const roll = randSeedInt(100); diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index 1246572b680..4db0595db26 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -211,7 +211,7 @@ export const DelibirdyEncounter: MysteryEncounter = } } else { // Check if the player has max stacks of that Healing Charm already - const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; + const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -220,7 +220,7 @@ export const DelibirdyEncounter: MysteryEncounter = scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM)); + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH)); } } @@ -289,8 +289,8 @@ export const DelibirdyEncounter: MysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter!; const modifier = encounter.misc.chosenModifier; - // Check if the player has max stacks of Berry Pouch already - const existing = scene.findModifier(m => m instanceof PreserveBerryModifier) as PreserveBerryModifier; + // Check if the player has max stacks of Healing Charm already + const existing = scene.findModifier(m => m instanceof HealingBoosterModifier) as HealingBoosterModifier; if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead @@ -299,7 +299,7 @@ export const DelibirdyEncounter: MysteryEncounter = scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, undefined, true); } else { - scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.BERRY_POUCH)); + scene.unshiftPhase(new ModifierRewardPhase(scene, modifierTypes.HEALING_CHARM)); } // Remove the modifier if its stacks go to 0 diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index cfb87a9e862..917395bf226 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -4,24 +4,30 @@ import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/mod import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; -import { TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; +import { AbilityRequirement, CombinationPokemonRequirement, TypeRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { Species } from "#enums/species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Gender } from "#app/data/gender"; import { Type } from "#app/data/type"; import { BattlerIndex } from "#app/battle"; -import { PokemonMove } from "#app/field/pokemon"; +import Pokemon, { PokemonMove } from "#app/field/pokemon"; import { Moves } from "#enums/moves"; import { EncounterBattleAnim } from "#app/data/battle-anims"; import { WeatherType } from "#app/data/weather"; import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { StatusEffect } from "#app/data/status-effect"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { applyAbilityOverrideToPokemon, applyDamageToPokemon, applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { EncounterAnim } from "#enums/encounter-anims"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; +import { Abilities } from "#enums/abilities"; +import { BattlerTagType } from "#enums/battler-tag-type"; +import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; +import { Stat } from "#enums/stat"; +import { Ability } from "#app/data/ability"; +import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounters/fieryFallout"; @@ -62,16 +68,24 @@ export const FieryFalloutEncounter: MysteryEncounter = { species: volcaronaSpecies, isBoss: false, - gender: Gender.MALE + gender: Gender.MALE, + tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], + mysteryEncounterBattleEffects: (pokemon: Pokemon) => { + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 2)); + } }, { species: volcaronaSpecies, isBoss: false, - gender: Gender.FEMALE + gender: Gender.FEMALE, + tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], + mysteryEncounterBattleEffects: (pokemon: Pokemon) => { + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [Stat.SPDEF, Stat.SPD], 2)); + } } ], doubleBattle: true, - disableSwitch: true + disableSwitch: true, }; encounter.enemyPartyConfigs = [config]; @@ -138,7 +152,7 @@ export const FieryFalloutEncounter: MysteryEncounter = async (scene: BattleScene) => { // Pick battle const encounter = scene.currentBattle.mysteryEncounter!; - setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonCharcoal(scene)); + setEncounterRewards(scene, { fillRemaining: true }, undefined, () => giveLeadPokemonAttackTypeBoostItem(scene)); encounter.startOfBattleEffects.push( { @@ -152,18 +166,6 @@ export const FieryFalloutEncounter: MysteryEncounter = targets: [BattlerIndex.PLAYER_2], move: new PokemonMove(Moves.FIRE_SPIN), ignorePp: true - }, - { - sourceBattlerIndex: BattlerIndex.ENEMY, - targets: [BattlerIndex.ENEMY], - move: new PokemonMove(Moves.QUIVER_DANCE), - ignorePp: true - }, - { - sourceBattlerIndex: BattlerIndex.ENEMY_2, - targets: [BattlerIndex.ENEMY_2], - move: new PokemonMove(Moves.QUIVER_DANCE), - ignorePp: true }); await initBattleWithEnemyConfig(scene, scene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0]); } @@ -179,7 +181,7 @@ export const FieryFalloutEncounter: MysteryEncounter = ], }, async (scene: BattleScene) => { - // Damage non-fire types and burn 1 random non-fire type member + // Damage non-fire types and burn 1 random non-fire type member + give it Heatproof const encounter = scene.currentBattle.mysteryEncounter!; const nonFireTypes = scene.getParty().filter((p) => p.isAllowedInBattle() && !p.getTypes().includes(Type.FIRE)); @@ -197,7 +199,11 @@ export const FieryFalloutEncounter: MysteryEncounter = if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { // Burn applied encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); + encounter.setDialogueToken("abilityName", new Ability(Abilities.HEATPROOF, 3).name); queueEncounterMessage(scene, `${namespace}:option.2.target_burned`); + + // Also permanently change the burned Pokemon's ability to Heatproof + applyAbilityOverrideToPokemon(chosenPokemon, Abilities.HEATPROOF); } } @@ -208,8 +214,10 @@ export const FieryFalloutEncounter: MysteryEncounter = .withOption( MysteryEncounterOptionBuilder .newOptionWithMode(MysteryEncounterOptionMode.DISABLED_OR_SPECIAL) - .withPrimaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3PrimaryName dialogue token automatically - .withSecondaryPokemonRequirement(new TypeRequirement(Type.FIRE, true, 1)) // Will set option3SecondaryName dialogue token automatically + .withPrimaryPokemonRequirement(new CombinationPokemonRequirement( + new TypeRequirement(Type.FIRE, true, 1), + new AbilityRequirement(FIRE_RESISTANT_ABILITIES) + )) // Will set option3PrimaryName dialogue token automatically .withDialogue({ buttonLabel: `${namespace}:option.3.label`, buttonTooltip: `${namespace}:option.3.tooltip`, @@ -231,26 +239,27 @@ export const FieryFalloutEncounter: MysteryEncounter = { fillRemaining: true }, undefined, () => { - giveLeadPokemonCharcoal(scene); + giveLeadPokemonAttackTypeBoostItem(scene); }); const primary = encounter.options[2].primaryPokemon!; - const secondary = encounter.options[2].secondaryPokemon![0]; - setEncounterExp(scene, [primary.id, secondary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2); + setEncounterExp(scene, [primary.id], getPokemonSpecies(Species.VOLCARONA).baseExp * 2); leaveEncounterWithoutBattle(scene); }) .build() ) .build(); -function giveLeadPokemonCharcoal(scene: BattleScene) { - // Give first party pokemon Charcoal for free at end of battle +function giveLeadPokemonAttackTypeBoostItem(scene: BattleScene) { + // Give first party pokemon attack type boost item for free at end of battle const leadPokemon = scene.getParty()?.[0]; if (leadPokemon) { - const charcoal = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]) as AttackTypeBoosterModifierType; - applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal); - scene.currentBattle.mysteryEncounter!.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); - queueEncounterMessage(scene, `${namespace}:found_charcoal`); + const encounter = scene.currentBattle.mysteryEncounter!; + const boosterModifierType = generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER) as AttackTypeBoosterModifierType; + applyModifierTypeToPlayerPokemon(scene, leadPokemon, boosterModifierType); + encounter.setDialogueToken("itemName", boosterModifierType.name); + encounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); + queueEncounterMessage(scene, `${namespace}:found_item`); } } diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index af01ecbb97c..abf19a5e2f9 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -56,7 +56,13 @@ export const MysteriousChallengersEncounter: MysteryEncounter = // Hard difficulty trainer is another random trainer, but with AVERAGE_BALANCED config // Number of mons is based off wave: 1-20 is 2, 20-40 is 3, etc. capping at 6 after wave 100 - const hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + let retries = 0; + let hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + while (retries < 5 && hardTrainerType === normalTrainerType) { + // Will try to use a different trainer from the normal trainer type + hardTrainerType = scene.arena.randomTrainerType(scene.currentBattle.waveIndex); + retries++; + } const hardTemplate = new TrainerPartyCompoundTemplate( new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER, false, true), new TrainerPartyTemplate( 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 a8ddfc28ae9..aaf08fadfcb 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -21,7 +21,7 @@ import i18next from "i18next"; const namespace = "mysteryEncounters/shadyVitaminDealer"; const VITAMIN_DEALER_CHEAP_PRICE_MULTIPLIER = 1.5; -const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 3.5; +const VITAMIN_DEALER_EXPENSIVE_PRICE_MULTIPLIER = 5; /** * Shady Vitamin Dealer encounter. diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 5abd4839c0c..0545fa5f0fd 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/mystery-encounters/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 }), + mysteryEncounterPokemonData: new CustomPokemonData({ spriteScale: 1.25 }), aiType: AiType.SMART // Required to ensure Snorlax uses Sleep Talk while it is asleep }; const config: EnemyPartyConfig = { 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 24e777d8c88..19b58a7f76e 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 @@ -162,7 +162,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; @@ -219,7 +219,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, { guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL], fillRemaining: true }, eggOptions); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon1); @@ -271,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, { guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL], fillRemaining: true }, eggOptions); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon2); @@ -323,7 +323,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, { guaranteedModifierTypeFuncs: [modifierTypes.SOOTHE_BELL], fillRemaining: true }, eggOptions); // Remove all Pokemon from the party except the chosen Pokemon removePokemonFromPartyAndStoreHeldItems(scene, encounter, pokemon3); @@ -460,12 +460,16 @@ function calculateEggRewardsForPokemon(pokemon: PlayerPokemon): [number, number] } // Maximum of 30 points - const totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30); + let totalPoints = Math.min(pointsFromStarterTier + pointsFromBst, 30); - // 1 Rare egg for every 6 points - const numRares = Math.floor(totalPoints / 6); + // First 5 points go to Common eggs + let numCommons = Math.min(totalPoints, 5); + totalPoints -= numCommons; + + // Then, 1 Rare egg for every 4 points + const numRares = Math.floor(totalPoints / 4); // 1 Common egg for every point leftover - const numCommons = totalPoints % 6; + numCommons += totalPoints % 4; return [numCommons, numRares]; } 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 bd99c8babf7..4893d95d41b 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/mystery-encounters/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 }), + mysteryEncounterPokemonData: 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/trash-to-treasure-encounter.ts b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts index 9673924e6d1..bbf52b0c118 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -70,7 +70,7 @@ export const TrashToTreasureEncounter: MysteryEncounter = moveSet: [Moves.PAYBACK, Moves.GUNK_SHOT, Moves.STOMPING_TANTRUM, Moves.DRAIN_PUNCH] }; const config: EnemyPartyConfig = { - levelAdditiveModifier: 1, + levelAdditiveModifier: 0.5, pokemonConfigs: [pokemonConfig], disableSwitch: true }; diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 1e68a894bc4..4842ec0a0ce 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -4,23 +4,31 @@ import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import MysteryEncounter, { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; -import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; +import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { IntegerHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils"; import PokemonSpecies, { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species"; -import { HiddenAbilityRateBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { HiddenAbilityRateBoosterModifier, PokemonBaseStatFlatModifier, 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/mystery-encounters/custom-pokemon-data"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import { modifierTypes } from "#app/modifier/modifier-type"; +import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; import { doPokemonTransformationSequence, TransformationScreenPosition } from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { getLevelTotalExp } from "#app/data/exp"; import { Stat } from "#enums/stat"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode"; import { Challenges } from "#enums/challenges"; +import { ModifierTier } from "#app/modifier/modifier-tier"; +import { PlayerGender } from "#enums/player-gender"; +import { TrainerType } from "#enums/trainer-type"; +import PokemonData from "#app/system/pokemon-data"; +import { Nature } from "#enums/nature"; +import HeldModifierConfig from "#app/interfaces/held-modifier-config"; +import { trainerConfigs, TrainerPartyTemplate } from "#app/data/trainer-config"; +import { PartyMemberStrength } from "#enums/party-member-strength"; /** i18n namespace for encounter */ const namespace = "mysteryEncounters/weirdDream"; @@ -80,10 +88,9 @@ const EXCLUDED_TRANSFORMATION_SPECIES = [ const SUPER_LEGENDARY_BST_THRESHOLD = 600; const NON_LEGENDARY_BST_THRESHOLD = 570; -const GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD = 450; /** 0-100 */ -const PERCENT_LEVEL_LOSS_ON_REFUSE = 12.5; +const PERCENT_LEVEL_LOSS_ON_REFUSE = 10; /** * Value ranges of the resulting species BST transformations after adding values to original species @@ -105,7 +112,8 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) .withEncounterTier(MysteryEncounterTier.ROGUE) .withDisallowedChallenges(Challenges.SINGLE_TYPE, Challenges.SINGLE_GENERATION) - .withSceneWaveRangeRequirement(...CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES) + // TODO: should reset minimum wave to 10 when there are more Rogue tiers in pool. Matching Dark Deal minimum for now. + .withSceneWaveRangeRequirement(30, CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES[1]) .withIntroSpriteConfigs([ { spriteKey: "weird_dream_woman", @@ -130,6 +138,15 @@ export const WeirdDreamEncounter: MysteryEncounter = .withQuery(`${namespace}:query`) .withOnInit((scene: BattleScene) => { scene.loadBgm("mystery_encounter_weird_dream", "mystery_encounter_weird_dream.mp3"); + + // Calculate all the newly transformed Pokemon and begin asset load + const teamTransformations = getTeamTransformations(scene); + const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets()); + scene.currentBattle.mysteryEncounter!.misc = { + teamTransformations, + loadAssets + }; + return true; }) .withOnVisualsStart((scene: BattleScene) => { @@ -155,13 +172,10 @@ export const WeirdDreamEncounter: MysteryEncounter = doShowDreamBackground(scene); }); - // Calculate all the newly transformed Pokemon and begin asset load - const teamTransformations = getTeamTransformations(scene); - const loadAssets = teamTransformations.map(t => (t.newPokemon as PlayerPokemon).loadAssets()); - scene.currentBattle.mysteryEncounter!.misc = { - teamTransformations, - loadAssets - }; + for (const transformation of scene.currentBattle.mysteryEncounter!.misc.teamTransformations) { + scene.removePokemonFromPlayerParty(transformation.previousPokemon, false); + scene.getParty().push(transformation.newPokemon); + } }) .withOptionPhase(async (scene: BattleScene) => { // Starts cutscene dialogue, but does not await so that cutscene plays as player goes through dialogue @@ -192,7 +206,7 @@ export const WeirdDreamEncounter: MysteryEncounter = await showEncounterText(scene, `${namespace}:option.1.dream_complete`); await doNewTeamPostProcess(scene, transformations); - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT]}); + setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MEMORY_MUSHROOM, modifierTypes.ROGUE_BALL, modifierTypes.MINT, modifierTypes.MINT, modifierTypes.MINT], fillRemaining: false }); leaveEncounterWithoutBattle(scene, true); }) .build() @@ -208,7 +222,88 @@ export const WeirdDreamEncounter: MysteryEncounter = ], }, async (scene: BattleScene) => { - // Reduce party levels by 20% + // Battle your "future" team for some item rewards + const transformations: PokemonTransformation[] = scene.currentBattle.mysteryEncounter!.misc.teamTransformations; + + // Uses the pokemon that player's party would have transformed into + const enemyPokemonConfigs: EnemyPokemonConfig[] = []; + for (const transformation of transformations) { + const newPokemon = transformation.newPokemon; + const previousPokemon = transformation.previousPokemon; + + await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, newPokemon.species.getRootSpeciesId(), true); + + const dataSource = new PokemonData(newPokemon); + dataSource.player = false; + + // Copy all held items + const heldItems = previousPokemon.getHeldItems(); + const newPokemonHeldItemConfigs: HeldModifierConfig[] = heldItems.map(previousMod => { + return { + modifier: previousMod.clone() as PokemonHeldItemModifier, + stackCount: previousMod.getStackCount(), + isTransferable: false + }; + }); + + if (newPokemonHeldItemConfigs.filter(config => config.modifier instanceof PokemonBaseStatFlatModifier).length === 0) { + // Also add Old Gateau (even on transformed mons that shouldn't normally get it) + newPokemonHeldItemConfigs.push({ + modifier: generateModifierType(scene, modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU) as PokemonHeldItemModifierType, + stackCount: 1, + isTransferable: false + }); + } + + const enemyConfig: EnemyPokemonConfig = { + species: transformation.newSpecies, + isBoss: newPokemon.getSpeciesForm().getBaseStatTotal() > NON_LEGENDARY_BST_THRESHOLD, + level: previousPokemon.level, + dataSource: dataSource, + modifierConfigs: newPokemonHeldItemConfigs + }; + + enemyPokemonConfigs.push(enemyConfig); + } + + const genderIndex = scene.gameData.gender ?? PlayerGender.UNSET; + const trainerConfig = trainerConfigs[genderIndex === PlayerGender.FEMALE ? TrainerType.FUTURE_SELF_F : TrainerType.FUTURE_SELF_M].clone(); + trainerConfig.setPartyTemplates(new TrainerPartyTemplate(transformations.length, PartyMemberStrength.STRONG)); + const enemyPartyConfig: EnemyPartyConfig = { + trainerConfig: trainerConfig, + pokemonConfigs: enemyPokemonConfigs, + female: genderIndex === PlayerGender.FEMALE + }; + + const onBeforeRewards = () => { + // Before battle rewards, unlock the passive on a pokemon in the player's team for the rest of the run (not permanently) + // One random pokemon will get its passive unlocked + const passiveDisabledPokemon = scene.getParty().filter(p => !p.passive); + if (passiveDisabledPokemon?.length > 0) { + const enablePassiveMon = passiveDisabledPokemon[randSeedInt(passiveDisabledPokemon.length)]; + enablePassiveMon.passive = true; + enablePassiveMon.updateInfo(true); + } + }; + + setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: false }, undefined, onBeforeRewards); + + await showEncounterText(scene, `${namespace}:option.2.selected_2`, null, undefined, true); + await initBattleWithEnemyConfig(scene, enemyPartyConfig); + } + ) + .withSimpleOption( + { + buttonLabel: `${namespace}:option.3.label`, + buttonTooltip: `${namespace}:option.3.tooltip`, + selected: [ + { + text: `${namespace}:option.3.selected`, + }, + ], + }, + async (scene: BattleScene) => { + // Leave, reduce party levels by 10% for (const pokemon of scene.getParty()) { pokemon.level = Math.max(Math.ceil((100 - PERCENT_LEVEL_LOSS_ON_REFUSE) / 100 * pokemon.level), 1); pokemon.exp = getLevelTotalExp(pokemon.level, pokemon.species.growthRate); @@ -234,7 +329,7 @@ interface PokemonTransformation { function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { const party = scene.getParty(); // Removes all pokemon from the party - const alreadyUsedSpecies: PokemonSpecies[] = []; + const alreadyUsedSpecies: PokemonSpecies[] = party.map(p => p.species); const pokemonTransformations: PokemonTransformation[] = party.map(p => { return { previousPokemon: p @@ -249,11 +344,11 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { // First, roll 2 of the party members to new Pokemon at a +90 to +110 BST difference // Then, roll the remainder of the party members at a +40 to +50 BST difference const numPokemon = party.length; + const removedPokemon = randSeedShuffle(party.slice(0)); for (let i = 0; i < numPokemon; i++) { - const removed = party[randSeedInt(party.length)]; + const removed = removedPokemon[i]; const index = pokemonTransformations.findIndex(p => p.previousPokemon.id === removed.id); pokemonTransformations[index].heldItems = removed.getHeldItems().filter(m => !(m instanceof PokemonFormChangeItemModifier)); - scene.removePokemonFromPlayerParty(removed, false); const bst = removed.calculateBaseStats().reduce((a, b) => a + b, 0); let newBstRange: [number, number]; @@ -275,14 +370,13 @@ function getTeamTransformations(scene: BattleScene): PokemonTransformation[] { pokemonTransformations[index].newSpecies = newSpecies; + console.log("New species: " + JSON.stringify(newSpecies)); alreadyUsedSpecies.push(newSpecies); } for (const transformation of pokemonTransformations) { const newAbilityIndex = randSeedInt(transformation.newSpecies.getAbilityCount()); - const newPlayerPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined); - transformation.newPokemon = newPlayerPokemon; - scene.getParty().push(newPlayerPokemon); + transformation.newPokemon = scene.addPlayerPokemon(transformation.newSpecies, transformation.previousPokemon.level, newAbilityIndex, undefined); } return pokemonTransformations; @@ -295,119 +389,16 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon const newPokemon = transformation.newPokemon; const speciesRootForm = newPokemon.species.getRootSpeciesId(); - // Roll HA a second time - if (newPokemon.species.abilityHidden) { - const hiddenIndex = newPokemon.species.ability2 ? 2 : 1; - if (newPokemon.abilityIndex < hiddenIndex) { - const hiddenAbilityChance = new IntegerHolder(256); - scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); - - const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); - - if (hasHiddenAbility) { - newPokemon.abilityIndex = hiddenIndex; - } - } + if (await postProcessTransformedPokemon(scene, previousPokemon, newPokemon, speciesRootForm)) { + atLeastOneNewStarter = true; } - // Roll IVs a second time - newPokemon.ivs = newPokemon.ivs.map(iv => { - const newValue = randSeedInt(31); - return newValue > iv ? newValue : iv; - }); - - // For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it - if (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny()) { - if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) { - scene.validateAchv(achvs.HIDDEN_ABILITY); - } - - if (newPokemon.species.subLegendary) { - scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); - } - - if (newPokemon.species.legendary) { - scene.validateAchv(achvs.CATCH_LEGENDARY); - } - - if (newPokemon.species.mythical) { - scene.validateAchv(achvs.CATCH_MYTHICAL); - } - - scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs); - const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false); - if (newStarterUnlocked) { - atLeastOneNewStarter = true; - await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); - } - } - - // If the previous pokemon had pokerus, transfer to new pokemon - newPokemon.pokerus = previousPokemon.pokerus; - - // Transfer previous Pokemon's luck value - newPokemon.luck = previousPokemon.getLuck(); - - // If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock) - newPokemon.ivs = newPokemon.ivs.map((iv, index) => { - return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv; - }); - - // For pokemon that the player owns (including ones just caught), gain a candy - if (!!scene.gameData.dexData[speciesRootForm].caughtAttr) { - scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1); - } - - // Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species - newPokemon.generateAndPopulateMoveset(); - // Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move - const newPokemonGeneratedMoveset = newPokemon.moveset; - - newPokemon.moveset = previousPokemon.moveset; - - const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm); - - // Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset) - addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex); - - // Randomize the second type of the pokemon - // If the pokemon does not normally have a second type, it will gain 1 - const newTypes = [newPokemon.getTypes()[0]]; - let newType = randSeedInt(18) as Type; - while (newType === newTypes[0]) { - newType = randSeedInt(18) as Type; - } - newTypes.push(newType); - if (!newPokemon.mysteryEncounterPokemonData) { - newPokemon.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(); - } - newPokemon.mysteryEncounterPokemonData.types = newTypes; - + // Copy old items to new pokemon for (const item of transformation.heldItems) { item.pokemonId = newPokemon.id; await scene.addModifier(item, false, false, false, true); } - // Any pokemon that is at or below 450 BST gets +20 permanent BST to 3 stats: HP (halved, +10), lowest of Atk/SpAtk, and lowest of Def/SpDef - if (newPokemon.getSpeciesForm().getBaseStatTotal() <= GAIN_OLD_GATEAU_ITEM_BST_THRESHOLD) { - const stats: Stat[] = [Stat.HP]; - const baseStats = newPokemon.getSpeciesForm().baseStats.slice(0); - // Attack or SpAtk - stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK); - // Def or SpDef - stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF); - const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU() - .generateType(scene.getParty(), [20, stats]) - ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); - const modifier = modType?.newModifier(newPokemon); - if (modifier) { - await scene.addModifier(modifier, false, false, false, true); - } - } - - // Enable passive if previous had it - newPokemon.passive = previousPokemon.passive; - newPokemon.calculateStats(); await newPokemon.updateInfo(); } @@ -426,6 +417,133 @@ async function doNewTeamPostProcess(scene: BattleScene, transformations: Pokemon } } +/** + * Applies special changes to the newly transformed pokemon, such as passing previous moves, gaining egg moves, etc. + * Returns whether the transformed pokemon unlocks a new starter for the player. + * @param scene + * @param previousPokemon + * @param newPokemon + * @param speciesRootForm + * @param forBattle Default `false`. If true, will perform notifications and dex unlocks for the player. + */ +async function postProcessTransformedPokemon(scene: BattleScene, previousPokemon: PlayerPokemon, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { + let isNewStarter = false; + // Roll HA a second time + if (newPokemon.species.abilityHidden) { + const hiddenIndex = newPokemon.species.ability2 ? 2 : 1; + if (newPokemon.abilityIndex < hiddenIndex) { + const hiddenAbilityChance = new IntegerHolder(256); + scene.applyModifiers(HiddenAbilityRateBoosterModifier, true, hiddenAbilityChance); + + const hasHiddenAbility = !randSeedInt(hiddenAbilityChance.value); + + if (hasHiddenAbility) { + newPokemon.abilityIndex = hiddenIndex; + } + } + } + + // Roll IVs a second time + newPokemon.ivs = newPokemon.ivs.map(iv => { + const newValue = randSeedInt(31); + return newValue > iv ? newValue : iv; + }); + + // Roll a neutral nature + newPokemon.nature = [Nature.HARDY, Nature.DOCILE, Nature.BASHFUL, Nature.QUIRKY, Nature.SERIOUS][randSeedInt(5)]; + + // For pokemon at/below 570 BST or any shiny pokemon, unlock it permanently as if you had caught it + if (!forBattle && (newPokemon.getSpeciesForm().getBaseStatTotal() <= NON_LEGENDARY_BST_THRESHOLD || newPokemon.isShiny())) { + if (newPokemon.getSpeciesForm().abilityHidden && newPokemon.abilityIndex === newPokemon.getSpeciesForm().getAbilityCount() - 1) { + scene.validateAchv(achvs.HIDDEN_ABILITY); + } + + if (newPokemon.species.subLegendary) { + scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); + } + + if (newPokemon.species.legendary) { + scene.validateAchv(achvs.CATCH_LEGENDARY); + } + + if (newPokemon.species.mythical) { + scene.validateAchv(achvs.CATCH_MYTHICAL); + } + + scene.gameData.updateSpeciesDexIvs(newPokemon.species.getRootSpeciesId(true), newPokemon.ivs); + const newStarterUnlocked = await scene.gameData.setPokemonCaught(newPokemon, true, false, false); + if (newStarterUnlocked) { + isNewStarter = true; + await showEncounterText(scene, i18next.t("battle:addedAsAStarter", { pokemonName: getPokemonSpecies(speciesRootForm).getName() })); + } + } + + // If the previous pokemon had pokerus, transfer to new pokemon + newPokemon.pokerus = previousPokemon.pokerus; + + // Transfer previous Pokemon's luck value + newPokemon.luck = previousPokemon.getLuck(); + + // If the previous pokemon had higher IVs, override to those (after updating dex IVs > prevents perfect 31s on a new unlock) + newPokemon.ivs = newPokemon.ivs.map((iv, index) => { + return previousPokemon.ivs[index] > iv ? previousPokemon.ivs[index] : iv; + }); + + // For pokemon that the player owns (including ones just caught), gain a candy + if (!forBattle && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { + scene.gameData.addStarterCandy(getPokemonSpecies(speciesRootForm), 1); + } + + // Set the moveset of the new pokemon to be the same as previous, but with 1 egg move and 1 (attempted) STAB move of the new species + newPokemon.generateAndPopulateMoveset(); + // Store a copy of a "standard" generated moveset for the new pokemon, will be used later for finding a favored move + const newPokemonGeneratedMoveset = newPokemon.moveset; + + newPokemon.moveset = previousPokemon.moveset.slice(0); + + const newEggMoveIndex = await addEggMoveToNewPokemonMoveset(scene, newPokemon, speciesRootForm, forBattle); + + // Try to add a favored STAB move (might fail if Pokemon already knows a bunch of moves from newPokemonGeneratedMoveset) + addFavoredMoveToNewPokemonMoveset(newPokemon, newPokemonGeneratedMoveset, newEggMoveIndex); + + // Randomize the second type of the pokemon + // If the pokemon does not normally have a second type, it will gain 1 + const newTypes = [newPokemon.getTypes()[0]]; + let newType = randSeedInt(18) as Type; + while (newType === newTypes[0]) { + newType = randSeedInt(18) as Type; + } + newTypes.push(newType); + if (!newPokemon.customPokemonData) { + newPokemon.customPokemonData = new CustomPokemonData(); + } + newPokemon.customPokemonData.types = newTypes; + + // Any pokemon that is below 570 BST gets +20 permanent BST to 3 stats: lowest of HP/Spd, lowest of Atk/SpAtk, and lowest of Def/SpDef + if (newPokemon.getSpeciesForm().getBaseStatTotal() < NON_LEGENDARY_BST_THRESHOLD) { + const stats: Stat[] = []; + const baseStats = newPokemon.getSpeciesForm().baseStats.slice(0); + // HP or Speed + stats.push(baseStats[Stat.HP] < baseStats[Stat.SPD] ? Stat.HP : Stat.SPD); + // Attack or SpAtk + stats.push(baseStats[Stat.ATK] < baseStats[Stat.SPATK] ? Stat.ATK : Stat.SPATK); + // Def or SpDef + stats.push(baseStats[Stat.DEF] < baseStats[Stat.SPDEF] ? Stat.DEF : Stat.SPDEF); + const modType = modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU() + .generateType(scene.getParty(), [20, stats]) + ?.withIdFromFunc(modifierTypes.MYSTERY_ENCOUNTER_OLD_GATEAU); + const modifier = modType?.newModifier(newPokemon); + if (modifier) { + await scene.addModifier(modifier, false, false, false, true); + } + } + + // Enable passive if previous had it + newPokemon.passive = previousPokemon.passive; + + return isNewStarter; +} + function getTransformedSpecies(originalBst: number, bstSearchRange: [number, number], hasPokemonBstHigherThan600: boolean, hasPokemonBstBetween570And600: boolean, alreadyUsedSpecies: PokemonSpecies[]): PokemonSpecies { let newSpecies: PokemonSpecies | undefined; while (isNullOrUndefined(newSpecies)) { @@ -549,7 +667,7 @@ function doSideBySideTransformations(scene: BattleScene, transformations: Pokemo * @param newPokemon * @param speciesRootForm */ -async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species): Promise { +async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: PlayerPokemon, speciesRootForm: Species, forBattle: boolean = false): Promise { let eggMoveIndex: null | number = null; const eggMoves = newPokemon.getEggMoves()?.slice(0); if (eggMoves) { @@ -575,7 +693,7 @@ async function addEggMoveToNewPokemonMoveset(scene: BattleScene, newPokemon: Pla } // For pokemon that the player owns (including ones just caught), unlock the egg move - if (!isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { + if (!forBattle && !isNullOrUndefined(randomEggMoveIndex) && !!scene.gameData.dexData[speciesRootForm].caughtAttr) { await scene.gameData.setEggMoveUnlocked(getPokemonSpecies(speciesRootForm), randomEggMoveIndex, true); } } diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index 5fa2a7066a5..4eb15d31df8 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -577,10 +577,10 @@ 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.requiredAbilities.some((ability) => pokemon.getAbility().id === ability || pokemon.getPassiveAbility().id === ability)); } 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.requiredAbilities.filter((ability) => pokemon.getAbility().id === ability || pokemon.getPassiveAbility().id === ability).length === 0); } } diff --git a/src/data/mystery-encounters/requirements/requirement-groups.ts b/src/data/mystery-encounters/requirements/requirement-groups.ts index 63c899fc5e9..3eac6806407 100644 --- a/src/data/mystery-encounters/requirements/requirement-groups.ts +++ b/src/data/mystery-encounters/requirements/requirement-groups.ts @@ -118,3 +118,17 @@ export const EXTORTION_ABILITIES = [ Abilities.SUCTION_CUPS, Abilities.STICKY_HOLD ]; + +/** + * Abilities that signify resistance to fire + */ +export const FIRE_RESISTANT_ABILITIES = [ + Abilities.FLAME_BODY, + Abilities.FLASH_FIRE, + Abilities.WELL_BAKED_BODY, + Abilities.HEATPROOF, + Abilities.THERMAL_EXCHANGE, + Abilities.THICK_FAT, + Abilities.WATER_BUBBLE, + Abilities.MAGMA_ARMOR +]; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index ca02c8bdfd6..6f7e6c259f3 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/mystery-encounters/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; + mysteryEncounterPokemonData?: 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 { @@ -251,7 +251,7 @@ 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; + enemyPokemon.customPokemonData = config.mysteryEncounterPokemonData; } // Set Boss diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index fb67305c78d..f6d4b8c38ae 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -20,6 +20,8 @@ import { Gender } from "#app/data/gender"; import { PermanentStat } from "#enums/stat"; import { VictoryPhase } from "#app/phases/victory-phase"; import { SummaryUiMode } from "#app/ui/summary-ui-handler"; +import { CustomPokemonData } from "#app/data/mystery-encounters/custom-pokemon-data"; +import { Abilities } from "#enums/abilities"; /** Will give +1 level every 10 waves */ export const STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER = 1; @@ -832,3 +834,23 @@ export function isPokemonValidForEncounterOptionSelection(pokemon: Pokemon, scen return null; } + +/** + * Permanently overrides the ability (not passive) of a pokemon. + * If the pokemon is a fusion, instead overrides the fused pokemon's ability. + * @param pokemon + * @param ability + */ +export function applyAbilityOverrideToPokemon(pokemon: Pokemon, ability: Abilities) { + if (pokemon.isFusion()) { + if (!pokemon.fusionCustomPokemonData) { + pokemon.fusionCustomPokemonData = new CustomPokemonData(); + } + pokemon.fusionCustomPokemonData.ability = ability; + } else { + if (!pokemon.customPokemonData) { + pokemon.customPokemonData = new CustomPokemonData(); + } + pokemon.customPokemonData.ability = ability; + } +} diff --git a/src/data/pokemon-evolutions.ts b/src/data/pokemon-evolutions.ts index fc1dfffb1ec..aac48b57e90 100644 --- a/src/data/pokemon-evolutions.ts +++ b/src/data/pokemon-evolutions.ts @@ -10,7 +10,7 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; -import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier } from "#app/modifier/modifier"; +import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -1652,11 +1652,11 @@ export const pokemonEvolutions: PokemonEvolutions = { new SpeciesFormEvolution(Species.GHOLDENGO, "chest", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG), + || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG), new SpeciesFormEvolution(Species.GHOLDENGO, "roaming", "", 1, null, new SpeciesEvolutionCondition(p => p.evoCounter + p.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length + p.scene.findModifiers(m => m instanceof MoneyMultiplierModifier - || m instanceof ExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG) + || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length > 9), SpeciesWildEvolutionDelay.VERY_LONG) ] }; diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 296e4e9b1b5..764cac5e901 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -2471,6 +2471,22 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.BUG_TYPE_SUPERFAN]: new TrainerConfig(++t).setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.ACE_TRAINER) .setPartyTemplates(new TrainerPartyTemplate(2, PartyMemberStrength.AVERAGE)), [TrainerType.EXPERT_POKEMON_BREEDER]: new TrainerConfig(++t).setMoneyMultiplier(3).setEncounterBgm(TrainerType.ACE_TRAINER).setLocalizedName("Expert Pokemon Breeder") - .setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.STRONG)) + .setPartyTemplates(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE)), + [TrainerType.FUTURE_SELF_M]: new TrainerConfig(++t) + .setMoneyMultiplier(0) + .setEncounterBgm("mystery_encounter_weird_dream") + .setBattleBgm("mystery_encounter_weird_dream") + .setMixedBattleBgm("mystery_encounter_weird_dream") + .setVictoryBgm("mystery_encounter_weird_dream") + .setLocalizedName("Future Self M") + .setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)), + [TrainerType.FUTURE_SELF_F]: new TrainerConfig(++t) + .setMoneyMultiplier(0) + .setEncounterBgm("mystery_encounter_weird_dream") + .setBattleBgm("mystery_encounter_weird_dream") + .setMixedBattleBgm("mystery_encounter_weird_dream") + .setVictoryBgm("mystery_encounter_weird_dream") + .setLocalizedName("Future Self F") + .setPartyTemplates(new TrainerPartyTemplate(6, PartyMemberStrength.STRONG)) }; diff --git a/src/enums/trainer-type.ts b/src/enums/trainer-type.ts index cb7509067b5..708faf69196 100644 --- a/src/enums/trainer-type.ts +++ b/src/enums/trainer-type.ts @@ -116,6 +116,8 @@ export enum TrainerType { VITO, BUG_TYPE_SUPERFAN, EXPERT_POKEMON_BREEDER, + FUTURE_SELF_M, + FUTURE_SELF_F, BROCK = 200, MISTY, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 6afc0770a7f..93a971cc3ef 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -60,7 +60,7 @@ import { ToggleDoublePositionPhase } from "#app/phases/toggle-double-position-ph import { Challenges } from "#enums/challenges"; import { PokemonAnimType } from "#app/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/mystery-encounters/custom-pokemon-data"; import { SwitchType } from "#enums/switch-type"; import { SpeciesFormKey } from "#enums/species-form-key"; @@ -96,7 +96,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { public stats: integer[]; public ivs: integer[]; public nature: Nature; - public natureOverride: Nature | -1; public moveset: (PokemonMove | null)[]; public status: Status | null; public friendship: integer; @@ -117,7 +116,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; @@ -125,7 +124,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; @@ -196,7 +195,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; @@ -215,9 +213,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); @@ -238,7 +236,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); @@ -246,8 +244,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; @@ -596,8 +592,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; } @@ -1013,7 +1009,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 { @@ -1179,15 +1175,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]) { @@ -1199,8 +1195,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); @@ -1211,7 +1207,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]) { @@ -1269,14 +1265,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) { @@ -1299,8 +1295,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; @@ -1985,7 +1981,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(); @@ -4209,7 +4205,6 @@ export class PlayerPokemon extends Pokemon { if (newEvolution.condition?.predicate(this)) { const newPokemon = this.scene.addPlayerPokemon(this.species, this.level, this.abilityIndex, this.formIndex, undefined, this.shiny, this.variant, this.ivs, this.nature); - newPokemon.natureOverride = this.natureOverride; newPokemon.passive = this.passive; newPokemon.moveset = this.moveset.slice(); newPokemon.moveset = this.copyMoveset(); @@ -4293,7 +4288,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 7e4ef402ead..cbc842f2d9d 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1564,6 +1564,7 @@ export const modifierTypes = { VOUCHER_PREMIUM: () => new AddVoucherModifierType(VoucherType.PREMIUM, 1), GOLDEN_POKEBALL: () => new ModifierType("modifierType:ModifierType.GOLDEN_POKEBALL", "pb_gold", (type, _args) => new Modifiers.ExtraModifierModifier(type), undefined, "se/pb_bounce_1"), + SILVER_POKEBALL: () => new ModifierType("modifierType:ModifierType.SILVER_POKEBALL", "pb_silver", (type, _args) => new Modifiers.TempExtraModifierModifier(type, 100), undefined, "se/pb_bounce_1"), ENEMY_DAMAGE_BOOSTER: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_BOOSTER", "wl_item_drop", (type, _args) => new Modifiers.EnemyDamageBoosterModifier(type, 5)), ENEMY_DAMAGE_REDUCTION: () => new ModifierType("modifierType:ModifierType.ENEMY_DAMAGE_REDUCTION", "wl_guard_spec", (type, _args) => new Modifiers.EnemyDamageReducerModifier(type, 2.5)), diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 537e2327a21..fcd9cafba85 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -379,6 +379,15 @@ export abstract class LapsingPersistentModifier extends PersistentModifier { this.battleCount = this.maxBattles; } + /** + * Updates an existing modifier with a new `maxBattles` and `battleCount`. + * @param count + */ + setNewBattleCount(count: number): void { + this.maxBattles = count; + this.battleCount = count; + } + getMaxBattles(): number { return this.maxBattles; } @@ -876,7 +885,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { this.stackCount = pokemon ? pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier).length + + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length : this.stackCount; const text = scene.add.bitmapText(10, 15, "item-count", this.stackCount.toString(), 11); @@ -891,7 +900,7 @@ export class EvoTrackerModifier extends PokemonHeldItemModifier { getMaxHeldItemCount(pokemon: Pokemon): integer { this.stackCount = pokemon.evoCounter + pokemon.getHeldItems().filter(m => m instanceof DamageMoneyRewardModifier).length - + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier).length; + + pokemon.scene.findModifiers(m => m instanceof MoneyMultiplierModifier || m instanceof ExtraModifierModifier || m instanceof TempExtraModifierModifier).length; return 999; } } @@ -1944,7 +1953,7 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier { apply(args: any[]): boolean { const pokemon = args[0] as Pokemon; - pokemon.natureOverride = this.nature; + pokemon.customPokemonData.nature = this.nature; let speciesId = pokemon.species.speciesId; pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1); @@ -2900,6 +2909,69 @@ export class ExtraModifierModifier extends PersistentModifier { } } +/** + * Modifier used for timed boosts to the player's shop item rewards. + * @extends LapsingPersistentModifier + * @see {@linkcode apply} + */ +export class TempExtraModifierModifier extends LapsingPersistentModifier { + constructor(type: ModifierType, maxBattles: number, battleCount?: number, stackCount?: number) { + super(type, maxBattles, battleCount, stackCount); + } + + /** + * Goes through existing modifiers for any that match Silver Pokeball, + * which will then add the max count of the new item to the existing count of the current item. + * If no existing Silver Pokeballs are found, will add a new one. + * @param modifiers {@linkcode PersistentModifier} array of the player's modifiers + * @param _virtual N/A + * @param scene + * @returns true if the modifier was successfully added or applied, false otherwise + */ + add(modifiers: PersistentModifier[], _virtual: boolean, scene: BattleScene): boolean { + for (const modifier of modifiers) { + if (this.match(modifier)) { + const modifierInstance = modifier as TempExtraModifierModifier; + const newBattleCount = this.getMaxBattles() + modifierInstance.getBattleCount(); + + modifierInstance.setNewBattleCount(newBattleCount); + scene.playSound("se/restore"); + return true; + } + } + + modifiers.push(this); + return true; + } + + clone() { + return new TempExtraModifierModifier(this.type, this.getMaxBattles(), this.getBattleCount(), this.stackCount); + } + + match(modifier: Modifier): boolean { + return (modifier instanceof TempExtraModifierModifier); + } + + /** + * Checks if {@linkcode args} contains the necessary elements. + * @param args [1] {@linkcode Utils.NumberHolder} that holds the resulting shop item reward count + * @returns true if the shop reward number modifier applies successfully + */ + shouldApply(args: any[]): boolean { + return args && (args.length === 1) && (args[0] instanceof Utils.NumberHolder); + } + + /** + * Increases the current rewards in the battle by the stackCount. + * @param args [0] {@linkcode Utils.IntegerHolder} that holds the resulting shop item reward count + * @returns true if the shop reward number modifier applies successfully + */ + apply(args: any[]): boolean { + (args[0] as Utils.IntegerHolder).value += this.getStackCount(); + return true; + } +} + export abstract class EnemyPersistentModifier extends PersistentModifier { constructor(type: ModifierType, stackCount?: integer) { super(type, stackCount); diff --git a/src/phases/select-modifier-phase.ts b/src/phases/select-modifier-phase.ts index ba55340bd8d..a4442997bb4 100644 --- a/src/phases/select-modifier-phase.ts +++ b/src/phases/select-modifier-phase.ts @@ -1,7 +1,7 @@ import BattleScene from "#app/battle-scene"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { regenerateModifierPoolThresholds, ModifierTypeOption, ModifierType, getPlayerShopModifierTypeOptionsForWave, PokemonModifierType, FusePokemonModifierType, PokemonMoveModifierType, TmModifierType, RememberMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, ModifierPoolType, getPlayerModifierTypeOptions } from "#app/modifier/modifier-type"; -import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { ExtraModifierModifier, HealShopCostModifier, Modifier, PokemonHeldItemModifier, TempExtraModifierModifier } from "#app/modifier/modifier"; import ModifierSelectUiHandler, { SHOP_OPTIONS_ROW_LIMIT } from "#app/ui/modifier-select-ui-handler"; import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler"; import { Mode } from "#app/ui/ui"; @@ -39,6 +39,7 @@ export class SelectModifierPhase extends BattlePhase { const modifierCount = new Utils.IntegerHolder(3); if (this.isPlayer()) { this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount); + this.scene.applyModifiers(TempExtraModifierModifier, true, modifierCount); } // If custom modifiers are specified, overrides default item count @@ -260,7 +261,13 @@ export class SelectModifierPhase extends BattlePhase { // Otherwise, continue with custom multiplier multiplier = this.customModifierSettings.rerollMultiplier; } - return Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); + + const baseMultiplier = Math.min(Math.ceil(this.scene.currentBattle.waveIndex / 10) * baseValue * Math.pow(2, this.rerollCount) * multiplier, Number.MAX_SAFE_INTEGER); + + // Apply Black Sludge to reroll cost + const modifiedRerollCost = new NumberHolder(baseMultiplier); + this.scene.applyModifier(HealShopCostModifier, true, modifiedRerollCost); + return modifiedRerollCost.value; } getPoolType(): ModifierPoolType { diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 8240b6bcf84..0e551fb00a2 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/mystery-encounters/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,14 @@ export default class PokemonData { public fusionVariant: Variant; public fusionGender: Gender; public fusionLuck: integer; - public fusionMysteryEncounterPokemonData: MysteryEncounterPokemonData; + public fusionCustomPokemonData: CustomPokemonData; 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; constructor(source: Pokemon | any, forHistory: boolean = false) { const sourcePokemon = source instanceof Pokemon ? source : null; @@ -87,7 +86,6 @@ export default class PokemonData { this.stats = source.stats; this.ivs = source.ivs; this.nature = source.nature !== undefined ? source.nature : 0 as Nature; - this.natureOverride = source.natureOverride !== undefined ? source.natureOverride : -1; this.friendship = source.friendship !== undefined ? source.friendship : getPokemonSpecies(this.species).baseFriendship; this.metLevel = source.metLevel || 5; this.metBiome = source.metBiome !== undefined ? source.metBiome : -1; @@ -107,9 +105,10 @@ 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.fusionMysteryEncounterPokemonData); this.usedTMs = source.usedTMs ?? []; - this.mysteryEncounterPokemonData = new MysteryEncounterPokemonData(source.mysteryEncounterPokemonData); + this.customPokemonData = new CustomPokemonData(source.customPokemonData); if (!forHistory) { this.boss = (source instanceof EnemyPokemon && !!source.bossSegments) || (!this.player && !!source.boss); 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 54e780aba50..06492cae0d2 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 @@ -15,7 +15,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Moves } from "#enums/moves"; -import { ShinyRateBoosterModifier } from "#app/modifier/modifier"; +import { TempExtraModifierModifier } from "#app/modifier/modifier"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; const namespace = "mysteryEncounters/anOfferYouCantRefuse"; @@ -100,7 +100,7 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { expect(onInitResult).toBe(true); }); - describe("Option 1 - Sell your Pokemon for money and a Shiny Charm", () => { + describe("Option 1 - Sell your Pokemon for money and a Silver Pokeball", () => { it("should have the correct properties", () => { const option = AnOfferYouCantRefuseEncounter.options[0]; expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); @@ -131,11 +131,11 @@ describe("An Offer You Can't Refuse - Mystery Encounter", () => { expect(scene.money).toBe(initialMoney + price); }); - it("Should give the player a Shiny Charm", async () => { + it("Should give the player a Silver Pokeball", async () => { await game.runToMysteryEncounter(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, defaultParty); await runMysteryEncounterToEnd(game, 1); - const itemModifier = scene.findModifier(m => m instanceof ShinyRateBoosterModifier) as ShinyRateBoosterModifier; + const itemModifier = scene.findModifier(m => m instanceof TempExtraModifierModifier) as TempExtraModifierModifier; expect(itemModifier).toBeDefined(); expect(itemModifier?.stackCount).toBe(1); 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 80434df2f44..e8c78eb09af 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -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/delibirdy-encounter.test.ts b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index e7a7c0bd50d..6003f2737f4 100644 --- a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -206,7 +206,7 @@ describe("Delibird-y - Mystery Encounter", () => { expect(candyJarAfter?.stackCount).toBe(1); }); - it("Should remove Reviver Seed and give the player a Healing Charm", async () => { + it("Should remove Reviver Seed and give the player a Berry Pouch", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Reviver Seed on party lead @@ -220,11 +220,11 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1}); const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); + const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); expect(reviverSeedAfter).toBeUndefined(); - expect(healingCharmAfter).toBeDefined(); - expect(healingCharmAfter?.stackCount).toBe(1); + expect(berryPouchAfter).toBeDefined(); + expect(berryPouchAfter?.stackCount).toBe(1); }); it("Should give the player a Shell Bell if they have max stacks of Candy Jars", async () => { @@ -256,13 +256,13 @@ describe("Delibird-y - Mystery Encounter", () => { expect(shellBellAfter?.stackCount).toBe(1); }); - it("Should give the player a Shell Bell if they have max stacks of Healing Charms", async () => { + it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); - // 5 Healing Charms + // 3 Berry Pouches scene.modifiers = []; - const healingCharm = generateModifierType(scene, modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; - healingCharm.stackCount = 5; + const healingCharm = generateModifierType(scene, modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; + healingCharm.stackCount = 3; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Reviver Seed on party lead @@ -275,12 +275,12 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 2, { pokemonNo: 1, optionNo: 1}); const reviverSeedAfter = scene.findModifier(m => m instanceof PokemonInstantReviveModifier); - const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); + const healingCharmAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); expect(reviverSeedAfter).toBeUndefined(); expect(healingCharmAfter).toBeDefined(); - expect(healingCharmAfter?.stackCount).toBe(5); + expect(healingCharmAfter?.stackCount).toBe(3); expect(shellBellAfter).toBeDefined(); expect(shellBellAfter?.stackCount).toBe(1); }); @@ -347,7 +347,7 @@ describe("Delibird-y - Mystery Encounter", () => { }); }); - it("Should decrease held item stacks and give the player a Berry Pouch", async () => { + it("Should decrease held item stacks and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 2 Soul Dew on party lead @@ -361,14 +361,14 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1}); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); + const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); expect(soulDewAfter?.stackCount).toBe(1); - expect(berryPouchAfter).toBeDefined(); - expect(berryPouchAfter?.stackCount).toBe(1); + expect(healingCharmAfter).toBeDefined(); + expect(healingCharmAfter?.stackCount).toBe(1); }); - it("Should remove held item and give the player a Berry Pouch", async () => { + it("Should remove held item and give the player a Healing Charm", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Soul Dew on party lead @@ -382,20 +382,20 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1}); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); + const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); expect(soulDewAfter).toBeUndefined(); - expect(berryPouchAfter).toBeDefined(); - expect(berryPouchAfter?.stackCount).toBe(1); + expect(healingCharmAfter).toBeDefined(); + expect(healingCharmAfter?.stackCount).toBe(1); }); - it("Should give the player a Shell Bell if they have max stacks of Berry Pouches", async () => { + it("Should give the player a Shell Bell if they have max stacks of Healing Charms", async () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // 5 Healing Charms scene.modifiers = []; - const healingCharm = generateModifierType(scene, modifierTypes.BERRY_POUCH)!.newModifier() as PreserveBerryModifier; - healingCharm.stackCount = 3; + const healingCharm = generateModifierType(scene, modifierTypes.HEALING_CHARM)!.newModifier() as HealingBoosterModifier; + healingCharm.stackCount = 5; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Soul Dew on party lead @@ -408,12 +408,12 @@ describe("Delibird-y - Mystery Encounter", () => { await runMysteryEncounterToEnd(game, 3, { pokemonNo: 1, optionNo: 1}); const soulDewAfter = scene.findModifier(m => m instanceof PokemonNatureWeightModifier); - const berryPouchAfter = scene.findModifier(m => m instanceof PreserveBerryModifier); + const healingCharmAfter = scene.findModifier(m => m instanceof HealingBoosterModifier); const shellBellAfter = scene.findModifier(m => m instanceof HitHealModifier); expect(soulDewAfter).toBeUndefined(); - expect(berryPouchAfter).toBeDefined(); - expect(berryPouchAfter?.stackCount).toBe(3); + expect(healingCharmAfter).toBeDefined(); + expect(healingCharmAfter?.stackCount).toBe(5); expect(shellBellAfter).toBeDefined(); expect(shellBellAfter?.stackCount).toBe(1); }); 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 24c2e083a72..ae556ccebb6 100644 --- a/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/fiery-fallout-encounter.test.ts @@ -12,7 +12,7 @@ import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encount import { runMysteryEncounterToEnd, runSelectMysteryEncounterOption, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import { Moves } from "#enums/moves"; import BattleScene from "#app/battle-scene"; -import { PokemonHeldItemModifier } from "#app/modifier/modifier"; +import { AttackTypeBoosterModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { Type } from "#app/data/type"; import { Status, StatusEffect } from "#app/data/status-effect"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; @@ -22,6 +22,8 @@ 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 { BattlerTagType } from "#enums/battler-tag-type"; +import { Abilities } from "#enums/abilities"; const namespace = "mysteryEncounters/fieryFallout"; /** Arcanine and Ninetails for 2 Fire types. Lapras, Gengar, Abra for burnable mon. */ @@ -41,10 +43,11 @@ describe("Fiery Fallout - 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() + .moveset([Moves.PAYBACK, Moves.THUNDERBOLT]); // Required for attack type booster item generation vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue( new Map([ @@ -108,12 +111,16 @@ describe("Fiery Fallout - Mystery Encounter", () => { { species: getPokemonSpecies(Species.VOLCARONA), isBoss: false, - gender: Gender.MALE + gender: Gender.MALE, + tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], + mysteryEncounterBattleEffects: expect.any(Function) }, { species: getPokemonSpecies(Species.VOLCARONA), isBoss: false, - gender: Gender.FEMALE + gender: Gender.FEMALE, + tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], + mysteryEncounterBattleEffects: expect.any(Function) } ], doubleBattle: true, @@ -156,12 +163,11 @@ describe("Fiery Fallout - Mystery Encounter", () => { expect(enemyField[0].gender).not.toEqual(enemyField[1].gender); // Should be opposite gender const movePhases = phaseSpy.mock.calls.filter(p => p[0] instanceof MovePhase).map(p => p[0]); - expect(movePhases.length).toBe(4); + expect(movePhases.length).toBe(2); expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.FIRE_SPIN).length).toBe(2); // Fire spin used twice before battle - expect(movePhases.filter(p => (p as MovePhase).move.moveId === Moves.QUIVER_DANCE).length).toBe(2); // Quiver Dance used twice before battle }); - it("should give charcoal to lead pokemon", async () => { + it("should give attack type boosting item to lead pokemon", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await runMysteryEncounterToEnd(game, 1, undefined, true); await skipBattleRunMysteryEncounterRewardsPhase(game); @@ -171,8 +177,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { const leadPokemonId = scene.getParty()?.[0].id; const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[]; - const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal"); - expect(charcoal).toBeDefined; + const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); + expect(item).toBeDefined; }); }); @@ -192,7 +198,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { }); }); - it("should damage all non-fire party PKM by 20% and randomly burn 1", async () => { + it("should damage all non-fire party PKM by 20%, and burn + give Heatproof to a random Pokemon", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); const party = scene.getParty(); @@ -209,7 +215,8 @@ describe("Fiery Fallout - Mystery Encounter", () => { 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)); }); - expect(burnablePokemon.some(pkm => pkm?.status?.effect === StatusEffect.BURN)).toBeTruthy(); + expect(burnablePokemon.some(pkm => pkm.status?.effect === StatusEffect.BURN)).toBeTruthy(); + expect(burnablePokemon.some(pkm => pkm.customPokemonData.ability === Abilities.HEATPROOF)); notBurnablePokemon.forEach((pkm) => expect(pkm.hp, `${pkm.name} should be full hp: ${pkm.hp} / ${pkm.getMaxHp()} HP`).toBe(pkm.getMaxHp())); }); @@ -240,17 +247,15 @@ describe("Fiery Fallout - Mystery Encounter", () => { }); }); - it("should give charcoal to lead pokemon", async () => { + it("should give attack type boosting item to lead pokemon", async () => { await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, defaultParty); await runMysteryEncounterToEnd(game, 3); await game.phaseInterceptor.to(SelectModifierPhase, false); expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); - const leadPokemonId = scene.getParty()?.[0].id; - const leadPokemonItems = scene.findModifiers(m => m instanceof PokemonHeldItemModifier - && (m as PokemonHeldItemModifier).pokemonId === leadPokemonId, true) as PokemonHeldItemModifier[]; - const charcoal = leadPokemonItems.find(i => i.type.name === "Charcoal"); - expect(charcoal).toBeDefined; + const leadPokemonItems = scene.getParty()?.[0].getHeldItems() as PokemonHeldItemModifier[]; + const item = leadPokemonItems.find(i => i instanceof AttackTypeBoosterModifier); + expect(item).toBeDefined; }); it("should leave encounter without battle", async () => { @@ -263,7 +268,7 @@ describe("Fiery Fallout - Mystery Encounter", () => { }); it("should be disabled if not enough FIRE types are in party", async () => { - await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, [Species.MAGIKARP, Species.ARCANINE]); + await game.runToMysteryEncounter(MysteryEncounterType.FIERY_FALLOUT, [Species.MAGIKARP]); await game.phaseInterceptor.to(MysteryEncounterPhase, false); const encounterPhase = scene.getCurrentPhase(); 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 3df2f35f02b..927e5cf0b02 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/mystery-encounters/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 }), + mysteryEncounterPokemonData: 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/trash-to-treasure-encounter.test.ts b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts index 7e92d17ba25..9120632f9e2 100644 --- a/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/trash-to-treasure-encounter.test.ts @@ -86,7 +86,7 @@ describe("Trash to Treasure - Mystery Encounter", () => { expect(TrashToTreasureEncounter.enemyPartyConfigs).toEqual([ { - levelAdditiveModifier: 1, + levelAdditiveModifier: 0.5, disableSwitch: true, pokemonConfigs: [ { 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 fdf9634e383..346e4235e6b 100644 --- a/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/weird-dream-encounter.test.ts @@ -5,7 +5,7 @@ import { Species } from "#app/enums/species"; import GameManager from "#app/test/utils/gameManager"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounter-test-utils"; +import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounter-test-utils"; import BattleScene from "#app/battle-scene"; import { Mode } from "#app/ui/ui"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; @@ -15,6 +15,8 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-encounter"; import * as EncounterTransformationSequence from "#app/data/mystery-encounters/utils/encounter-transformation-sequence"; import { SelectModifierPhase } from "#app/phases/select-modifier-phase"; +import { CommandPhase } from "#app/phases/command-phase"; +import { ModifierTier } from "#app/modifier/modifier-tier"; const namespace = "mysteryEncounters/weirdDream"; const defaultParty = [Species.MAGBY, Species.HAUNTER, Species.ABRA]; @@ -70,7 +72,7 @@ describe("Weird Dream - Mystery Encounter", () => { expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.title).toBe(`${namespace}:title`); expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.description).toBe(`${namespace}:description`); expect(WeirdDreamEncounter.dialogue.encounterOptionsDialogue?.query).toBe(`${namespace}:query`); - expect(WeirdDreamEncounter.options.length).toBe(2); + expect(WeirdDreamEncounter.options.length).toBe(3); }); it("should initialize fully", async () => { @@ -122,7 +124,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); @@ -132,7 +134,7 @@ describe("Weird Dream - Mystery Encounter", () => { expect(plus40To50.length).toBe(1); }); - it("should have 1 Memory Mushroom, 5 Rogue Balls, and 2 Mints in rewards", async () => { + it("should have 1 Memory Mushroom, 5 Rogue Balls, and 3 Mints in rewards", async () => { await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); await runMysteryEncounterToEnd(game, 1); await game.phaseInterceptor.to(SelectModifierPhase, false); @@ -141,11 +143,12 @@ describe("Weird Dream - 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(4); + expect(modifierSelectHandler.options.length).toEqual(5); expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toEqual("MEMORY_MUSHROOM"); expect(modifierSelectHandler.options[1].modifierTypeOption.type.id).toEqual("ROGUE_BALL"); expect(modifierSelectHandler.options[2].modifierTypeOption.type.id).toEqual("MINT"); expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("MINT"); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.id).toEqual("MINT"); }); it("should leave encounter without battle", async () => { @@ -158,7 +161,7 @@ describe("Weird Dream - Mystery Encounter", () => { }); }); - describe("Option 2 - Leave", () => { + describe("Option 2 - Battle Future Self", () => { it("should have the correct properties", () => { const option = WeirdDreamEncounter.options[1]; expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); @@ -174,17 +177,63 @@ describe("Weird Dream - Mystery Encounter", () => { }); }); - it("should reduce party levels by 12.5%", async () => { + it("should start a battle against the player's transformation team", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); + await runMysteryEncounterToEnd(game, 2, undefined, true); + + const enemyField = scene.getEnemyField(); + expect(scene.getCurrentPhase()?.constructor.name).toBe(CommandPhase.name); + expect(enemyField.length).toBe(1); + expect(scene.getEnemyParty().length).toBe(scene.getParty().length); + }); + + it("should have 2 Rogue/2 Ultra/2 Great items in rewards", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); + await runMysteryEncounterToEnd(game, 2, undefined, true); + await skipBattleRunMysteryEncounterRewardsPhase(game); + await game.phaseInterceptor.to(SelectModifierPhase, false); + expect(scene.getCurrentPhase()?.constructor.name).toBe(SelectModifierPhase.name); + await game.phaseInterceptor.run(SelectModifierPhase); + + 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(6); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.tier - modifierSelectHandler.options[0].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ROGUE); + expect(modifierSelectHandler.options[1].modifierTypeOption.type.tier - modifierSelectHandler.options[1].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ROGUE); + expect(modifierSelectHandler.options[2].modifierTypeOption.type.tier - modifierSelectHandler.options[2].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ULTRA); + expect(modifierSelectHandler.options[3].modifierTypeOption.type.tier - modifierSelectHandler.options[3].modifierTypeOption.upgradeCount).toEqual(ModifierTier.ULTRA); + expect(modifierSelectHandler.options[4].modifierTypeOption.type.tier - modifierSelectHandler.options[4].modifierTypeOption.upgradeCount).toEqual(ModifierTier.GREAT); + expect(modifierSelectHandler.options[5].modifierTypeOption.type.tier - modifierSelectHandler.options[5].modifierTypeOption.upgradeCount).toEqual(ModifierTier.GREAT); + }); + }); + + describe("Option 3 - Leave", () => { + it("should have the correct properties", () => { + const option = WeirdDreamEncounter.options[2]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}:option.3.label`, + buttonTooltip: `${namespace}:option.3.tooltip`, + selected: [ + { + text: `${namespace}:option.3.selected`, + }, + ], + }); + }); + + it("should reduce party levels by 10%", async () => { const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); const levelsPrior = scene.getParty().map(p => p.level); - await runMysteryEncounterToEnd(game, 2); + await runMysteryEncounterToEnd(game, 3); const levelsAfter = scene.getParty().map(p => p.level); for (let i = 0; i < levelsPrior.length; i++) { - expect(Math.max(Math.ceil(0.8875 * levelsPrior[i]), 1)).toBe(levelsAfter[i]); + expect(Math.max(Math.ceil(0.9 * levelsPrior[i]), 1)).toBe(levelsAfter[i]); expect(scene.getParty()[i].levelExp).toBe(0); } @@ -195,7 +244,7 @@ describe("Weird Dream - Mystery Encounter", () => { const leaveEncounterWithoutBattleSpy = vi.spyOn(EncounterPhaseUtils, "leaveEncounterWithoutBattle"); await game.runToMysteryEncounter(MysteryEncounterType.WEIRD_DREAM, defaultParty); - await runMysteryEncounterToEnd(game, 2); + await runMysteryEncounterToEnd(game, 3); expect(leaveEncounterWithoutBattleSpy).toBeCalled(); });