diff --git a/public/images/items.json b/public/images/items.json index 01d28e79e58..5ac69059d64 100644 --- a/public/images/items.json +++ b/public/images/items.json @@ -3348,6 +3348,27 @@ "h": 23 } }, + { + "filename": "macho_brace", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 4, + "y": 5, + "w": 23, + "h": 23 + }, + "frame": { + "x": 162, + "y": 400, + "w": 23, + "h": 23 + } + }, { "filename": "mystic_water", "rotated": false, @@ -3363,8 +3384,8 @@ "h": 23 }, "frame": { - "x": 162, - "y": 400, + "x": 166, + "y": 263, "w": 20, "h": 23 } @@ -3383,27 +3404,6 @@ "w": 22, "h": 23 }, - "frame": { - "x": 166, - "y": 263, - "w": 22, - "h": 23 - } - }, - { - "filename": "normal_tera_shard", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 4, - "w": 22, - "h": 23 - }, "frame": { "x": 168, "y": 286, @@ -3432,6 +3432,27 @@ "h": 22 } }, + { + "filename": "normal_tera_shard", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 4, + "w": 22, + "h": 23 + }, + "frame": { + "x": 179, + "y": 331, + "w": 22, + "h": 23 + } + }, { "filename": "petaya_berry", "rotated": false, @@ -3446,27 +3467,6 @@ "w": 22, "h": 23 }, - "frame": { - "x": 179, - "y": 331, - "w": 22, - "h": 23 - } - }, - { - "filename": "poison_tera_shard", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 4, - "w": 22, - "h": 23 - }, "frame": { "x": 179, "y": 354, @@ -3475,7 +3475,7 @@ } }, { - "filename": "psychic_tera_shard", + "filename": "poison_tera_shard", "rotated": false, "trimmed": true, "sourceSize": { @@ -3496,7 +3496,7 @@ } }, { - "filename": "rare_candy", + "filename": "psychic_tera_shard", "rotated": false, "trimmed": true, "sourceSize": { @@ -3504,15 +3504,15 @@ "h": 32 }, "spriteSourceSize": { - "x": 4, - "y": 5, - "w": 23, + "x": 6, + "y": 4, + "w": 22, "h": 23 }, "frame": { - "x": 182, + "x": 185, "y": 400, - "w": 23, + "w": 22, "h": 23 } }, @@ -3538,7 +3538,7 @@ } }, { - "filename": "rarer_candy", + "filename": "rare_candy", "rotated": false, "trimmed": true, "sourceSize": { @@ -3558,6 +3558,27 @@ "h": 23 } }, + { + "filename": "rarer_candy", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 4, + "y": 5, + "w": 23, + "h": 23 + }, + "frame": { + "x": 161, + "y": 64, + "w": 23, + "h": 23 + } + }, { "filename": "healing_charm", "rotated": false, @@ -3574,7 +3595,7 @@ }, "frame": { "x": 161, - "y": 64, + "y": 87, "w": 23, "h": 22 } @@ -3595,7 +3616,7 @@ }, "frame": { "x": 161, - "y": 86, + "y": 109, "w": 22, "h": 23 } @@ -3615,14 +3636,14 @@ "h": 23 }, "frame": { - "x": 161, - "y": 109, + "x": 163, + "y": 132, "w": 22, "h": 23 } }, { - "filename": "sharp_beak", + "filename": "deep_sea_tooth", "rotated": false, "trimmed": true, "sourceSize": { @@ -3631,35 +3652,14 @@ }, "spriteSourceSize": { "x": 5, - "y": 5, - "w": 21, - "h": 23 - }, - "frame": { - "x": 163, - "y": 132, - "w": 21, - "h": 23 - } - }, - { - "filename": "dawn_stone", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, "y": 6, - "w": 20, + "w": 22, "h": 21 }, "frame": { "x": 164, "y": 155, - "w": 20, + "w": 22, "h": 21 } }, @@ -3741,7 +3741,7 @@ "h": 23 }, "frame": { - "x": 183, + "x": 184, "y": 86, "w": 22, "h": 23 @@ -3769,7 +3769,7 @@ } }, { - "filename": "whipped_dream", + "filename": "sharp_beak", "rotated": false, "trimmed": true, "sourceSize": { @@ -3778,12 +3778,12 @@ }, "spriteSourceSize": { "x": 5, - "y": 4, + "y": 5, "w": 21, "h": 23 }, "frame": { - "x": 184, + "x": 185, "y": 132, "w": 21, "h": 23 @@ -3867,54 +3867,12 @@ "h": 22 }, "frame": { - "x": 205, + "x": 206, "y": 86, "w": 22, "h": 22 } }, - { - "filename": "charcoal", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 205, - "y": 108, - "w": 22, - "h": 22 - } - }, - { - "filename": "dark_memory", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 205, - "y": 130, - "w": 22, - "h": 22 - } - }, { "filename": "moon_stone", "rotated": false, @@ -4000,7 +3958,7 @@ } }, { - "filename": "deep_sea_tooth", + "filename": "charcoal", "rotated": false, "trimmed": true, "sourceSize": { @@ -4009,19 +3967,40 @@ }, "spriteSourceSize": { "x": 5, - "y": 6, + "y": 5, "w": 22, - "h": 21 + "h": 22 }, "frame": { - "x": 184, + "x": 186, "y": 155, "w": 22, - "h": 21 + "h": 22 } }, { - "filename": "relic_crown", + "filename": "mystery_egg", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 18 + }, + "frame": { + "x": 170, + "y": 176, + "w": 16, + "h": 18 + } + }, + { + "filename": "black_glasses", "rotated": false, "trimmed": true, "sourceSize": { @@ -4030,19 +4009,19 @@ }, "spriteSourceSize": { "x": 4, - "y": 7, + "y": 8, "w": 23, - "h": 18 + "h": 17 }, "frame": { - "x": 170, - "y": 176, + "x": 186, + "y": 177, "w": 23, - "h": 18 + "h": 17 } }, { - "filename": "sachet", + "filename": "whipped_dream", "rotated": false, "trimmed": true, "sourceSize": { @@ -4050,18 +4029,39 @@ "h": 32 }, "spriteSourceSize": { - "x": 6, + "x": 5, "y": 4, - "w": 18, + "w": 21, "h": 23 }, "frame": { "x": 175, "y": 194, - "w": 18, + "w": 21, "h": 23 } }, + { + "filename": "dark_memory", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 176, + "y": 217, + "w": 22, + "h": 22 + } + }, { "filename": "dire_hit", "rotated": false, @@ -4077,54 +4077,12 @@ "h": 22 }, "frame": { - "x": 206, - "y": 152, + "x": 176, + "y": 239, "w": 22, "h": 22 } }, - { - "filename": "potion", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 17, - "h": 23 - }, - "frame": { - "x": 176, - "y": 217, - "w": 17, - "h": 23 - } - }, - { - "filename": "super_potion", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 5, - "w": 17, - "h": 23 - }, - "frame": { - "x": 176, - "y": 240, - "w": 17, - "h": 23 - } - }, { "filename": "dna_splicers", "rotated": false, @@ -4245,7 +4203,7 @@ "h": 22 }, "frame": { - "x": 227, + "x": 228, "y": 86, "w": 22, "h": 22 @@ -4287,8 +4245,8 @@ "h": 22 }, "frame": { - "x": 227, - "y": 108, + "x": 250, + "y": 86, "w": 22, "h": 22 } @@ -4308,8 +4266,8 @@ "h": 22 }, "frame": { - "x": 227, - "y": 130, + "x": 272, + "y": 64, "w": 22, "h": 22 } @@ -4329,7 +4287,7 @@ "h": 22 }, "frame": { - "x": 249, + "x": 272, "y": 86, "w": 22, "h": 22 @@ -4350,7 +4308,7 @@ "h": 22 }, "frame": { - "x": 272, + "x": 294, "y": 64, "w": 22, "h": 22 @@ -4371,8 +4329,8 @@ "h": 22 }, "frame": { - "x": 249, - "y": 108, + "x": 294, + "y": 86, "w": 22, "h": 22 } @@ -4391,258 +4349,6 @@ "w": 22, "h": 22 }, - "frame": { - "x": 271, - "y": 86, - "w": 22, - "h": 22 - } - }, - { - "filename": "guard_spec", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 294, - "y": 64, - "w": 22, - "h": 22 - } - }, - { - "filename": "ice_memory", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 249, - "y": 130, - "w": 22, - "h": 22 - } - }, - { - "filename": "ice_stone", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 228, - "y": 152, - "w": 22, - "h": 22 - } - }, - { - "filename": "magmarizer", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 271, - "y": 108, - "w": 22, - "h": 22 - } - }, - { - "filename": "mini_black_hole", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 293, - "y": 86, - "w": 22, - "h": 22 - } - }, - { - "filename": "poison_memory", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 271, - "y": 130, - "w": 22, - "h": 22 - } - }, - { - "filename": "protector", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 250, - "y": 152, - "w": 22, - "h": 22 - } - }, - { - "filename": "psychic_memory", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 293, - "y": 108, - "w": 22, - "h": 22 - } - }, - { - "filename": "rock_memory", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 293, - "y": 130, - "w": 22, - "h": 22 - } - }, - { - "filename": "scroll_of_darkness", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 272, - "y": 152, - "w": 22, - "h": 22 - } - }, - { - "filename": "scroll_of_waters", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 294, - "y": 152, - "w": 22, - "h": 22 - } - }, - { - "filename": "shed_shell", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, "frame": { "x": 317, "y": 42, @@ -4651,7 +4357,7 @@ } }, { - "filename": "starf_berry", + "filename": "guard_spec", "rotated": false, "trimmed": true, "sourceSize": { @@ -4672,28 +4378,7 @@ } }, { - "filename": "steel_memory", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 315, - "y": 86, - "w": 22, - "h": 22 - } - }, - { - "filename": "thick_club", + "filename": "ice_memory", "rotated": false, "trimmed": true, "sourceSize": { @@ -4714,7 +4399,28 @@ } }, { - "filename": "thunder_stone", + "filename": "ice_stone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 316, + "y": 86, + "w": 22, + "h": 22 + } + }, + { + "filename": "magmarizer", "rotated": false, "trimmed": true, "sourceSize": { @@ -4735,133 +4441,7 @@ } }, { - "filename": "tm_bug", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 315, - "y": 108, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_dark", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 315, - "y": 130, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_dragon", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 337, - "y": 86, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_electric", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 337, - "y": 108, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_fairy", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 337, - "y": 130, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_fighting", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 316, - "y": 152, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_fire", + "filename": "mini_black_hole", "rotated": false, "trimmed": true, "sourceSize": { @@ -4876,7 +4456,7 @@ }, "frame": { "x": 338, - "y": 152, + "y": 86, "w": 22, "h": 22 } @@ -4924,7 +4504,7 @@ } }, { - "filename": "tm_flying", + "filename": "poison_memory", "rotated": false, "trimmed": true, "sourceSize": { @@ -4945,70 +4525,7 @@ } }, { - "filename": "tm_ghost", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 359, - "y": 86, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_grass", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 359, - "y": 108, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_ground", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 359, - "y": 130, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_ice", + "filename": "protector", "rotated": false, "trimmed": true, "sourceSize": { @@ -5023,11 +4540,74 @@ }, "frame": { "x": 360, - "y": 152, + "y": 86, "w": 22, "h": 22 } }, + { + "filename": "sachet", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 4, + "w": 18, + "h": 23 + }, + "frame": { + "x": 382, + "y": 64, + "w": 18, + "h": 23 + } + }, + { + "filename": "psychic_memory", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 400, + "y": 62, + "w": 22, + "h": 22 + } + }, + { + "filename": "dawn_stone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 20, + "h": 21 + }, + "frame": { + "x": 382, + "y": 87, + "w": 20, + "h": 21 + } + }, { "filename": "hard_meteorite", "rotated": false, @@ -5043,14 +4623,14 @@ "h": 22 }, "frame": { - "x": 382, - "y": 64, + "x": 402, + "y": 84, "w": 20, "h": 22 } }, { - "filename": "tm_normal", + "filename": "potion", "rotated": false, "trimmed": true, "sourceSize": { @@ -5058,20 +4638,20 @@ "h": 32 }, "spriteSourceSize": { - "x": 5, + "x": 8, "y": 5, - "w": 22, - "h": 22 + "w": 17, + "h": 23 }, "frame": { - "x": 381, - "y": 86, - "w": 22, - "h": 22 + "x": 196, + "y": 194, + "w": 17, + "h": 23 } }, { - "filename": "tm_poison", + "filename": "super_potion", "rotated": false, "trimmed": true, "sourceSize": { @@ -5079,142 +4659,16 @@ "h": 32 }, "spriteSourceSize": { - "x": 5, + "x": 8, "y": 5, - "w": 22, - "h": 22 + "w": 17, + "h": 23 }, "frame": { - "x": 381, - "y": 108, - "w": 22, - "h": 22 - } - }, - { - "filename": "tm_psychic", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 381, - "y": 130, - "w": 22, - "h": 22 - } - }, - { - "filename": "reviver_seed", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 8, - "w": 23, - "h": 20 - }, - "frame": { - "x": 403, - "y": 62, - "w": 23, - "h": 20 - } - }, - { - "filename": "shell_bell", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 7, - "w": 23, - "h": 20 - }, - "frame": { - "x": 403, - "y": 82, - "w": 23, - "h": 20 - } - }, - { - "filename": "coupon", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 4, - "y": 7, - "w": 23, - "h": 19 - }, - "frame": { - "x": 403, - "y": 102, - "w": 23, - "h": 19 - } - }, - { - "filename": "golden_mystic_ticket", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 4, - "y": 7, - "w": 23, - "h": 19 - }, - "frame": { - "x": 403, - "y": 121, - "w": 23, - "h": 19 - } - }, - { - "filename": "mystic_ticket", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 4, - "y": 7, - "w": 23, - "h": 19 - }, - "frame": { - "x": 403, - "y": 140, - "w": 23, - "h": 19 + "x": 198, + "y": 217, + "w": 17, + "h": 23 } }, { @@ -5232,14 +4686,14 @@ "h": 21 }, "frame": { - "x": 382, - "y": 152, + "x": 198, + "y": 240, "w": 21, "h": 21 } }, { - "filename": "pair_of_tickets", + "filename": "rock_memory", "rotated": false, "trimmed": true, "sourceSize": { @@ -5247,16 +4701,37 @@ "h": 32 }, "spriteSourceSize": { - "x": 4, - "y": 7, - "w": 23, - "h": 19 + "x": 5, + "y": 5, + "w": 22, + "h": 22 }, "frame": { - "x": 403, - "y": 159, - "w": 23, - "h": 19 + "x": 186, + "y": 261, + "w": 22, + "h": 22 + } + }, + { + "filename": "metal_coat", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 5, + "w": 19, + "h": 22 + }, + "frame": { + "x": 190, + "y": 283, + "w": 19, + "h": 22 } }, { @@ -5274,14 +4749,14 @@ "h": 22 }, "frame": { - "x": 193, - "y": 176, + "x": 208, + "y": 261, "w": 15, "h": 22 } }, { - "filename": "tm_rock", + "filename": "scroll_of_darkness", "rotated": false, "trimmed": true, "sourceSize": { @@ -5295,14 +4770,14 @@ "h": 22 }, "frame": { - "x": 208, - "y": 174, + "x": 209, + "y": 283, "w": 22, "h": 22 } }, { - "filename": "tm_steel", + "filename": "scroll_of_waters", "rotated": false, "trimmed": true, "sourceSize": { @@ -5316,14 +4791,14 @@ "h": 22 }, "frame": { - "x": 230, - "y": 174, + "x": 193, + "y": 305, "w": 22, "h": 22 } }, { - "filename": "tm_water", + "filename": "shed_shell", "rotated": false, "trimmed": true, "sourceSize": { @@ -5337,14 +4812,14 @@ "h": 22 }, "frame": { - "x": 252, - "y": 174, + "x": 215, + "y": 305, "w": 22, "h": 22 } }, { - "filename": "water_memory", + "filename": "starf_berry", "rotated": false, "trimmed": true, "sourceSize": { @@ -5358,14 +4833,14 @@ "h": 22 }, "frame": { - "x": 274, - "y": 174, + "x": 201, + "y": 327, "w": 22, "h": 22 } }, { - "filename": "water_stone", + "filename": "steel_memory", "rotated": false, "trimmed": true, "sourceSize": { @@ -5379,50 +4854,8 @@ "h": 22 }, "frame": { - "x": 296, - "y": 174, - "w": 22, - "h": 22 - } - }, - { - "filename": "x_accuracy", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 318, - "y": 174, - "w": 22, - "h": 22 - } - }, - { - "filename": "x_attack", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 5, - "w": 22, - "h": 22 - }, - "frame": { - "x": 340, - "y": 174, + "x": 201, + "y": 349, "w": 22, "h": 22 } @@ -5442,77 +4875,14 @@ "h": 22 }, "frame": { - "x": 362, - "y": 174, + "x": 223, + "y": 327, "w": 20, "h": 22 } }, { - "filename": "poison_barb", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 21, - "h": 21 - }, - "frame": { - "x": 382, - "y": 173, - "w": 21, - "h": 21 - } - }, - { - "filename": "black_glasses", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 4, - "y": 8, - "w": 23, - "h": 17 - }, - "frame": { - "x": 403, - "y": 178, - "w": 23, - "h": 17 - } - }, - { - "filename": "metal_coat", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 5, - "w": 19, - "h": 22 - }, - "frame": { - "x": 193, - "y": 198, - "w": 19, - "h": 22 - } - }, - { - "filename": "x_defense", + "filename": "thick_club", "rotated": false, "trimmed": true, "sourceSize": { @@ -5526,14 +4896,14 @@ "h": 22 }, "frame": { - "x": 212, - "y": 196, + "x": 223, + "y": 349, "w": 22, "h": 22 } }, { - "filename": "x_sp_atk", + "filename": "thunder_stone", "rotated": false, "trimmed": true, "sourceSize": { @@ -5547,14 +4917,14 @@ "h": 22 }, "frame": { - "x": 234, - "y": 196, + "x": 203, + "y": 371, "w": 22, "h": 22 } }, { - "filename": "x_sp_def", + "filename": "tm_bug", "rotated": false, "trimmed": true, "sourceSize": { @@ -5568,14 +4938,14 @@ "h": 22 }, "frame": { - "x": 256, - "y": 196, + "x": 225, + "y": 371, "w": 22, "h": 22 } }, { - "filename": "x_speed", + "filename": "tm_dark", "rotated": false, "trimmed": true, "sourceSize": { @@ -5589,14 +4959,14 @@ "h": 22 }, "frame": { - "x": 278, - "y": 196, + "x": 207, + "y": 393, "w": 22, "h": 22 } }, { - "filename": "sweet_apple", + "filename": "tm_dragon", "rotated": false, "trimmed": true, "sourceSize": { @@ -5605,19 +4975,19 @@ }, "spriteSourceSize": { "x": 5, - "y": 6, + "y": 5, "w": 22, - "h": 21 + "h": 22 }, "frame": { - "x": 300, - "y": 196, + "x": 229, + "y": 393, "w": 22, - "h": 21 + "h": 22 } }, { - "filename": "syrupy_apple", + "filename": "relic_gold", "rotated": false, "trimmed": true, "sourceSize": { @@ -5625,289 +4995,16 @@ "h": 32 }, "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 22, - "h": 21 + "x": 9, + "y": 11, + "w": 15, + "h": 11 }, "frame": { - "x": 322, - "y": 196, - "w": 22, - "h": 21 - } - }, - { - "filename": "tart_apple", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 22, - "h": 21 - }, - "frame": { - "x": 344, - "y": 196, - "w": 22, - "h": 21 - } - }, - { - "filename": "quick_claw", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 6, - "w": 19, - "h": 21 - }, - "frame": { - "x": 193, - "y": 220, - "w": 19, - "h": 21 - } - }, - { - "filename": "deep_sea_scale", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 22, - "h": 20 - }, - "frame": { - "x": 212, - "y": 218, - "w": 22, - "h": 20 - } - }, - { - "filename": "fairy_feather", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 7, - "w": 22, - "h": 20 - }, - "frame": { - "x": 234, - "y": 218, - "w": 22, - "h": 20 - } - }, - { - "filename": "malicious_armor", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 22, - "h": 20 - }, - "frame": { - "x": 256, - "y": 218, - "w": 22, - "h": 20 - } - }, - { - "filename": "tera_orb", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 22, - "h": 20 - }, - "frame": { - "x": 278, - "y": 218, - "w": 22, - "h": 20 - } - }, - { - "filename": "shiny_stone", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 21, - "h": 21 - }, - "frame": { - "x": 300, - "y": 217, - "w": 21, - "h": 21 - } - }, - { - "filename": "zoom_lens", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 21, - "h": 21 - }, - "frame": { - "x": 321, - "y": 217, - "w": 21, - "h": 21 - } - }, - { - "filename": "black_sludge", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 24, - "h": 24 - }, - "spriteSourceSize": { - "x": 1, - "y": 2, - "w": 22, - "h": 19 - }, - "frame": { - "x": 342, - "y": 217, - "w": 22, - "h": 19 - } - }, - { - "filename": "spell_tag", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 19, - "h": 21 - }, - "frame": { - "x": 193, - "y": 241, - "w": 19, - "h": 21 - } - }, - { - "filename": "blunder_policy", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 6, - "w": 22, - "h": 19 - }, - "frame": { - "x": 212, - "y": 238, - "w": 22, - "h": 19 - } - }, - { - "filename": "dubious_disc", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 7, - "w": 22, - "h": 19 - }, - "frame": { - "x": 234, - "y": 238, - "w": 22, - "h": 19 - } - }, - { - "filename": "upgrade", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, - "y": 7, - "w": 22, - "h": 19 - }, - "frame": { - "x": 256, - "y": 238, - "w": 22, - "h": 19 + "x": 207, + "y": 415, + "w": 15, + "h": 11 } }, { @@ -5925,8 +5022,8 @@ "h": 17 }, "frame": { - "x": 278, - "y": 238, + "x": 206, + "y": 108, "w": 23, "h": 17 } @@ -5946,14 +5043,14 @@ "h": 17 }, "frame": { - "x": 301, - "y": 238, + "x": 229, + "y": 108, "w": 23, "h": 17 } }, { - "filename": "soothe_bell", + "filename": "coupon", "rotated": false, "trimmed": true, "sourceSize": { @@ -5961,15 +5058,372 @@ "h": 32 }, "spriteSourceSize": { - "x": 8, + "x": 4, + "y": 7, + "w": 23, + "h": 19 + }, + "frame": { + "x": 252, + "y": 108, + "w": 23, + "h": 19 + } + }, + { + "filename": "golden_mystic_ticket", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 4, + "y": 7, + "w": 23, + "h": 19 + }, + "frame": { + "x": 275, + "y": 108, + "w": 23, + "h": 19 + } + }, + { + "filename": "mystic_ticket", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 4, + "y": 7, + "w": 23, + "h": 19 + }, + "frame": { + "x": 298, + "y": 108, + "w": 23, + "h": 19 + } + }, + { + "filename": "pair_of_tickets", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 4, + "y": 7, + "w": 23, + "h": 19 + }, + "frame": { + "x": 321, + "y": 108, + "w": 23, + "h": 19 + } + }, + { + "filename": "reviver_seed", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 8, + "w": 23, + "h": 20 + }, + "frame": { + "x": 344, + "y": 108, + "w": 23, + "h": 20 + } + }, + { + "filename": "shell_bell", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 7, + "w": 23, + "h": 20 + }, + "frame": { + "x": 367, + "y": 108, + "w": 23, + "h": 20 + } + }, + { + "filename": "tm_electric", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, "y": 5, - "w": 17, + "w": 22, "h": 22 }, "frame": { - "x": 366, - "y": 196, - "w": 17, + "x": 206, + "y": 125, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_fairy", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 228, + "y": 125, + "w": 22, + "h": 22 + } + }, + { + "filename": "deep_sea_scale", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 22, + "h": 20 + }, + "frame": { + "x": 250, + "y": 127, + "w": 22, + "h": 20 + } + }, + { + "filename": "fairy_feather", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 7, + "w": 22, + "h": 20 + }, + "frame": { + "x": 272, + "y": 127, + "w": 22, + "h": 20 + } + }, + { + "filename": "malicious_armor", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 22, + "h": 20 + }, + "frame": { + "x": 294, + "y": 127, + "w": 22, + "h": 20 + } + }, + { + "filename": "sweet_apple", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 22, + "h": 21 + }, + "frame": { + "x": 316, + "y": 127, + "w": 22, + "h": 21 + } + }, + { + "filename": "syrupy_apple", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 22, + "h": 21 + }, + "frame": { + "x": 338, + "y": 128, + "w": 22, + "h": 21 + } + }, + { + "filename": "tart_apple", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 22, + "h": 21 + }, + "frame": { + "x": 360, + "y": 128, + "w": 22, + "h": 21 + } + }, + { + "filename": "tm_fighting", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 208, + "y": 147, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_fire", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 230, + "y": 147, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_flying", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 252, + "y": 147, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_ghost", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 274, + "y": 147, + "w": 22, "h": 22 } }, @@ -5988,35 +5442,35 @@ "h": 20 }, "frame": { - "x": 383, - "y": 194, + "x": 296, + "y": 147, "w": 20, "h": 20 } }, { - "filename": "douse_drive", + "filename": "black_sludge", "rotated": false, "trimmed": true, "sourceSize": { - "w": 32, - "h": 32 + "w": 24, + "h": 24 }, "spriteSourceSize": { - "x": 4, - "y": 8, - "w": 23, - "h": 17 + "x": 1, + "y": 2, + "w": 22, + "h": 19 }, "frame": { - "x": 403, - "y": 195, - "w": 23, - "h": 17 + "x": 316, + "y": 148, + "w": 22, + "h": 19 } }, { - "filename": "shock_drive", + "filename": "relic_crown", "rotated": false, "trimmed": true, "sourceSize": { @@ -6025,15 +5479,162 @@ }, "spriteSourceSize": { "x": 4, - "y": 8, + "y": 7, "w": 23, - "h": 17 + "h": 18 }, "frame": { - "x": 403, - "y": 212, + "x": 338, + "y": 149, "w": 23, - "h": 17 + "h": 18 + } + }, + { + "filename": "blunder_policy", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 22, + "h": 19 + }, + "frame": { + "x": 361, + "y": 149, + "w": 22, + "h": 19 + } + }, + { + "filename": "tm_grass", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 209, + "y": 169, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_ground", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 231, + "y": 169, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_ice", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 253, + "y": 169, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_normal", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 275, + "y": 169, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_poison", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 297, + "y": 167, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_psychic", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 319, + "y": 167, + "w": 22, + "h": 22 } }, { @@ -6051,12 +5652,348 @@ "h": 20 }, "frame": { - "x": 383, - "y": 214, + "x": 341, + "y": 167, "w": 20, "h": 20 } }, + { + "filename": "dubious_disc", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 7, + "w": 22, + "h": 19 + }, + "frame": { + "x": 361, + "y": 168, + "w": 22, + "h": 19 + } + }, + { + "filename": "tm_rock", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 213, + "y": 191, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_steel", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 235, + "y": 191, + "w": 22, + "h": 22 + } + }, + { + "filename": "tm_water", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 257, + "y": 191, + "w": 22, + "h": 22 + } + }, + { + "filename": "water_memory", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 215, + "y": 213, + "w": 22, + "h": 22 + } + }, + { + "filename": "water_stone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 237, + "y": 213, + "w": 22, + "h": 22 + } + }, + { + "filename": "x_accuracy", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 259, + "y": 213, + "w": 22, + "h": 22 + } + }, + { + "filename": "x_attack", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 279, + "y": 191, + "w": 22, + "h": 22 + } + }, + { + "filename": "x_defense", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 301, + "y": 189, + "w": 22, + "h": 22 + } + }, + { + "filename": "x_sp_atk", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 281, + "y": 213, + "w": 22, + "h": 22 + } + }, + { + "filename": "x_sp_def", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 323, + "y": 189, + "w": 22, + "h": 22 + } + }, + { + "filename": "x_speed", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 5, + "w": 22, + "h": 22 + }, + "frame": { + "x": 303, + "y": 211, + "w": 22, + "h": 22 + } + }, + { + "filename": "poison_barb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 21, + "h": 21 + }, + "frame": { + "x": 325, + "y": 211, + "w": 21, + "h": 21 + } + }, + { + "filename": "shiny_stone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 21, + "h": 21 + }, + "frame": { + "x": 345, + "y": 187, + "w": 21, + "h": 21 + } + }, + { + "filename": "quick_claw", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 19, + "h": 21 + }, + "frame": { + "x": 366, + "y": 187, + "w": 19, + "h": 21 + } + }, + { + "filename": "tera_orb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 22, + "h": 20 + }, + "frame": { + "x": 346, + "y": 208, + "w": 22, + "h": 20 + } + }, { "filename": "apicot_berry", "rotated": false, @@ -6072,14 +6009,56 @@ "h": 20 }, "frame": { - "x": 364, - "y": 218, + "x": 368, + "y": 208, "w": 19, "h": 20 } }, { - "filename": "wise_glasses", + "filename": "zoom_lens", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 6, + "w": 21, + "h": 21 + }, + "frame": { + "x": 219, + "y": 235, + "w": 21, + "h": 21 + } + }, + { + "filename": "upgrade", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 5, + "y": 7, + "w": 22, + "h": 19 + }, + "frame": { + "x": 240, + "y": 235, + "w": 22, + "h": 19 + } + }, + { + "filename": "douse_drive", "rotated": false, "trimmed": true, "sourceSize": { @@ -6093,33 +6072,12 @@ "h": 17 }, "frame": { - "x": 403, - "y": 229, + "x": 262, + "y": 235, "w": 23, "h": 17 } }, - { - "filename": "everstone", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 8, - "w": 20, - "h": 17 - }, - "frame": { - "x": 383, - "y": 234, - "w": 20, - "h": 17 - } - }, { "filename": "big_mushroom", "rotated": false, @@ -6135,54 +6093,12 @@ "h": 19 }, "frame": { - "x": 324, - "y": 238, + "x": 285, + "y": 235, "w": 19, "h": 19 } }, - { - "filename": "metal_alloy", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 7, - "w": 21, - "h": 19 - }, - "frame": { - "x": 343, - "y": 236, - "w": 21, - "h": 19 - } - }, - { - "filename": "candy_jar", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, - "y": 6, - "w": 19, - "h": 20 - }, - "frame": { - "x": 364, - "y": 238, - "w": 19, - "h": 20 - } - }, { "filename": "gb", "rotated": false, @@ -6198,12 +6114,33 @@ "h": 20 }, "frame": { - "x": 383, - "y": 251, + "x": 304, + "y": 233, "w": 20, "h": 20 } }, + { + "filename": "soothe_bell", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 5, + "w": 17, + "h": 22 + }, + "frame": { + "x": 223, + "y": 256, + "w": 17, + "h": 22 + } + }, { "filename": "magnet", "rotated": false, @@ -6219,12 +6156,117 @@ "h": 20 }, "frame": { - "x": 403, - "y": 246, + "x": 240, + "y": 254, "w": 20, "h": 20 } }, + { + "filename": "candy_jar", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 19, + "h": 20 + }, + "frame": { + "x": 390, + "y": 108, + "w": 19, + "h": 20 + } + }, + { + "filename": "golden_egg", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 17, + "h": 20 + }, + "frame": { + "x": 409, + "y": 106, + "w": 17, + "h": 20 + } + }, + { + "filename": "spell_tag", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 19, + "h": 21 + }, + "frame": { + "x": 382, + "y": 128, + "w": 19, + "h": 21 + } + }, + { + "filename": "lucky_egg", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 6, + "w": 17, + "h": 20 + }, + "frame": { + "x": 409, + "y": 126, + "w": 17, + "h": 20 + } + }, + { + "filename": "hard_stone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 19, + "h": 20 + }, + "frame": { + "x": 383, + "y": 149, + "w": 19, + "h": 20 + } + }, { "filename": "masterpiece_teacup", "rotated": false, @@ -6240,12 +6282,75 @@ "h": 18 }, "frame": { - "x": 212, - "y": 257, + "x": 383, + "y": 169, "w": 21, "h": 18 } }, + { + "filename": "mb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 20, + "h": 20 + }, + "frame": { + "x": 385, + "y": 187, + "w": 20, + "h": 20 + } + }, + { + "filename": "pb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 20, + "h": 20 + }, + "frame": { + "x": 387, + "y": 207, + "w": 20, + "h": 20 + } + }, + { + "filename": "metal_alloy", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 7, + "w": 21, + "h": 19 + }, + "frame": { + "x": 405, + "y": 146, + "w": 21, + "h": 19 + } + }, { "filename": "old_gateau", "rotated": false, @@ -6261,8 +6366,8 @@ "h": 18 }, "frame": { - "x": 233, - "y": 257, + "x": 405, + "y": 165, "w": 21, "h": 18 } @@ -6282,14 +6387,14 @@ "h": 18 }, "frame": { - "x": 254, - "y": 257, + "x": 405, + "y": 183, "w": 21, "h": 18 } }, { - "filename": "hard_stone", + "filename": "miracle_seed", "rotated": false, "trimmed": true, "sourceSize": { @@ -6298,19 +6403,19 @@ }, "spriteSourceSize": { "x": 6, - "y": 6, + "y": 7, "w": 19, - "h": 20 + "h": 19 }, "frame": { - "x": 193, - "y": 262, + "x": 407, + "y": 201, "w": 19, - "h": 20 + "h": 19 } }, { - "filename": "mb", + "filename": "shock_drive", "rotated": false, "trimmed": true, "sourceSize": { @@ -6318,20 +6423,20 @@ "h": 32 }, "spriteSourceSize": { - "x": 6, - "y": 6, - "w": 20, - "h": 20 + "x": 4, + "y": 8, + "w": 23, + "h": 17 }, "frame": { - "x": 190, - "y": 282, - "w": 20, - "h": 20 + "x": 346, + "y": 228, + "w": 23, + "h": 17 } }, { - "filename": "golden_egg", + "filename": "dark_stone", "rotated": false, "trimmed": true, "sourceSize": { @@ -6340,35 +6445,14 @@ }, "spriteSourceSize": { "x": 7, - "y": 6, - "w": 17, - "h": 20 - }, - "frame": { - "x": 193, - "y": 302, - "w": 17, - "h": 20 - } - }, - { - "filename": "unremarkable_teacup", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 5, "y": 7, - "w": 21, + "w": 18, "h": 18 }, "frame": { - "x": 212, - "y": 275, - "w": 21, + "x": 369, + "y": 228, + "w": 18, "h": 18 } }, @@ -6387,14 +6471,14 @@ "h": 19 }, "frame": { - "x": 233, - "y": 275, + "x": 387, + "y": 227, "w": 20, "h": 19 } }, { - "filename": "pb", + "filename": "razor_fang", "rotated": false, "trimmed": true, "sourceSize": { @@ -6402,20 +6486,20 @@ "h": 32 }, "spriteSourceSize": { - "x": 6, + "x": 7, "y": 6, - "w": 20, + "w": 18, "h": 20 }, "frame": { - "x": 253, - "y": 275, - "w": 20, + "x": 407, + "y": 220, + "w": 18, "h": 20 } }, { - "filename": "pb_gold", + "filename": "unremarkable_teacup", "rotated": false, "trimmed": true, "sourceSize": { @@ -6423,57 +6507,99 @@ "h": 32 }, "spriteSourceSize": { - "x": 6, - "y": 6, - "w": 20, - "h": 20 - }, - "frame": { - "x": 210, - "y": 293, - "w": 20, - "h": 20 - } - }, - { - "filename": "power_herb", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 24, - "h": 24 - }, - "spriteSourceSize": { - "x": 2, - "y": 3, - "w": 20, - "h": 19 - }, - "frame": { - "x": 230, - "y": 294, - "w": 20, - "h": 19 - } - }, - { - "filename": "razor_claw", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 6, + "x": 5, "y": 7, + "w": 21, + "h": 18 + }, + "frame": { + "x": 325, + "y": 232, + "w": 21, + "h": 18 + } + }, + { + "filename": "wise_glasses", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 4, + "y": 8, + "w": 23, + "h": 17 + }, + "frame": { + "x": 346, + "y": 245, + "w": 23, + "h": 17 + } + }, + { + "filename": "everstone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 8, "w": 20, + "h": 17 + }, + "frame": { + "x": 369, + "y": 246, + "w": 20, + "h": 17 + } + }, + { + "filename": "flame_orb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 18, + "h": 18 + }, + "frame": { + "x": 389, + "y": 246, + "w": 18, + "h": 18 + } + }, + { + "filename": "oval_stone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 18, "h": 19 }, "frame": { - "x": 250, - "y": 295, - "w": 20, + "x": 407, + "y": 240, + "w": 18, "h": 19 } }, @@ -6492,12 +6618,75 @@ "h": 16 }, "frame": { - "x": 210, - "y": 313, + "x": 240, + "y": 274, "w": 20, "h": 16 } }, + { + "filename": "prism_scale", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 9, + "y": 8, + "w": 15, + "h": 15 + }, + "frame": { + "x": 231, + "y": 290, + "w": 15, + "h": 15 + } + }, + { + "filename": "abomasite", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 16, + "h": 16 + }, + "frame": { + "x": 246, + "y": 290, + "w": 16, + "h": 16 + } + }, + { + "filename": "pb_gold", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 6, + "y": 6, + "w": 20, + "h": 20 + }, + "frame": { + "x": 237, + "y": 306, + "w": 20, + "h": 20 + } + }, { "filename": "rb", "rotated": false, @@ -6513,8 +6702,8 @@ "h": 20 }, "frame": { - "x": 230, - "y": 313, + "x": 243, + "y": 326, "w": 20, "h": 20 } @@ -6534,8 +6723,8 @@ "h": 20 }, "frame": { - "x": 250, - "y": 314, + "x": 245, + "y": 346, "w": 20, "h": 20 } @@ -6555,8 +6744,8 @@ "h": 20 }, "frame": { - "x": 201, - "y": 329, + "x": 247, + "y": 366, "w": 20, "h": 20 } @@ -6576,14 +6765,35 @@ "h": 20 }, "frame": { - "x": 201, - "y": 349, + "x": 257, + "y": 306, "w": 20, "h": 20 } }, { - "filename": "razor_fang", + "filename": "power_herb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 24, + "h": 24 + }, + "spriteSourceSize": { + "x": 2, + "y": 3, + "w": 20, + "h": 19 + }, + "frame": { + "x": 263, + "y": 326, + "w": 20, + "h": 19 + } + }, + { + "filename": "razor_claw", "rotated": false, "trimmed": true, "sourceSize": { @@ -6591,16 +6801,16 @@ "h": 32 }, "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 18, - "h": 20 + "x": 6, + "y": 7, + "w": 20, + "h": 19 }, "frame": { - "x": 203, - "y": 369, - "w": 18, - "h": 20 + "x": 265, + "y": 345, + "w": 20, + "h": 19 } }, { @@ -6618,14 +6828,14 @@ "h": 19 }, "frame": { - "x": 221, - "y": 333, + "x": 267, + "y": 364, "w": 20, "h": 19 } }, { - "filename": "miracle_seed", + "filename": "light_ball", "rotated": false, "trimmed": true, "sourceSize": { @@ -6633,16 +6843,58 @@ "h": 32 }, "spriteSourceSize": { - "x": 6, + "x": 7, "y": 7, - "w": 19, - "h": 19 + "w": 18, + "h": 18 }, "frame": { - "x": 221, - "y": 352, - "w": 19, - "h": 19 + "x": 251, + "y": 386, + "w": 18, + "h": 18 + } + }, + { + "filename": "light_stone", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 18, + "h": 18 + }, + "frame": { + "x": 269, + "y": 383, + "w": 18, + "h": 18 + } + }, + { + "filename": "toxic_orb", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 7, + "w": 18, + "h": 18 + }, + "frame": { + "x": 251, + "y": 404, + "w": 18, + "h": 18 } }, { @@ -6660,12 +6912,33 @@ "h": 18 }, "frame": { - "x": 221, - "y": 371, + "x": 269, + "y": 401, "w": 20, "h": 18 } }, + { + "filename": "relic_band", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 32, + "h": 32 + }, + "spriteSourceSize": { + "x": 7, + "y": 9, + "w": 17, + "h": 16 + }, + "frame": { + "x": 407, + "y": 259, + "w": 17, + "h": 16 + } + }, { "filename": "wl_antidote", "rotated": false, @@ -6681,117 +6954,12 @@ "h": 18 }, "frame": { - "x": 241, - "y": 334, + "x": 324, + "y": 250, "w": 20, "h": 18 } }, - { - "filename": "oval_stone", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 18, - "h": 19 - }, - "frame": { - "x": 240, - "y": 352, - "w": 18, - "h": 19 - } - }, - { - "filename": "dark_stone", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 18, - "h": 18 - }, - "frame": { - "x": 241, - "y": 371, - "w": 18, - "h": 18 - } - }, - { - "filename": "relic_gold", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 9, - "y": 11, - "w": 15, - "h": 11 - }, - "frame": { - "x": 203, - "y": 389, - "w": 15, - "h": 11 - } - }, - { - "filename": "lucky_egg", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 6, - "w": 17, - "h": 20 - }, - "frame": { - "x": 205, - "y": 400, - "w": 17, - "h": 20 - } - }, - { - "filename": "flame_orb", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 18, - "h": 18 - }, - "frame": { - "x": 261, - "y": 334, - "w": 18, - "h": 18 - } - }, { "filename": "wl_awakening", "rotated": false, @@ -6807,8 +6975,8 @@ "h": 18 }, "frame": { - "x": 258, - "y": 352, + "x": 304, + "y": 253, "w": 20, "h": 18 } @@ -6828,8 +6996,8 @@ "h": 18 }, "frame": { - "x": 259, - "y": 370, + "x": 344, + "y": 262, "w": 20, "h": 18 } @@ -6849,33 +7017,12 @@ "h": 18 }, "frame": { - "x": 222, - "y": 389, + "x": 324, + "y": 268, "w": 20, "h": 18 } }, - { - "filename": "light_ball", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 18, - "h": 18 - }, - "frame": { - "x": 242, - "y": 389, - "w": 18, - "h": 18 - } - }, { "filename": "wl_custom_thief", "rotated": false, @@ -6891,8 +7038,8 @@ "h": 18 }, "frame": { - "x": 260, - "y": 388, + "x": 364, + "y": 263, "w": 20, "h": 18 } @@ -6912,33 +7059,12 @@ "h": 18 }, "frame": { - "x": 222, - "y": 407, + "x": 384, + "y": 264, "w": 20, "h": 18 } }, - { - "filename": "light_stone", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 18, - "h": 18 - }, - "frame": { - "x": 242, - "y": 407, - "w": 18, - "h": 18 - } - }, { "filename": "wl_ether", "rotated": false, @@ -6954,8 +7080,8 @@ "h": 18 }, "frame": { - "x": 260, - "y": 406, + "x": 344, + "y": 280, "w": 20, "h": 18 } @@ -6975,8 +7101,8 @@ "h": 18 }, "frame": { - "x": 403, - "y": 266, + "x": 364, + "y": 281, "w": 20, "h": 18 } @@ -6996,8 +7122,8 @@ "h": 18 }, "frame": { - "x": 343, - "y": 255, + "x": 384, + "y": 282, "w": 20, "h": 18 } @@ -7017,8 +7143,8 @@ "h": 18 }, "frame": { - "x": 363, - "y": 258, + "x": 404, + "y": 275, "w": 20, "h": 18 } @@ -7038,8 +7164,8 @@ "h": 18 }, "frame": { - "x": 383, - "y": 271, + "x": 404, + "y": 293, "w": 20, "h": 18 } @@ -7059,8 +7185,8 @@ "h": 18 }, "frame": { - "x": 403, - "y": 284, + "x": 262, + "y": 252, "w": 20, "h": 18 } @@ -7080,8 +7206,8 @@ "h": 18 }, "frame": { - "x": 278, - "y": 255, + "x": 260, + "y": 270, "w": 20, "h": 18 } @@ -7101,8 +7227,8 @@ "h": 18 }, "frame": { - "x": 298, - "y": 255, + "x": 262, + "y": 288, "w": 20, "h": 18 } @@ -7122,14 +7248,14 @@ "h": 18 }, "frame": { - "x": 318, - "y": 257, + "x": 282, + "y": 254, "w": 20, "h": 18 } }, { - "filename": "mystery_egg", + "filename": "absolite", "rotated": false, "trimmed": true, "sourceSize": { @@ -7140,34 +7266,13 @@ "x": 8, "y": 8, "w": 16, - "h": 18 + "h": 16 }, "frame": { - "x": 270, - "y": 295, + "x": 280, + "y": 272, "w": 16, - "h": 18 - } - }, - { - "filename": "toxic_orb", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 7, - "w": 18, - "h": 18 - }, - "frame": { - "x": 270, - "y": 313, - "w": 18, - "h": 18 + "h": 16 } }, { @@ -7185,8 +7290,8 @@ "h": 18 }, "frame": { - "x": 279, - "y": 331, + "x": 282, + "y": 288, "w": 20, "h": 18 } @@ -7206,8 +7311,8 @@ "h": 18 }, "frame": { - "x": 279, - "y": 349, + "x": 277, + "y": 306, "w": 20, "h": 18 } @@ -7227,8 +7332,8 @@ "h": 18 }, "frame": { - "x": 279, - "y": 367, + "x": 283, + "y": 324, "w": 20, "h": 18 } @@ -7248,8 +7353,8 @@ "h": 18 }, "frame": { - "x": 280, - "y": 385, + "x": 285, + "y": 342, "w": 20, "h": 18 } @@ -7269,8 +7374,8 @@ "h": 18 }, "frame": { - "x": 280, - "y": 403, + "x": 287, + "y": 360, "w": 20, "h": 18 } @@ -7290,8 +7395,8 @@ "h": 18 }, "frame": { - "x": 273, - "y": 275, + "x": 287, + "y": 378, "w": 20, "h": 18 } @@ -7311,8 +7416,8 @@ "h": 18 }, "frame": { - "x": 293, - "y": 273, + "x": 289, + "y": 396, "w": 20, "h": 18 } @@ -7332,75 +7437,12 @@ "h": 18 }, "frame": { - "x": 313, - "y": 275, + "x": 297, + "y": 306, "w": 20, "h": 18 } }, - { - "filename": "abomasite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 286, - "y": 293, - "w": 16, - "h": 16 - } - }, - { - "filename": "absolite", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 8, - "y": 8, - "w": 16, - "h": 16 - }, - "frame": { - "x": 288, - "y": 309, - "w": 16, - "h": 16 - } - }, - { - "filename": "relic_band", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 7, - "y": 9, - "w": 17, - "h": 16 - }, - "frame": { - "x": 302, - "y": 293, - "w": 17, - "h": 16 - } - }, { "filename": "aerodactylite", "rotated": false, @@ -7416,8 +7458,8 @@ "h": 16 }, "frame": { - "x": 304, - "y": 309, + "x": 303, + "y": 324, "w": 16, "h": 16 } @@ -7437,8 +7479,8 @@ "h": 16 }, "frame": { - "x": 319, - "y": 293, + "x": 296, + "y": 272, "w": 16, "h": 16 } @@ -7458,8 +7500,8 @@ "h": 16 }, "frame": { - "x": 320, - "y": 309, + "x": 305, + "y": 340, "w": 16, "h": 16 } @@ -7479,8 +7521,8 @@ "h": 16 }, "frame": { - "x": 299, - "y": 325, + "x": 302, + "y": 288, "w": 16, "h": 16 } @@ -7500,8 +7542,8 @@ "h": 16 }, "frame": { - "x": 299, - "y": 341, + "x": 307, + "y": 356, "w": 16, "h": 16 } @@ -7521,8 +7563,8 @@ "h": 16 }, "frame": { - "x": 315, - "y": 325, + "x": 307, + "y": 372, "w": 16, "h": 16 } @@ -7542,8 +7584,8 @@ "h": 16 }, "frame": { - "x": 299, - "y": 357, + "x": 318, + "y": 286, "w": 16, "h": 16 } @@ -7563,8 +7605,8 @@ "h": 16 }, "frame": { - "x": 315, - "y": 341, + "x": 309, + "y": 388, "w": 16, "h": 16 } @@ -7584,8 +7626,8 @@ "h": 16 }, "frame": { - "x": 315, - "y": 357, + "x": 309, + "y": 404, "w": 16, "h": 16 } @@ -7605,8 +7647,8 @@ "h": 16 }, "frame": { - "x": 333, - "y": 275, + "x": 318, + "y": 302, "w": 16, "h": 16 } @@ -7626,8 +7668,8 @@ "h": 16 }, "frame": { - "x": 335, - "y": 291, + "x": 319, + "y": 318, "w": 16, "h": 16 } @@ -7647,8 +7689,8 @@ "h": 16 }, "frame": { - "x": 336, - "y": 307, + "x": 321, + "y": 334, "w": 16, "h": 16 } @@ -7668,8 +7710,8 @@ "h": 16 }, "frame": { - "x": 331, - "y": 325, + "x": 323, + "y": 350, "w": 16, "h": 16 } @@ -7689,8 +7731,8 @@ "h": 16 }, "frame": { - "x": 331, - "y": 341, + "x": 323, + "y": 366, "w": 16, "h": 16 } @@ -7710,8 +7752,8 @@ "h": 16 }, "frame": { - "x": 331, - "y": 357, + "x": 325, + "y": 382, "w": 16, "h": 16 } @@ -7731,8 +7773,8 @@ "h": 16 }, "frame": { - "x": 300, - "y": 373, + "x": 325, + "y": 398, "w": 16, "h": 16 } @@ -7752,8 +7794,8 @@ "h": 16 }, "frame": { - "x": 300, - "y": 389, + "x": 334, + "y": 298, "w": 16, "h": 16 } @@ -7773,8 +7815,8 @@ "h": 16 }, "frame": { - "x": 316, - "y": 373, + "x": 335, + "y": 314, "w": 16, "h": 16 } @@ -7794,8 +7836,8 @@ "h": 16 }, "frame": { - "x": 300, - "y": 405, + "x": 337, + "y": 330, "w": 16, "h": 16 } @@ -7815,8 +7857,8 @@ "h": 16 }, "frame": { - "x": 316, - "y": 389, + "x": 339, + "y": 346, "w": 16, "h": 16 } @@ -7836,8 +7878,8 @@ "h": 16 }, "frame": { - "x": 316, - "y": 405, + "x": 339, + "y": 362, "w": 16, "h": 16 } @@ -7857,8 +7899,8 @@ "h": 16 }, "frame": { - "x": 332, - "y": 373, + "x": 341, + "y": 378, "w": 16, "h": 16 } @@ -7878,8 +7920,8 @@ "h": 16 }, "frame": { - "x": 332, - "y": 389, + "x": 341, + "y": 394, "w": 16, "h": 16 } @@ -7899,8 +7941,8 @@ "h": 16 }, "frame": { - "x": 332, - "y": 405, + "x": 341, + "y": 410, "w": 16, "h": 16 } @@ -7920,8 +7962,8 @@ "h": 16 }, "frame": { - "x": 347, - "y": 323, + "x": 351, + "y": 311, "w": 16, "h": 16 } @@ -7941,8 +7983,8 @@ "h": 16 }, "frame": { - "x": 347, - "y": 339, + "x": 353, + "y": 327, "w": 16, "h": 16 } @@ -7962,8 +8004,8 @@ "h": 16 }, "frame": { - "x": 347, - "y": 355, + "x": 355, + "y": 343, "w": 16, "h": 16 } @@ -7983,8 +8025,8 @@ "h": 16 }, "frame": { - "x": 348, - "y": 371, + "x": 355, + "y": 359, "w": 16, "h": 16 } @@ -8004,8 +8046,8 @@ "h": 16 }, "frame": { - "x": 348, - "y": 387, + "x": 357, + "y": 375, "w": 16, "h": 16 } @@ -8025,33 +8067,12 @@ "h": 16 }, "frame": { - "x": 348, - "y": 403, + "x": 357, + "y": 391, "w": 16, "h": 16 } }, - { - "filename": "prism_scale", - "rotated": false, - "trimmed": true, - "sourceSize": { - "w": 32, - "h": 32 - }, - "spriteSourceSize": { - "x": 9, - "y": 8, - "w": 15, - "h": 15 - }, - "frame": { - "x": 349, - "y": 276, - "w": 15, - "h": 15 - } - }, { "filename": "metagrossite", "rotated": false, @@ -8067,8 +8088,8 @@ "h": 16 }, "frame": { - "x": 364, - "y": 276, + "x": 357, + "y": 407, "w": 16, "h": 16 } @@ -8088,8 +8109,8 @@ "h": 16 }, "frame": { - "x": 364, - "y": 292, + "x": 367, + "y": 299, "w": 16, "h": 16 } @@ -8109,8 +8130,8 @@ "h": 16 }, "frame": { - "x": 380, - "y": 289, + "x": 383, + "y": 300, "w": 16, "h": 16 } @@ -8130,8 +8151,8 @@ "h": 16 }, "frame": { - "x": 380, - "y": 305, + "x": 399, + "y": 311, "w": 16, "h": 16 } @@ -8151,8 +8172,8 @@ "h": 16 }, "frame": { - "x": 364, - "y": 308, + "x": 369, + "y": 327, "w": 16, "h": 16 } @@ -8172,8 +8193,8 @@ "h": 16 }, "frame": { - "x": 363, - "y": 324, + "x": 371, + "y": 343, "w": 16, "h": 16 } @@ -8193,8 +8214,8 @@ "h": 16 }, "frame": { - "x": 363, - "y": 340, + "x": 371, + "y": 359, "w": 16, "h": 16 } @@ -8214,8 +8235,8 @@ "h": 16 }, "frame": { - "x": 396, - "y": 302, + "x": 373, + "y": 375, "w": 16, "h": 16 } @@ -8235,8 +8256,8 @@ "h": 16 }, "frame": { - "x": 380, - "y": 321, + "x": 373, + "y": 391, "w": 16, "h": 16 } @@ -8256,8 +8277,8 @@ "h": 16 }, "frame": { - "x": 396, - "y": 318, + "x": 373, + "y": 407, "w": 16, "h": 16 } @@ -8277,8 +8298,8 @@ "h": 16 }, "frame": { - "x": 379, - "y": 337, + "x": 385, + "y": 327, "w": 16, "h": 16 } @@ -8298,8 +8319,8 @@ "h": 16 }, "frame": { - "x": 396, - "y": 334, + "x": 387, + "y": 343, "w": 16, "h": 16 } @@ -8319,8 +8340,8 @@ "h": 16 }, "frame": { - "x": 379, - "y": 353, + "x": 387, + "y": 359, "w": 16, "h": 16 } @@ -8340,8 +8361,8 @@ "h": 16 }, "frame": { - "x": 395, - "y": 350, + "x": 401, + "y": 327, "w": 16, "h": 16 } @@ -8361,8 +8382,8 @@ "h": 16 }, "frame": { - "x": 395, - "y": 366, + "x": 389, + "y": 375, "w": 16, "h": 16 } @@ -8382,8 +8403,8 @@ "h": 16 }, "frame": { - "x": 364, - "y": 382, + "x": 389, + "y": 391, "w": 16, "h": 16 } @@ -8403,8 +8424,8 @@ "h": 16 }, "frame": { - "x": 364, - "y": 398, + "x": 389, + "y": 407, "w": 16, "h": 16 } @@ -8424,8 +8445,8 @@ "h": 16 }, "frame": { - "x": 380, - "y": 382, + "x": 403, + "y": 343, "w": 16, "h": 16 } @@ -8445,8 +8466,8 @@ "h": 16 }, "frame": { - "x": 380, - "y": 398, + "x": 403, + "y": 359, "w": 16, "h": 16 } @@ -8457,6 +8478,6 @@ "meta": { "app": "https://www.codeandweb.com/texturepacker", "version": "3.0", - "smartupdate": "$TexturePacker:SmartUpdate:06a43a858ac0838c2d5400a7c8e124da:6c9201d0b6d07fc299c0fa41c226a22d:110e074689c9edd2c54833ce2e4d9270$" + "smartupdate": "$TexturePacker:SmartUpdate:c428fceb82c2d05d4e09932aca0e849e:b87abcdcfeb40d09d19eb74093035e71:110e074689c9edd2c54833ce2e4d9270$" } } diff --git a/public/images/items.png b/public/images/items.png index 2bf9a012139..3f69fbd7406 100644 Binary files a/public/images/items.png and b/public/images/items.png differ diff --git a/public/images/items/macho_brace.png b/public/images/items/macho_brace.png new file mode 100644 index 00000000000..2085829e1ce Binary files /dev/null and b/public/images/items/macho_brace.png differ diff --git a/public/images/trainer/vicky.json b/public/images/trainer/vicky.json new file mode 100644 index 00000000000..c19cf11622d --- /dev/null +++ b/public/images/trainer/vicky.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "vicky.png", + "format": "RGBA8888", + "size": { + "w": 52, + "h": 53 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 27, + "w": 52, + "h": 53 + }, + "frame": { + "x": 0, + "y": 0, + "w": 52, + "h": 53 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:bf9d2d417a1982282dd711456ac71206:101e07828e3d6e2a2a7a80aebfa802ad:cabe44a4410c334298b1984a219f8160$" + } +} diff --git a/public/images/trainer/vicky.png b/public/images/trainer/vicky.png new file mode 100644 index 00000000000..3e2d6c13696 Binary files /dev/null and b/public/images/trainer/vicky.png differ diff --git a/public/images/trainer/victor.json b/public/images/trainer/victor.json new file mode 100644 index 00000000000..5afa9704567 --- /dev/null +++ b/public/images/trainer/victor.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "victor.png", + "format": "RGBA8888", + "size": { + "w": 55, + "h": 53 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 12, + "y": 27, + "w": 55, + "h": 53 + }, + "frame": { + "x": 0, + "y": 0, + "w": 55, + "h": 53 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:64eff0f697754cdf9552b46342c9292a:611e0e2cacbd90c1229ce5443b2414f0:0cc0f5a2c1b2eedb46dd8318e8feb1d8$" + } +} diff --git a/public/images/trainer/victor.png b/public/images/trainer/victor.png new file mode 100644 index 00000000000..3ffddea24bb Binary files /dev/null and b/public/images/trainer/victor.png differ diff --git a/public/images/trainer/victoria.json b/public/images/trainer/victoria.json new file mode 100644 index 00000000000..7917113621a --- /dev/null +++ b/public/images/trainer/victoria.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "victoria.png", + "format": "RGBA8888", + "size": { + "w": 52, + "h": 54 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 14, + "y": 26, + "w": 52, + "h": 54 + }, + "frame": { + "x": 0, + "y": 0, + "w": 52, + "h": 54 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:4dafeae3674d63b12cc4d8044f67b5a3:7834687d784c31169256927f419c7958:cf0eb39e0a3f2e42f23ca29747d73c40$" + } +} diff --git a/public/images/trainer/victoria.png b/public/images/trainer/victoria.png new file mode 100644 index 00000000000..e2874f266ad Binary files /dev/null and b/public/images/trainer/victoria.png differ diff --git a/public/images/trainer/vito.json b/public/images/trainer/vito.json new file mode 100644 index 00000000000..61dcf7af0ef --- /dev/null +++ b/public/images/trainer/vito.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "vito.png", + "format": "RGBA8888", + "size": { + "w": 41, + "h": 78 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 20, + "y": 2, + "w": 41, + "h": 78 + }, + "frame": { + "x": 0, + "y": 0, + "w": 41, + "h": 78 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:cb988be58fcd5381174e9d120b051e38:4d4723dbbcd9713ee0ed3c2d84ef4bfb:1c7723b536b218346e3138016d865ce9$" + } +} diff --git a/public/images/trainer/vito.png b/public/images/trainer/vito.png new file mode 100644 index 00000000000..a7c6c0444f4 Binary files /dev/null and b/public/images/trainer/vito.png differ diff --git a/public/images/trainer/vivi.json b/public/images/trainer/vivi.json new file mode 100644 index 00000000000..b36ebcd7c0c --- /dev/null +++ b/public/images/trainer/vivi.json @@ -0,0 +1,41 @@ +{ + "textures": [ + { + "image": "vivi.png", + "format": "RGBA8888", + "size": { + "w": 48, + "h": 69 + }, + "scale": 1, + "frames": [ + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 11, + "w": 48, + "h": 69 + }, + "frame": { + "x": 0, + "y": 0, + "w": 48, + "h": 69 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:0a51b4df0b2ed0fed7e3bdb5dffd9e28:af1f3b1480023b3e3761c49e49faf5f1:4fc6bf2bec74c4bb8809df38231deb01$" + } +} diff --git a/public/images/trainer/vivi.png b/public/images/trainer/vivi.png new file mode 100644 index 00000000000..cd97e676cfb Binary files /dev/null and b/public/images/trainer/vivi.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 2b1c4ba719e..cac73d6791b 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -14,7 +14,7 @@ import { Arena, ArenaBase } from "./field/arena"; import { GameData } from "./system/game-data"; import { addTextObject, getTextColor, TextStyle } from "./ui/text"; import { allMoves } from "./data/move"; -import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue, ModifierPoolType, PokemonHeldItemModifierType } from "./modifier/modifier-type"; +import { getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue, ModifierPoolType } from "./modifier/modifier-type"; import AbilityBar from "./ui/ability-bar"; import { allAbilities, applyAbAttrs, applyPostBattleInitAbAttrs, BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr } from "./data/ability"; import Battle, { BattleType, FixedBattleConfig } from "./battle"; @@ -67,12 +67,13 @@ import { UiTheme } from "#enums/ui-theme"; import { TimedEventManager } from "#app/timed-event-manager.js"; import i18next from "i18next"; import { TrainerType } from "#enums/trainer-type"; -import IMysteryEncounter from "./data/mystery-encounters/mystery-encounter"; +import MysteryEncounter from "./data/mystery-encounters/mystery-encounter"; import { allMysteryEncounters, AVERAGE_ENCOUNTERS_PER_RUN_TARGET, BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT, mysteryEncountersByBiome, WEIGHT_INCREMENT_ON_SPAWN_MISS } from "./data/mystery-encounters/mystery-encounters"; import { MysteryEncounterData } from "#app/data/mystery-encounters/mystery-encounter-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import HeldModifierConfig from "#app/interfaces/held-modifier-config"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -223,7 +224,7 @@ export default class BattleScene extends SceneBase { public pokemonInfoContainer: PokemonInfoContainer; private party: PlayerPokemon[]; public mysteryEncounterData: MysteryEncounterData = new MysteryEncounterData(null); - public lastMysteryEncounter: IMysteryEncounter; + public lastMysteryEncounter: MysteryEncounter; /** Combined Biome and Wave count text */ private biomeWaveText: Phaser.GameObjects.Text; private moneyText: Phaser.GameObjects.Text; @@ -1036,7 +1037,7 @@ export default class BattleScene extends SceneBase { } } - newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: IMysteryEncounter): Battle { + newBattle(waveIndex?: integer, battleType?: BattleType, trainerData?: TrainerData, double?: boolean, mysteryEncounter?: MysteryEncounter): Battle { const _startingWave = Overrides.STARTING_WAVE_OVERRIDE || startingWave; const newWaveIndex = waveIndex || ((this.currentBattle?.waveIndex || (_startingWave - 1)) + 1); let newDouble: boolean; @@ -2423,7 +2424,7 @@ export default class BattleScene extends SceneBase { }); } - generateEnemyModifiers(customHeldModifiers?: PokemonHeldItemModifierType[][]): Promise { + generateEnemyModifiers(heldModifiersConfigs?: HeldModifierConfig[][]): Promise { return new Promise(resolve => { if (this.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { return resolve(); @@ -2445,8 +2446,16 @@ export default class BattleScene extends SceneBase { } party.every((enemyPokemon: EnemyPokemon, i: integer) => { - if (customHeldModifiers && i < customHeldModifiers.length && customHeldModifiers[i] && customHeldModifiers[i].length > 0) { - customHeldModifiers[i].forEach(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); + if (heldModifiersConfigs && i < heldModifiersConfigs.length && heldModifiersConfigs[i] && heldModifiersConfigs[i].length > 0) { + heldModifiersConfigs[i].forEach(mt => { + const stackCount = mt.stackCount ?? 1; + // const isTransferable = mt.isTransferable ?? true; + const modifier = mt.modifierType.newModifier(enemyPokemon); + modifier.stackCount = stackCount; + // TODO: set isTransferable + // modifier.setIsTransferable(isTransferable); + this.addEnemyModifier(modifier, false, true); + }); return true; } @@ -2702,9 +2711,9 @@ export default class BattleScene extends SceneBase { * @param override - used to load session encounter when restarting game, etc. * @returns */ - getMysteryEncounter(override: IMysteryEncounter): IMysteryEncounter { + getMysteryEncounter(override: MysteryEncounter): MysteryEncounter { // Loading override or session encounter - let encounter: IMysteryEncounter; + let encounter: MysteryEncounter; if (!isNullOrUndefined(Overrides.MYSTERY_ENCOUNTER_OVERRIDE) && allMysteryEncounters.hasOwnProperty(Overrides.MYSTERY_ENCOUNTER_OVERRIDE)) { encounter = allMysteryEncounters[Overrides.MYSTERY_ENCOUNTER_OVERRIDE]; } else { @@ -2726,7 +2735,7 @@ export default class BattleScene extends SceneBase { } if (encounter) { - encounter = new IMysteryEncounter(encounter); + encounter = new MysteryEncounter(encounter); encounter.populateDialogueTokensFromRequirements(this); return encounter; } @@ -2755,7 +2764,7 @@ export default class BattleScene extends SceneBase { tier = Overrides.MYSTERY_ENCOUNTER_TIER_OVERRIDE; } - let availableEncounters: IMysteryEncounter[] = []; + let availableEncounters: MysteryEncounter[] = []; // New encounter will never be the same as the most recent encounter const previousEncounter = this.mysteryEncounterData.encounteredEvents?.length > 0 ? this.mysteryEncounterData.encounteredEvents[this.mysteryEncounterData.encounteredEvents.length - 1][0] : null; const biomeMysteryEncounters = mysteryEncountersByBiome.get(this.arena.biomeType) ?? []; @@ -2802,7 +2811,7 @@ export default class BattleScene extends SceneBase { } encounter = availableEncounters[Utils.randSeedInt(availableEncounters.length)]; // New encounter object to not dirty flags - encounter = new IMysteryEncounter(encounter); + encounter = new MysteryEncounter(encounter); encounter.populateDialogueTokensFromRequirements(this); return encounter; } diff --git a/src/battle.ts b/src/battle.ts index 7205b883eb9..bec0e4363a2 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -14,7 +14,7 @@ import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; import i18next from "#app/plugins/i18n"; -import IMysteryEncounter from "./data/mystery-encounters/mystery-encounter"; +import MysteryEncounter from "./data/mystery-encounters/mystery-encounter"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; export enum BattleType { @@ -70,7 +70,7 @@ export default class Battle { public lastUsedPokeball: PokeballType; public playerFaints: number; // The amount of times pokemon on the players side have fainted public enemyFaints: number; // The amount of times pokemon on the enemies side have fainted - public mysteryEncounter: IMysteryEncounter; + public mysteryEncounter: MysteryEncounter; private rngCounter: integer = 0; diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index 5d4bea542de..d8659c15a56 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -735,6 +735,56 @@ export const trainerTypeDialogue: TrainerTypeDialogue = { ] } ], + [TrainerType.VICTOR]: [ + { + encounter: [ + "dialogue:winstrates_victor.encounter.1", + ], + victory: [ + "dialogue:winstrates_victor.victory.1" + ], + } + ], + [TrainerType.VICTORIA]: [ + { + encounter: [ + "dialogue:winstrates_victoria.encounter.1", + ], + victory: [ + "dialogue:winstrates_victoria.victory.1" + ], + } + ], + [TrainerType.VIVI]: [ + { + encounter: [ + "dialogue:winstrates_vivi.encounter.1", + ], + victory: [ + "dialogue:winstrates_vivi.victory.1" + ], + } + ], + [TrainerType.VICKY]: [ + { + encounter: [ + "dialogue:winstrates_vicky.encounter.1", + ], + victory: [ + "dialogue:winstrates_vicky.victory.1" + ], + } + ], + [TrainerType.VITO]: [ + { + encounter: [ + "dialogue:winstrates_vito.encounter.1", + ], + victory: [ + "dialogue:winstrates_vito.victory.1" + ], + } + ], [TrainerType.BROCK]: { encounter: [ "dialogue:brock.encounter.1", diff --git a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts index 9a69a85d722..d42d4051aa6 100644 --- a/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts +++ b/src/data/mystery-encounters/encounters/a-trainers-test-encounter.ts @@ -2,7 +2,7 @@ import { EnemyPartyConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattl import { trainerConfigs, } from "#app/data/trainer-config"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { TrainerType } from "#enums/trainer-type"; import { Species } from "#enums/species"; @@ -22,10 +22,10 @@ const namespace = "mysteryEncounter:aTrainersTest"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/115 | GitHub Issue #115} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const ATrainersTestEncounter: IMysteryEncounter = +export const ATrainersTestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.A_TRAINERS_TEST) .withEncounterTier(MysteryEncounterTier.ROGUE) - .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 + .withSceneWaveRangeRequirement(100, 180) .withIntroSpriteConfigs([]) // These are set in onInit() .withIntroDialogue([ { @@ -139,13 +139,6 @@ export const ATrainersTestEncounter: IMysteryEncounter = // Spawn standard trainer battle with memory mushroom reward const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; - let eggTier; - if (randSeedInt(64) >= 54) { - eggTier = EggTier.MASTER; - } else { - eggTier = EggTier.ULTRA; - } - await transitionMysteryEncounterIntroVisuals(scene); const eggOptions: IEggOptions = { @@ -153,9 +146,9 @@ export const ATrainersTestEncounter: IMysteryEncounter = pulled: false, sourceType: EggSourceType.EVENT, eventEggTypeDescriptor: encounter.misc.trainerEggDescription, - tier: eggTier + tier: EggTier.ULTRA }; - encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.${eggTier === EggTier.ULTRA ? "epic" : "legendary"}`)); + encounter.setDialogueToken("eggType", i18next.t(`${namespace}.eggTypes.epic`)); setEncounterRewards(scene, { fillRemaining: true }, [eggOptions]); return initBattleWithEnemyConfig(scene, config); @@ -185,7 +178,7 @@ export const ATrainersTestEncounter: IMysteryEncounter = ) .withOutroDialogue([ { - text: `${namespace}:outro`, + text: `${namespace}.outro`, }, ]) .build(); diff --git a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts index 45645a6b17f..500512b70c3 100644 --- a/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts +++ b/src/data/mystery-encounters/encounters/absolute-avarice-encounter.ts @@ -1,10 +1,10 @@ -import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import Pokemon, { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { BerryModifierType, modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { PersistentModifierRequirement } from "../mystery-encounter-requirements"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -21,6 +21,7 @@ import { BattlerIndex } from "#app/battle"; import { applyModifierTypeToPlayerPokemon, catchPokemon, getHighestLevelPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { TrainerSlot } from "#app/data/trainer-config"; import { PokeballType } from "#app/data/pokeball"; +import HeldModifierConfig from "#app/interfaces/held-modifier-config"; /** the i18n namespace for this encounter */ const namespace = "mysteryEncounter:absoluteAvarice"; @@ -30,7 +31,7 @@ const namespace = "mysteryEncounter:absoluteAvarice"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/58 | GitHub Issue #58} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const AbsoluteAvariceEncounter: IMysteryEncounter = +export const AbsoluteAvariceEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.ABSOLUTE_AVARICE) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) @@ -191,13 +192,13 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter = encounter.misc = { berryItemsMap }; // Generates copies of the stolen berries to put on the Greedent - const bossModifierTypes: PokemonHeldItemModifierType[] = []; + const bossModifierConfigs: HeldModifierConfig[] = []; berryItems.forEach(berryMod => { // Can't define stack count on a ModifierType, have to just create separate instances for each stack // Overflow berries will be "lost" on the boss, but it's un-catchable anyway for (let i = 0; i < berryMod.stackCount; i++) { - const modifierType = generateModifierTypeOption(scene, modifierTypes.BERRY, [berryMod.berryType]).type as PokemonHeldItemModifierType; - bossModifierTypes.push(modifierType); + const modifierType = generateModifierType(scene, modifierTypes.BERRY, [berryMod.berryType]) as PokemonHeldItemModifierType; + bossModifierConfigs.push({ modifierType }); } scene.removeModifier(berryMod); @@ -212,7 +213,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter = isBoss: true, bossSegments: 3, moveSet: [Moves.THRASH, Moves.BODY_PRESS, Moves.STUFF_CHEEKS, Moves.SLACK_OFF], - modifierTypes: bossModifierTypes, + modifierConfigs: bossModifierConfigs, tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { queueEncounterMessage(pokemon.scene, `${namespace}.option.1.boss_enraged`); @@ -243,7 +244,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter = const encounter = scene.currentBattle.mysteryEncounter; // Provides 1x Reviver Seed to each party member at end of battle - const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type; + const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED); const givePartyPokemonReviverSeeds = () => { const party = scene.getParty(); party.forEach(p => { @@ -296,7 +297,7 @@ export const AbsoluteAvariceEncounter: IMysteryEncounter = Phaser.Math.RND.shuffle(berryTypesAsArray); const randBerryType = berryTypesAsArray.pop(); - const berryModType = generateModifierTypeOption(scene, modifierTypes.BERRY, [randBerryType]).type as BerryModifierType; + const berryModType = generateModifierType(scene, modifierTypes.BERRY, [randBerryType]) as BerryModifierType; applyModifierTypeToPlayerPokemon(scene, pokemon, berryModType); } } 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 8eabbbd8d05..6dfa72129fb 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 @@ -3,7 +3,7 @@ import { modifierTypes } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { AbilityRequirement, CombinationPokemonRequirement, MoveRequirement } from "../mystery-encounter-requirements"; import { getHighestStatTotalPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; @@ -21,7 +21,7 @@ const namespace = "mysteryEncounter:offerYouCantRefuse"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/72 | GitHub Issue #72} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const AnOfferYouCantRefuseEncounter: IMysteryEncounter = +export const AnOfferYouCantRefuseEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) diff --git a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts index 4f67905235e..30c82a9dd9e 100644 --- a/src/data/mystery-encounters/encounters/berries-abound-encounter.ts +++ b/src/data/mystery-encounters/encounters/berries-abound-encounter.ts @@ -1,7 +1,7 @@ import { BattleStat } from "#app/data/battle-stat"; import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { - EnemyPartyConfig, generateModifierTypeOption, + EnemyPartyConfig, generateModifierType, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards @@ -19,7 +19,7 @@ import { randSeedInt } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -40,7 +40,7 @@ const namespace = "mysteryEncounter:berriesAbound"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const BerriesAboundEncounter: IMysteryEncounter = +export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.BERRIES_ABOUND) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 @@ -315,7 +315,7 @@ export const BerriesAboundEncounter: IMysteryEncounter = async function tryGiveBerry(scene: BattleScene, prioritizedPokemon?: PlayerPokemon) { const berryType = randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType; - const berry = generateModifierTypeOption(scene, modifierTypes.BERRY, [berryType]).type as BerryModifierType; + const berry = generateModifierType(scene, modifierTypes.BERRY, [berryType]) as BerryModifierType; const party = scene.getParty(); diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts index c03508a5700..147343bad31 100644 --- a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -1,11 +1,11 @@ -import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, selectPokemonForOption, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { trainerConfigs, TrainerPartyCompoundTemplate, TrainerPartyTemplate, } from "#app/data/trainer-config"; import { ModifierTier } from "#app/modifier/modifier-tier"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; @@ -56,7 +56,7 @@ const RANDOM_ABILITY_POOL = [ * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const ClowningAroundEncounter: IMysteryEncounter = +export const ClowningAroundEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) .withEncounterTier(MysteryEncounterTier.ULTRA) .withSceneWaveRangeRequirement(80, 180) @@ -128,8 +128,7 @@ export const ClowningAroundEncounter: IMysteryEncounter = }, { // Blacephalon has the random ability from pool, and 2 entirely random types to fit with the theme of the encounter species: getPokemonSpecies(Species.BLACEPHALON), - ability: ability, - mysteryEncounterData: new MysteryEncounterPokemonData(null, null, null, [randSeedInt(18), randSeedInt(18)]), + mysteryEncounterData: new MysteryEncounterPokemonData(null, ability, null, [randSeedInt(18), randSeedInt(18)]), isBoss: true, moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN] }, @@ -483,9 +482,9 @@ function generateItemsOfTier(scene: BattleScene, pokemon: PlayerPokemon, numItem const newItemType = pool[randIndex]; let newMod; if (tier === "Berries") { - newMod = generateModifierTypeOption(scene, modifierTypes.BERRY, [newItemType[0]]).type as PokemonHeldItemModifierType; + newMod = generateModifierType(scene, modifierTypes.BERRY, [newItemType[0]]) as PokemonHeldItemModifierType; } else { - newMod = generateModifierTypeOption(scene, newItemType[0]).type as PokemonHeldItemModifierType; + newMod = generateModifierType(scene, newItemType[0]) as PokemonHeldItemModifierType; } applyModifierTypeToPlayerPokemon(scene, pokemon, newMod); // Decrement max stacks and remove from pool if at max diff --git a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts index 5b127984880..3e87d8e3aa9 100644 --- a/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts +++ b/src/data/mystery-encounters/encounters/dancing-lessons-encounter.ts @@ -3,7 +3,7 @@ import Pokemon, { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -77,7 +77,7 @@ const SENSU_STYLE_BIOMES = [ * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/130 | GitHub Issue #130} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const DancingLessonsEncounter: IMysteryEncounter = +export const DancingLessonsEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DANCING_LESSONS) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index fde28a2ab00..1251cc9bf32 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -6,7 +6,7 @@ import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import { modifierTypes } from "#app/modifier/modifier-type"; import { getPokemonSpecies } from "#app/data/pokemon-species"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, } from "../utils/encounter-phase-utils"; import { getRandomPlayerPokemon, getRandomSpeciesByStarterTier } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; @@ -74,7 +74,7 @@ const excludedBosses = [ * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/61 | GitHub Issue #61} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const DarkDealEncounter: IMysteryEncounter = +export const DarkDealEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DARK_DEAL) .withEncounterTier(MysteryEncounterTier.ROGUE) .withIntroSpriteConfigs([ diff --git a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts index bcab1b4bd02..2c7f8b9fe7e 100644 --- a/src/data/mystery-encounters/encounters/delibirdy-encounter.ts +++ b/src/data/mystery-encounters/encounters/delibirdy-encounter.ts @@ -1,10 +1,10 @@ -import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { CombinationPokemonRequirement, HeldItemRequirement, MoneyRequirement } from "../mystery-encounter-requirements"; import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -36,7 +36,7 @@ const OPTION_3_DISALLOWED_MODIFIERS = [ * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/57 | GitHub Issue #57} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const DelibirdyEncounter: IMysteryEncounter = +export const DelibirdyEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DELIBIRDY) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) @@ -113,7 +113,7 @@ export const DelibirdyEncounter: IMysteryEncounter = if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType; + const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell); scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); @@ -169,7 +169,7 @@ export const DelibirdyEncounter: IMysteryEncounter = // If pokemon meets primary pokemon reqs, it can be selected const meetsReqs = encounter.options[1].pokemonMeetsPrimaryRequirements(scene, pokemon); if (!meetsReqs) { - return getEncounterText(scene, `${namespace}:invalid_selection`); + return getEncounterText(scene, `${namespace}.invalid_selection`); } return null; @@ -188,7 +188,7 @@ export const DelibirdyEncounter: IMysteryEncounter = if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType; + const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell); scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); @@ -201,7 +201,7 @@ export const DelibirdyEncounter: IMysteryEncounter = if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType; + const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell); scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); @@ -281,7 +281,7 @@ export const DelibirdyEncounter: IMysteryEncounter = if (existing && existing.getStackCount() >= existing.getMaxStackCount(scene)) { // At max stacks, give the first party pokemon a Shell Bell instead - const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType; + const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; await applyModifierTypeToPlayerPokemon(scene, scene.getParty()[0], shellBell); scene.playSound("item_fanfare"); await showEncounterText(scene, i18next.t("battle:rewardGain", { modifierName: shellBell.name }), null, true); diff --git a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts index fa27c31c273..9a11a2e8888 100644 --- a/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts +++ b/src/data/mystery-encounters/encounters/department-store-sale-encounter.ts @@ -7,7 +7,7 @@ import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { +import MysteryEncounter, { MysteryEncounterBuilder, } from "../mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -20,7 +20,7 @@ const namespace = "mysteryEncounter:departmentStoreSale"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/33 | GitHub Issue #33} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const DepartmentStoreSaleEncounter: IMysteryEncounter = +export const DepartmentStoreSaleEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 100) diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index 93e2d71aa1a..27322ea052b 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -7,7 +7,7 @@ import { modifierTypes } from "#app/modifier/modifier-type"; import { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -19,7 +19,7 @@ const namespace = "mysteryEncounter:fieldTrip"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/17 | GitHub Issue #17} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const FieldTripEncounter: IMysteryEncounter = +export const FieldTripEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIELD_TRIP) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 5b4b582da01..533f9b93e79 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -3,7 +3,7 @@ import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig import { AttackTypeBoosterModifierType, modifierTypes, } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { TypeRequirement } from "../mystery-encounter-requirements"; import { Species } from "#enums/species"; import { getPokemonSpecies } from "#app/data/pokemon-species"; @@ -36,7 +36,7 @@ const DAMAGE_PERCENTAGE: number = 20; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/88 | GitHub Issue #88} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const FieryFalloutEncounter: IMysteryEncounter = +export const FieryFalloutEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIERY_FALLOUT) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(40, 180) diff --git a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts index 2f000742ea9..5fc827a0c77 100644 --- a/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts +++ b/src/data/mystery-encounters/encounters/fight-or-flight-encounter.ts @@ -17,7 +17,7 @@ import { } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MoveRequirement } from "../mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -33,7 +33,7 @@ const namespace = "mysteryEncounter:fightOrFlight"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/24 | GitHub Issue #24} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const FightOrFlightEncounter: IMysteryEncounter = +export const FightOrFlightEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 diff --git a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts index 02f2393811a..02f4f2f1cdc 100644 --- a/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts +++ b/src/data/mystery-encounters/encounters/lost-at-sea-encounter.ts @@ -127,7 +127,7 @@ export const LostAtSeaEncounter: MysteryEncounter = MysteryEncounterBuilder.with * @param scene Battle scene * @param guidePokemon pokemon choosen as a guide */ -function handlePokemonGuidingYouPhase(scene: BattleScene) { +async function handlePokemonGuidingYouPhase(scene: BattleScene) { const laprasSpecies = getPokemonSpecies(Species.LAPRAS); const { mysteryEncounter } = scene.currentBattle; diff --git a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts index 368b18b11c0..f3738059b0b 100644 --- a/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-challengers-encounter.ts @@ -15,7 +15,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { PartyMemberStrength } from "#enums/party-member-strength"; import BattleScene from "#app/battle-scene"; import * as Utils from "#app/utils"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; /** the i18n namespace for the encounter */ @@ -26,7 +26,7 @@ const namespace = "mysteryEncounter:mysteriousChallengers"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/41 | GitHub Issue #41} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const MysteriousChallengersEncounter: IMysteryEncounter = +export const MysteriousChallengersEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHALLENGERS) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index f0a96356eef..1d965e38fa6 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -5,7 +5,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier"; import { randSeedInt } from "#app/utils.js"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -18,7 +18,7 @@ const namespace = "mysteryEncounter:mysteriousChest"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/32 | GitHub Issue #32} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const MysteriousChestEncounter: IMysteryEncounter = +export const MysteriousChestEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.MYSTERIOUS_CHEST) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) // waves 2 to 180 @@ -116,9 +116,8 @@ export const MysteriousChestEncounter: IMysteryEncounter = scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.getNameToRender()); // Show which Pokemon was KOed, then leave encounter with no rewards // Does this synchronously so that game over doesn't happen over result message - await showEncounterText(scene, `${namespace}.option.1.bad`).then(() => { - leaveEncounterWithoutBattle(scene); - }); + await showEncounterText(scene, `${namespace}.option.1.bad`); + leaveEncounterWithoutBattle(scene); } }) .build() diff --git a/src/data/mystery-encounters/encounters/part-timer-encounter.ts b/src/data/mystery-encounters/encounters/part-timer-encounter.ts index a7f53976bbb..c66a8efe5f7 100644 --- a/src/data/mystery-encounters/encounters/part-timer-encounter.ts +++ b/src/data/mystery-encounters/encounters/part-timer-encounter.ts @@ -2,7 +2,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst import { leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, setEncounterRewards, transitionMysteryEncounterIntroVisuals, updatePlayerMoney } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MoveRequirement } from "../mystery-encounter-requirements"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; @@ -20,7 +20,7 @@ const namespace = "mysteryEncounter:partTimer"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/82 | GitHub Issue #82} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const PartTimerEncounter: IMysteryEncounter = +export const PartTimerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.PART_TIMER) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) @@ -115,7 +115,7 @@ export const PartTimerEncounter: IMysteryEncounter = // Only Pokemon non-KOd pokemon can be selected const selectableFilter = (pokemon: Pokemon) => { if (!pokemon.isAllowedInBattle()) { - return getEncounterText(scene, `${namespace}:invalid_selection`); + return getEncounterText(scene, `${namespace}.invalid_selection`); } return null; @@ -194,7 +194,7 @@ export const PartTimerEncounter: IMysteryEncounter = // Only Pokemon non-KOd pokemon can be selected const selectableFilter = (pokemon: Pokemon) => { if (!pokemon.isAllowedInBattle()) { - return getEncounterText(scene, `${namespace}:invalid_selection`); + return getEncounterText(scene, `${namespace}.invalid_selection`); } return null; diff --git a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts index e110d43fc50..56e3bb2852b 100644 --- a/src/data/mystery-encounters/encounters/safari-zone-encounter.ts +++ b/src/data/mystery-encounters/encounters/safari-zone-encounter.ts @@ -1,7 +1,7 @@ import { initSubsequentOptionSelect, leaveEncounterWithoutBattle, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import MysteryEncounterOption, { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; import { TrainerSlot } from "#app/data/trainer-config"; import { ScanIvsPhase, SummonPhase } from "#app/phases"; @@ -17,7 +17,6 @@ import { getEncounterText, showEncounterText } from "#app/data/mystery-encounter import { getPokemonNameWithAffix } from "#app/messages"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:safariZone"; @@ -29,7 +28,7 @@ const TRAINER_THROW_ANIMATION_TIMES = [512, 184, 768]; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/39 | GitHub Issue #39} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const SafariZoneEncounter: IMysteryEncounter = +export const SafariZoneEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SAFARI_ZONE) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) @@ -66,7 +65,7 @@ export const SafariZoneEncounter: IMysteryEncounter = .withOptionPhase(async (scene: BattleScene) => { // Start safari encounter const encounter = scene.currentBattle.mysteryEncounter; - encounter.encounterMode = MysteryEncounterMode.CONTINUOUS_ENCOUNTER; + encounter.continuousEncounter = true; encounter.misc = { safariPokemonRemaining: 3 }; @@ -104,7 +103,7 @@ export const SafariZoneEncounter: IMysteryEncounter = /** * SAFARI ZONE MINIGAME OPTIONS * - * Catch and flee rate **stages** are calculated in the same way stat changes are (they range from -6/+6) + * Catch and flee rate stages are calculated in the same way stat changes are (they range from -6/+6) * https://bulbapedia.bulbagarden.net/wiki/Catch_rate#Great_Marsh_and_Johto_Safari_Zone * * Catch Rate calculation: @@ -130,21 +129,23 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [ }) .withOptionPhase(async (scene: BattleScene) => { // Throw a ball option - const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon; + const encounter = scene.currentBattle.mysteryEncounter; + const pokemon = encounter.misc.pokemon; const catchResult = await throwPokeball(scene, pokemon); if (catchResult) { // You caught pokemon // Check how many safari pokemon left - if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) { + if (encounter.misc.safariPokemonRemaining > 0) { await summonSafariPokemon(scene); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 0, hideDescription: true }); } else { // End safari mode + encounter.continuousEncounter = false; leaveEncounterWithoutBattle(scene, true); } } else { - // Pokemon failed to catch, end turn + // Pokemon catch failed, end turn await doEndTurn(scene, 0); } return true; @@ -217,14 +218,16 @@ const safariZoneGameOptions: MysteryEncounterOption[] = [ }) .withOptionPhase(async (scene: BattleScene) => { // Flee option - const pokemon = scene.currentBattle.mysteryEncounter.misc.pokemon; + const encounter = scene.currentBattle.mysteryEncounter; + const pokemon = encounter.misc.pokemon; await doPlayerFlee(scene, pokemon); // Check how many safari pokemon left - if (scene.currentBattle.mysteryEncounter.misc.safariPokemonRemaining > 0) { + if (encounter.misc.safariPokemonRemaining > 0) { await summonSafariPokemon(scene); initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: 3, hideDescription: true }); } else { // End safari mode + encounter.continuousEncounter = false; leaveEncounterWithoutBattle(scene, true); } return true; @@ -487,6 +490,7 @@ async function doEndTurn(scene: BattleScene, cursorIndex: number) { initSubsequentOptionSelect(scene, { overrideOptions: safariZoneGameOptions, startingCursorIndex: cursorIndex, hideDescription: true }); } else { // End safari mode + encounter.continuousEncounter = false; leaveEncounterWithoutBattle(scene, true); } } else { 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 86a8a614ba6..836c0cb1f98 100644 --- a/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts +++ b/src/data/mystery-encounters/encounters/shady-vitamin-dealer-encounter.ts @@ -1,4 +1,4 @@ -import { generateModifierTypeOption, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType, leaveEncounterWithoutBattle, selectPokemonForOption, setEncounterExp, updatePlayerMoney, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { StatusEffect } from "#app/data/status-effect"; import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import { modifierTypes } from "#app/modifier/modifier-type"; @@ -6,7 +6,7 @@ import { randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MoneyRequirement } from "../mystery-encounter-requirements"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; @@ -22,7 +22,7 @@ const namespace = "mysteryEncounter:shadyVitaminDealer"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/34 | GitHub Issue #34} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const ShadyVitaminDealerEncounter: IMysteryEncounter = +export const ShadyVitaminDealerEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) @@ -79,8 +79,8 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter = updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens const modifiers = [ - generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, - generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, + generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER), + generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER), ]; encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost2", modifiers[1].name); @@ -95,7 +95,7 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter = // If pokemon meets primary pokemon reqs, it can be selected const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon); if (!meetsReqs) { - return getEncounterText(scene, `${namespace}:invalid_selection`); + return getEncounterText(scene, `${namespace}.invalid_selection`); } return null; @@ -162,8 +162,8 @@ export const ShadyVitaminDealerEncounter: IMysteryEncounter = updatePlayerMoney(scene, -(encounter.options[1].requirements[0] as MoneyRequirement).requiredMoney); // Calculate modifiers and dialogue tokens const modifiers = [ - generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, - generateModifierTypeOption(scene, modifierTypes.BASE_STAT_BOOSTER).type, + generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER), + generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER), ]; encounter.setDialogueToken("boost1", modifiers[0].name); encounter.setDialogueToken("boost2", modifiers[1].name); diff --git a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts index 6d56f2cde46..4f24ca69936 100644 --- a/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts +++ b/src/data/mystery-encounters/encounters/slumbering-snorlax-encounter.ts @@ -4,7 +4,7 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import { StatusEffect } from "#app/data/status-effect"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { MoveRequirement } from "../mystery-encounter-requirements"; import { EnemyPartyConfig, EnemyPokemonConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterExp, setEncounterRewards, } from "../utils/encounter-phase-utils"; @@ -25,7 +25,7 @@ const namespace = "mysteryEncounter:slumberingSnorlax"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/103 | GitHub Issue #103} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const SlumberingSnorlaxEncounter: IMysteryEncounter = +export const SlumberingSnorlaxEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.SLUMBERING_SNORLAX) .withEncounterTier(MysteryEncounterTier.GREAT) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 diff --git a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts index 80b7354b747..e1c7ff8a7f6 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -2,7 +2,7 @@ import { leaveEncounterWithoutBattle, transitionMysteryEncounterIntroVisuals, up import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MoneyRequirement } from "../mystery-encounter-requirements"; import { catchPokemon, getRandomSpeciesByStarterTier, getSpriteKeysFromPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; @@ -25,7 +25,7 @@ const MAX_POKEMON_PRICE_MULTIPLIER = 6; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/36 | GitHub Issue #36} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const ThePokemonSalesmanEncounter: IMysteryEncounter = +export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_POKEMON_SALESMAN) .withEncounterTier(MysteryEncounterTier.ULTRA) .withSceneWaveRangeRequirement(10, 180) 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 36ac31f8046..fd67d2a0234 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -1,8 +1,8 @@ -import { EnemyPartyConfig, generateModifierTypeOption, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { EnemyPartyConfig, initBattleWithEnemyConfig, loadCustomMovesForEncounter, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { modifierTypes, PokemonHeldItemModifierType, } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { Species } from "#enums/species"; import { Nature } from "#app/data/nature"; @@ -26,7 +26,7 @@ const namespace = "mysteryEncounter:theStrongStuff"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/54 | GitHub Issue #54} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const TheStrongStuffEncounter: IMysteryEncounter = +export const TheStrongStuffEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_STRONG_STUFF) .withEncounterTier(MysteryEncounterTier.COMMON) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 @@ -74,12 +74,20 @@ export const TheStrongStuffEncounter: IMysteryEncounter = mysteryEncounterData: new MysteryEncounterPokemonData(1.5), nature: Nature.BOLD, moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER], - modifierTypes: [ - generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType, - generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.APICOT]).type as PokemonHeldItemModifierType, - generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.GANLON]).type as PokemonHeldItemModifierType, - generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.LUM]).type as PokemonHeldItemModifierType, - generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.LUM]).type as PokemonHeldItemModifierType + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, + stackCount: 2 + } ], tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: (pokemon: Pokemon) => { diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts new file mode 100644 index 00000000000..c0886ed9f6b --- /dev/null +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -0,0 +1,489 @@ +import { EnemyPartyConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import BattleScene from "#app/battle-scene"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { TrainerType } from "#enums/trainer-type"; +import { Species } from "#enums/species"; +import { Abilities } from "#enums/abilities"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { Moves } from "#enums/moves"; +import { Nature } from "#enums/nature"; +import { Type } from "#app/data/type"; +import { BerryType } from "#enums/berry-type"; +import { Stat } from "#enums/stat"; +import { PartyHealPhase, ReturnPhase, ShowTrainerPhase } from "#app/phases"; +import { SpeciesFormChangeManualTrigger } from "#app/data/pokemon-forms"; +import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/ability"; +import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; +import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; + +/** the i18n namespace for the encounter */ +const namespace = "mysteryEncounter:theWinstrateChallenge"; + +/** + * The Winstrate Challenge encounter. + * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/136 | GitHub Issue #136} + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} + */ +export const TheWinstrateChallengeEncounter: MysteryEncounter = + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.THE_WINSTRATE_CHALLENGE) + .withEncounterTier(MysteryEncounterTier.ROGUE) + .withSceneWaveRangeRequirement(80, 180) + .withIntroSpriteConfigs([ + { + spriteKey: "vito", + fileRoot: "trainer", + hasShadow: false, + x: 16, + y: -4 + }, + { + spriteKey: "vivi", + fileRoot: "trainer", + hasShadow: false, + x: -14, + y: -4 + }, + { + spriteKey: "victor", + fileRoot: "trainer", + hasShadow: true, + x: -32 + }, + { + spriteKey: "victoria", + fileRoot: "trainer", + hasShadow: true, + x: 40, + }, + { + spriteKey: "vicky", + fileRoot: "trainer", + hasShadow: true, + x: 3, + y: 5, + yShadow: 5 + }, + ]) + .withIntroDialogue([ + { + text: `${namespace}.intro`, + }, + { + speaker: `${namespace}.speaker`, + text: `${namespace}.intro_dialogue`, + }, + ]) + .withAutoHideIntroVisuals(false) + .withOnInit((scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + + // Loaded back to front for pop() operations + encounter.enemyPartyConfigs.push(getVitoTrainerConfig(scene)); + encounter.enemyPartyConfigs.push(getVickyTrainerConfig(scene)); + encounter.enemyPartyConfigs.push(getViviTrainerConfig(scene)); + encounter.enemyPartyConfigs.push(getVictoriaTrainerConfig(scene)); + encounter.enemyPartyConfigs.push(getVictorTrainerConfig(scene)); + + return true; + }) + .withTitle(`${namespace}.title`) + .withDescription(`${namespace}.description`) + .withQuery(`${namespace}.query`) + .withSimpleOption( + { + buttonLabel: `${namespace}.option.1.label`, + buttonTooltip: `${namespace}.option.1.tooltip`, + selected: [ + { + speaker: "trainerNames:victor", + text: `${namespace}.option.1.selected`, + }, + ], + }, + async (scene: BattleScene) => { + // Spawn 5 trainer battles back to back with Macho Brace in rewards + // scene.currentBattle.mysteryEncounter.continuousEncounter = true; + scene.currentBattle.mysteryEncounter.doContinueEncounter = (scene: BattleScene) => { + return endTrainerBattleAndShowDialogue(scene); + }; + await transitionMysteryEncounterIntroVisuals(scene, true, false); + await spawnNextTrainerOrEndEncounter(scene); + } + ) + .withSimpleOption( + { + buttonLabel: `${namespace}.option.2.label`, + buttonTooltip: `${namespace}.option.2.tooltip`, + selected: [ + { + speaker: `${namespace}.speaker`, + text: `${namespace}.option.2.selected`, + }, + ], + }, + async (scene: BattleScene) => { + // Refuse the challenge, they full heal the party and give the player a Rarer Candy + scene.unshiftPhase(new PartyHealPhase(scene, true)); + setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.RARER_CANDY], fillRemaining: false }); + leaveEncounterWithoutBattle(scene); + } + ) + .build(); + +async function spawnNextTrainerOrEndEncounter(scene: BattleScene) { + const encounter = scene.currentBattle.mysteryEncounter; + const nextConfig = encounter.enemyPartyConfigs.pop(); + if (!nextConfig) { + await transitionMysteryEncounterIntroVisuals(scene, false, false); + await showEncounterDialogue(scene, `${namespace}.victory`, `${namespace}.speaker`); + setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.MYSTERY_ENCOUNTER_MACHO_BRACE], fillRemaining: false }); + encounter.doContinueEncounter = null; + leaveEncounterWithoutBattle(scene, false, MysteryEncounterMode.TRAINER_BATTLE); + } else { + await initBattleWithEnemyConfig(scene, nextConfig); + } +} + +function endTrainerBattleAndShowDialogue(scene: BattleScene): Promise { + return new Promise(async resolve => { + if (scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length === 0) { + // Battle is over + scene.tweens.add({ + targets: scene.currentBattle.trainer, + x: "+=16", + y: "-=16", + alpha: 0, + ease: "Sine.easeInOut", + duration: 750, + onComplete: () => { + scene.field.remove(scene.currentBattle.trainer, true); + } + }); + await spawnNextTrainerOrEndEncounter(scene); + resolve(); // Wait for all dialogue/post battle stuff to complete before resolving + } else { + scene.arena.resetArenaEffects(); + const playerField = scene.getPlayerField(); + playerField.forEach((_, p) => scene.unshiftPhase(new ReturnPhase(scene, p))); + + for (const pokemon of scene.getParty()) { + // Only trigger form change when Eiscue is in Noice form + // Hardcoded Eiscue for now in case it is fused with another pokemon + if (pokemon.species.speciesId === Species.EISCUE && pokemon.hasAbility(Abilities.ICE_FACE) && pokemon.formIndex === 1) { + scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeManualTrigger); + } + + pokemon.resetBattleData(); + applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon); + } + + scene.unshiftPhase(new ShowTrainerPhase(scene)); + // Hide the trainer and init next battle + const trainer = scene.currentBattle.trainer; + // Unassign previous trainer from battle so it isn't destroyed before animation completes + scene.currentBattle.trainer = null; + await spawnNextTrainerOrEndEncounter(scene); + scene.tweens.add({ + targets: trainer, + x: "+=16", + y: "-=16", + alpha: 0, + ease: "Sine.easeInOut", + duration: 750, + onComplete: () => { + scene.field.remove(trainer, true); + resolve(); + } + }); + } + }); +} + +function getVictorTrainerConfig(scene: BattleScene): EnemyPartyConfig { + return { + trainerType: TrainerType.VICTOR, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.SWELLOW), + isBoss: false, + abilityIndex: 0, // Guts + nature: Nature.ADAMANT, + moveSet: [Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, + isTransferable: false + }, + { + modifierType: generateModifierType(scene, modifierTypes.FOCUS_BAND) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + }, + ] + }, + { + species: getPokemonSpecies(Species.OBSTAGOON), + isBoss: false, + abilityIndex: 1, // Guts + nature: Nature.ADAMANT, + moveSet: [Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.FLAME_ORB) as PokemonHeldItemModifierType, + isTransferable: false + }, + { + modifierType: generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + } + ] + } + ] + }; +} + +function getVictoriaTrainerConfig(scene: BattleScene): EnemyPartyConfig { + return { + trainerType: TrainerType.VICTORIA, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.ROSERADE), + isBoss: false, + abilityIndex: 0, // Natural Cure + nature: Nature.CALM, + moveSet: [Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType, + isTransferable: false + }, + { + modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + } + ] + }, + { + species: getPokemonSpecies(Species.GARDEVOIR), + isBoss: false, + formIndex: 1, + nature: Nature.TIMID, + moveSet: [Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.PSYCHIC]) as PokemonHeldItemModifierType, + stackCount: 1, + isTransferable: false + }, + { + modifierType: generateModifierType(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FAIRY]) as PokemonHeldItemModifierType, + stackCount: 1, + isTransferable: false + } + ] + } + ] + }; +} + +function getViviTrainerConfig(scene: BattleScene): EnemyPartyConfig { + return { + trainerType: TrainerType.VIVI, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.SEAKING), + isBoss: false, + abilityIndex: 3, // Lightning Rod + nature: Nature.ADAMANT, + moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + }, + { + modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, + stackCount: 4, + isTransferable: false + } + ] + }, + { + species: getPokemonSpecies(Species.BRELOOM), + isBoss: false, + abilityIndex: 1, // Poison Heal + nature: Nature.JOLLY, + moveSet: [Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.HP]) as PokemonHeldItemModifierType, + stackCount: 4, + isTransferable: false + }, + { + modifierType: generateModifierType(scene, modifierTypes.TOXIC_ORB) as PokemonHeldItemModifierType, + isTransferable: false + } + ] + }, + { + species: getPokemonSpecies(Species.CAMERUPT), + isBoss: false, + formIndex: 1, + nature: Nature.CALM, + moveSet: [Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, + stackCount: 3, + isTransferable: false + }, + ] + } + ] + }; +} + +function getVickyTrainerConfig(scene: BattleScene): EnemyPartyConfig { + return { + trainerType: TrainerType.VICKY, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.MEDICHAM), + isBoss: false, + formIndex: 1, + nature: Nature.IMPISH, + moveSet: [Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType, + isTransferable: false + } + ] + } + ] + }; +} + +function getVitoTrainerConfig(scene: BattleScene): EnemyPartyConfig { + return { + trainerType: TrainerType.VITO, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.HISUI_ELECTRODE), + isBoss: false, + abilityIndex: 0, // Soundproof + nature: Nature.MODEST, + moveSet: [Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER, [Stat.SPD]) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + } + ] + }, + { + species: getPokemonSpecies(Species.SWALOT), + isBoss: false, + abilityIndex: 2, // Gluttony + nature: Nature.QUIET, + moveSet: [Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.APICOT]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.STARF]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.SALAC]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LUM]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LANSAT]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LIECHI]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.PETAYA]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.ENIGMA]) as PokemonHeldItemModifierType, + stackCount: 2, + }, + { + modifierType: generateModifierType(scene, modifierTypes.BERRY, [BerryType.LEPPA]) as PokemonHeldItemModifierType, + stackCount: 2, + } + ] + }, + { + species: getPokemonSpecies(Species.DODRIO), + isBoss: false, + abilityIndex: 2, // Tangled Feet + nature: Nature.JOLLY, + moveSet: [Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.KINGS_ROCK) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + } + ] + }, + { + species: getPokemonSpecies(Species.ALAKAZAM), + isBoss: false, + formIndex: 1, + nature: Nature.BOLD, + moveSet: [Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.WIDE_LENS) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + }, + ] + }, + { + species: getPokemonSpecies(Species.DARMANITAN), + isBoss: false, + abilityIndex: 0, // Sheer Force + nature: Nature.IMPISH, + moveSet: [Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE], + modifierConfigs: [ + { + modifierType: generateModifierType(scene, modifierTypes.QUICK_CLAW) as PokemonHeldItemModifierType, + stackCount: 2, + isTransferable: false + }, + ] + } + ] + }; +} diff --git a/src/data/mystery-encounters/encounters/training-session-encounter.ts b/src/data/mystery-encounters/encounters/training-session-encounter.ts index c6cd93068b9..9e89fd24d1d 100644 --- a/src/data/mystery-encounters/encounters/training-session-encounter.ts +++ b/src/data/mystery-encounters/encounters/training-session-encounter.ts @@ -14,7 +14,7 @@ import { randSeedShuffle } from "#app/utils"; import { BattlerTagType } from "#enums/battler-tag-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { getEncounterText, queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -28,7 +28,7 @@ const namespace = "mysteryEncounter:trainingSession"; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/43 | GitHub Issue #43} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const TrainingSessionEncounter: IMysteryEncounter = +export const TrainingSessionEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRAINING_SESSION) .withEncounterTier(MysteryEncounterTier.ULTRA) .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 @@ -444,7 +444,7 @@ function getEnemyConfig(scene: BattleScene, playerPokemon: PlayerPokemon,segment formIndex: playerPokemon.formIndex, level: playerPokemon.level, dataSource: data, - modifierTypes: modifierTypes, + modifierConfigs: modifierTypes, }, ], }; 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 0ea9d42ca63..4f21f70c005 100644 --- a/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts +++ b/src/data/mystery-encounters/encounters/trash-to-treasure-encounter.ts @@ -1,8 +1,8 @@ -import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierTypeOption, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { EnemyPartyConfig, EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, loadCustomMovesForEncounter, setEncounterRewards, transitionMysteryEncounterIntroVisuals, } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { ModifierRewardPhase } from "#app/phases"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -28,7 +28,7 @@ const SOUND_EFFECT_WAIT_TIME = 700; * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/74 | GitHub Issue #74} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const TrashToTreasureEncounter: IMysteryEncounter = +export const TrashToTreasureEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.TRASH_TO_TREASURE) .withEncounterTier(MysteryEncounterTier.ULTRA) .withSceneWaveRangeRequirement(10, 180) @@ -147,8 +147,8 @@ export const TrashToTreasureEncounter: IMysteryEncounter = .build(); async function tryApplyDigRewardItems(scene: BattleScene) { - const shellBell = generateModifierTypeOption(scene, modifierTypes.SHELL_BELL).type as PokemonHeldItemModifierType; - const leftovers = generateModifierTypeOption(scene, modifierTypes.LEFTOVERS).type as PokemonHeldItemModifierType; + const shellBell = generateModifierType(scene, modifierTypes.SHELL_BELL) as PokemonHeldItemModifierType; + const leftovers = generateModifierType(scene, modifierTypes.LEFTOVERS) as PokemonHeldItemModifierType; const party = scene.getParty(); diff --git a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts index 2f141566648..9f8005575fb 100644 --- a/src/data/mystery-encounters/encounters/weird-dream-encounter.ts +++ b/src/data/mystery-encounters/encounters/weird-dream-encounter.ts @@ -2,7 +2,7 @@ import { Type } from "#app/data/type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; -import IMysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; +import MysteryEncounter, { MysteryEncounterBuilder } from "../mystery-encounter"; import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; import { leaveEncounterWithoutBattle, setEncounterRewards, } from "../utils/encounter-phase-utils"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; @@ -82,7 +82,7 @@ const excludedPokemon = [ * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/137 | GitHub Issue #137} * @see For biome requirements check {@linkcode mysteryEncountersByBiome} */ -export const WeirdDreamEncounter: IMysteryEncounter = +export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.WEIRD_DREAM) .withEncounterTier(MysteryEncounterTier.ROGUE) .withIntroSpriteConfigs([ @@ -205,11 +205,6 @@ export const WeirdDreamEncounter: IMysteryEncounter = return true; } ) - .withOutroDialogue([ - { - text: `${namespace}.outro` - } - ]) .build(); interface PokemonTransformation { diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index b1b47cef8ef..6d2bd27f88b 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -6,25 +6,16 @@ import BattleScene from "#app/battle-scene"; import MysteryEncounterIntroVisuals, { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro"; import * as Utils from "#app/utils"; import { StatusEffect } from "../status-effect"; -import MysteryEncounterDialogue, { - OptionTextDisplay -} from "./mystery-encounter-dialogue"; +import MysteryEncounterDialogue, { OptionTextDisplay } from "./mystery-encounter-dialogue"; import MysteryEncounterOption, { MysteryEncounterOptionBuilder, OptionPhaseCallback } from "./mystery-encounter-option"; -import { - EncounterPokemonRequirement, - EncounterSceneRequirement, - HealthRatioRequirement, - PartySizeRequirement, - StatusEffectRequirement, - WaveRangeRequirement -} from "./mystery-encounter-requirements"; +import { EncounterPokemonRequirement, EncounterSceneRequirement, HealthRatioRequirement, PartySizeRequirement, StatusEffectRequirement, WaveRangeRequirement } from "./mystery-encounter-requirements"; import { BattlerIndex } from "#app/battle"; import { EncounterAnim } from "#app/data/battle-anims"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; -export interface StartOfBattleEffect { +export interface EncounterStartOfBattleEffect { sourcePokemon?: Pokemon; sourceBattlerIndex?: BattlerIndex; targets: BattlerIndex[]; @@ -33,7 +24,7 @@ export interface StartOfBattleEffect { followUp?: boolean; } -export default interface IMysteryEncounter { +export default interface MysteryEncounter { /** * Required params */ @@ -44,13 +35,44 @@ export default interface IMysteryEncounter { * Optional params */ encounterTier?: MysteryEncounterTier; + /** + * Custom battle animations that are configured for encounter effects and visuals + * Specify here so that assets are loaded on initialization of encounter + */ encounterAnimations?: EncounterAnim[]; + /** + * If true, hides "A Wild X Appeared" etc. messages + * Default true + */ hideBattleIntroMessage?: boolean; + /** + * If true, when an option is selected the field visuals will fade out automatically + * Default false + */ autoHideIntroVisuals?: boolean; + /** + * Intro visuals on the field will slide in from the right instead of the left + * Default false + */ enterIntroVisualsFromRight?: boolean; + /** + * If true, allows catching a wild pokemon during the encounter + * Default false + */ catchAllowed?: boolean; + /** + * If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave + * MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE + * Default false + */ + continuousEncounter?: boolean; + /** + * Maximum number of times the encounter can be seen per run + * Rogue tier encounters default to 1, others default to 3 + */ maxAllowedEncounters?: number; + /** * Event callback functions */ @@ -62,6 +84,8 @@ export default interface IMysteryEncounter { doEncounterExp?: (scene: BattleScene) => boolean; /** Will provide the player a rewards shop for that wave */ doEncounterRewards?: (scene: BattleScene) => boolean; + /** Will execute callback during VictoryPhase of a continuousEncounter */ + doContinueEncounter?: (scene: BattleScene) => Promise; /** * Requirements @@ -90,6 +114,7 @@ export default interface IMysteryEncounter { /** * Data used for setting up/initializing enemy party in battles * Can store multiple configs so that one can be chosen based on option selected + * Should usually be defined in `onInit()` or `onPreOptionPhase()` */ enemyPartyConfigs?: EnemyPartyConfig[]; /** @@ -131,7 +156,7 @@ export default interface IMysteryEncounter { /** * Will be set by option select handlers automatically, and can be used to refer to which option was chosen by later phases */ - startOfBattleEffects?: StartOfBattleEffect[]; + startOfBattleEffects?: EncounterStartOfBattleEffect[]; /** * Can be set higher or lower based on the type of battle or exp gained for an option/encounter * Defaults to 1 @@ -149,14 +174,14 @@ export default interface IMysteryEncounter { * These objects will be saved as part of session data any time the player is on a floor with an encounter * Unless you know what you're doing, you should use MysteryEncounterBuilder to create an instance for this class */ -export default class IMysteryEncounter implements IMysteryEncounter { +export default class MysteryEncounter implements MysteryEncounter { /** * Used for keeping RNG consistent on session resets, but increments when cycling through multiple "Encounters" on the same wave * You should only need to interact via getter/update methods */ private seedOffset?: any; - constructor(encounter: IMysteryEncounter) { + constructor(encounter: MysteryEncounter) { if (!isNullOrUndefined(encounter)) { Object.assign(this, encounter); } @@ -170,6 +195,7 @@ export default class IMysteryEncounter implements IMysteryEncounter { this.hideBattleIntroMessage = this.hideBattleIntroMessage ?? false; this.autoHideIntroVisuals = this.autoHideIntroVisuals ?? true; this.enterIntroVisualsFromRight = this.enterIntroVisualsFromRight ?? false; + this.continuousEncounter = this.continuousEncounter ?? false; // Reset any dirty flags or encounter data this.startOfBattleEffectsComplete = false; @@ -238,11 +264,11 @@ export default class IMysteryEncounter implements IMysteryEncounter { } if (truePrimaryPool.length > 0) { - // always choose from the non-overlapping pokemon first + // Always choose from the non-overlapping pokemon first this.primaryPokemon = truePrimaryPool[Utils.randSeedInt(truePrimaryPool.length, 0)]; return true; } else { - // if there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool + // If there are multiple overlapping pokemon, we're okay - just choose one and take it out of the primary pokemon pool if (overlap.length > 1 || (this.secondaryPokemon.length - overlap.length >= 1)) { // is this working? this.primaryPokemon = overlap[Utils.randSeedInt(overlap.length, 0)]; @@ -371,7 +397,7 @@ export default class IMysteryEncounter implements IMysteryEncounter { } /** - * If an encounter uses {@link MysteryEncounterMode.CONTINUOUS_ENCOUNTER}, + * If an encounter uses {@link MysteryEncounterMode.continuousEncounter}, * should rely on this value for seed offset instead of wave index. * * This offset is incremented for each new {@link MysteryEncounterPhase} that occurs, @@ -396,7 +422,11 @@ export default class IMysteryEncounter implements IMysteryEncounter { } } -export class MysteryEncounterBuilder implements Partial { +/** + * Builder class for creating a MysteryEncounter + * must call `build()` at the end after specifying all params for the MysteryEncounter + */ +export class MysteryEncounterBuilder implements Partial { encounterType?: MysteryEncounterType; options?: [MysteryEncounterOption, MysteryEncounterOption, ...MysteryEncounterOption[]] = [null, null]; spriteConfigs?: MysteryEncounterSpriteConfig[]; @@ -418,7 +448,7 @@ export class MysteryEncounterBuilder implements Partial { hideBattleIntroMessage?: boolean; hideIntroVisuals?: boolean; enterIntroVisualsFromRight?: boolean; - enemyPartyConfigs?: EnemyPartyConfig[] = []; + continuousEncounter?: boolean; /** * REQUIRED @@ -429,7 +459,7 @@ export class MysteryEncounterBuilder implements Partial { * @param encounterType * @returns this */ - static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick { + static withEncounterType(encounterType: MysteryEncounterType): MysteryEncounterBuilder & Pick { return Object.assign(new MysteryEncounterBuilder(), { encounterType: encounterType }); } @@ -442,7 +472,7 @@ export class MysteryEncounterBuilder implements Partial { * @param option - MysteryEncounterOption to add, can use MysteryEncounterOptionBuilder to create instance * @returns */ - withOption(option: MysteryEncounterOption): this & Pick { + withOption(option: MysteryEncounterOption): this & Pick { if (this.options[0] === null) { return Object.assign(this, { options: [option, this.options[0]] }); } else if (this.options[1] === null) { @@ -464,7 +494,7 @@ export class MysteryEncounterBuilder implements Partial { * @param callback - {@linkcode OptionPhaseCallback} * @returns */ - withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick { + withSimpleOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick { return this.withOption(new MysteryEncounterOptionBuilder().withOptionMode(MysteryEncounterOptionMode.DEFAULT).withDialogue(dialogue).withOptionPhase(callback).build()); } @@ -478,7 +508,7 @@ export class MysteryEncounterBuilder implements Partial { * @param callback - {@linkcode OptionPhaseCallback} * @returns */ - withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick { + withSimpleDexProgressOption(dialogue: OptionTextDisplay, callback: OptionPhaseCallback): this & Pick { return this.withOption(new MysteryEncounterOptionBuilder() .withOptionMode(MysteryEncounterOptionMode.DEFAULT) .withHasDexProgress(true) @@ -492,7 +522,7 @@ export class MysteryEncounterBuilder implements Partial { * @param spriteConfigs * @returns */ - withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick { + withIntroSpriteConfigs(spriteConfigs: MysteryEncounterSpriteConfig[]): this & Pick { return Object.assign(this, { spriteConfigs: spriteConfigs }); } @@ -521,28 +551,38 @@ export class MysteryEncounterBuilder implements Partial { * @param encounterTier * @returns */ - withEncounterTier(encounterTier: MysteryEncounterTier): this & Required> { + withEncounterTier(encounterTier: MysteryEncounterTier): this & Required> { return Object.assign(this, { encounterTier: encounterTier }); } /** * Defines any EncounterAnim animations that are intended to be used during the encounter - * EncounterAnims can be played at any point during an encounter or callback + * EncounterAnims are custom battle animations (think Ice Beam) that can be played at any point during an encounter or callback * They just need to be specified here so that resources are loaded on encounter init * @param encounterAnimations * @returns */ - withAnimations(...encounterAnimations: EncounterAnim[]): this & Required> { + withAnimations(...encounterAnimations: EncounterAnim[]): this & Required> { const animations = Array.isArray(encounterAnimations) ? encounterAnimations : [encounterAnimations]; return Object.assign(this, { encounterAnimations: animations }); } + /** + * If true, encounter will continuously run through multiple battles/puzzles/etc. instead of going to next wave + * MUST EVENTUALLY BE DISABLED TO CONTINUE TO NEXT WAVE + * Default false + * @param continuousEncounter + */ + withContinuousEncounter(continuousEncounter: boolean): this & Required> { + return Object.assign(this, { continuousEncounter: continuousEncounter }); + } + /** * Sets the maximum number of times that an encounter can spawn in a given Classic run * @param maxAllowedEncounters * @returns */ - withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required> { + withMaxAllowedEncounters(maxAllowedEncounters: number): this & Required> { return Object.assign(this, { maxAllowedEncounters: maxAllowedEncounters }); } @@ -553,7 +593,7 @@ export class MysteryEncounterBuilder implements Partial { * @param requirement * @returns */ - withSceneRequirement(requirement: EncounterSceneRequirement): this & Required> { + withSceneRequirement(requirement: EncounterSceneRequirement): this & Required> { if (requirement instanceof EncounterPokemonRequirement) { Error("Incorrectly added pokemon requirement as scene requirement."); } @@ -568,7 +608,7 @@ export class MysteryEncounterBuilder implements Partial { * @param max optional max wave. If not given, defaults to min => exact wave * @returns */ - withSceneWaveRangeRequirement(min: number, max?: number): this & Required> { + withSceneWaveRangeRequirement(min: number, max?: number): this & Required> { return this.withSceneRequirement(new WaveRangeRequirement([min, max ?? min])); } @@ -580,7 +620,7 @@ export class MysteryEncounterBuilder implements Partial { * @param excludeFainted - if true, only counts unfainted mons * @returns */ - withScenePartySizeRequirement(min: number, max?: number, excludeFainted?: boolean): this & Required> { + withScenePartySizeRequirement(min: number, max?: number, excludeFainted?: boolean): this & Required> { return this.withSceneRequirement(new PartySizeRequirement([min, max ?? min], excludeFainted)); } @@ -590,7 +630,7 @@ export class MysteryEncounterBuilder implements Partial { * @param requirement {@linkcode EncounterPokemonRequirement} * @returns */ - withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required> { + withPrimaryPokemonRequirement(requirement: EncounterPokemonRequirement): this & Required> { if (requirement instanceof EncounterSceneRequirement) { Error("Incorrectly added scene requirement as pokemon requirement."); } @@ -607,7 +647,7 @@ export class MysteryEncounterBuilder implements Partial { * @param invertQuery if true will invert the query * @returns */ - withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required> { + withPrimaryPokemonStatusEffectRequirement(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required> { return this.withPrimaryPokemonRequirement(new StatusEffectRequirement(statusEffect, minNumberOfPokemon, invertQuery)); } @@ -619,14 +659,14 @@ export class MysteryEncounterBuilder implements Partial { * @param invertQuery if true will invert the query * @returns */ - withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required> { + withPrimaryPokemonHealthRatioRequirement(requiredHealthRange: [number, number], minNumberOfPokemon: number = 1, invertQuery: boolean = false): this & Required> { return this.withPrimaryPokemonRequirement(new HealthRatioRequirement(requiredHealthRange, minNumberOfPokemon, invertQuery)); } // TODO: Maybe add an optional parameter for excluding primary pokemon from the support cast? // ex. if your only grass type pokemon, a snivy, is chosen as primary, if the support pokemon requires a grass type, the event won't trigger because // it's already been - withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required> { + withSecondaryPokemonRequirement(requirement: EncounterPokemonRequirement, excludePrimaryFromSecondaryRequirements: boolean = false): this & Required> { if (requirement instanceof EncounterSceneRequirement) { Error("Incorrectly added scene requirement as pokemon requirement."); } @@ -646,7 +686,7 @@ export class MysteryEncounterBuilder implements Partial { * @param doEncounterRewards - synchronous callback function to perform during rewards phase of the encounter * @returns */ - withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required> { + withRewards(doEncounterRewards: (scene: BattleScene) => boolean): this & Required> { return Object.assign(this, { doEncounterRewards: doEncounterRewards }); } @@ -660,7 +700,7 @@ export class MysteryEncounterBuilder implements Partial { * @param doEncounterExp - synchronous callback function to perform during rewards phase of the encounter * @returns */ - withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required> { + withExp(doEncounterExp: (scene: BattleScene) => boolean): this & Required> { return Object.assign(this, { doEncounterExp: doEncounterExp }); } @@ -671,7 +711,7 @@ export class MysteryEncounterBuilder implements Partial { * @param onInit - synchronous callback function to perform as soon as the encounter is selected for the next phase * @returns */ - withOnInit(onInit: (scene: BattleScene) => boolean): this & Required> { + withOnInit(onInit: (scene: BattleScene) => boolean): this & Required> { return Object.assign(this, { onInit: onInit }); } @@ -681,27 +721,17 @@ export class MysteryEncounterBuilder implements Partial { * @param onVisualsStart - synchronous callback function to perform as soon as the enemy field finishes sliding in * @returns */ - withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required> { + withOnVisualsStart(onVisualsStart: (scene: BattleScene) => boolean): this & Required> { return Object.assign(this, { onVisualsStart: onVisualsStart }); } - /** - * Defines any enemies to use for a battle from the mystery encounter - * @param enemyPartyConfig - * @returns - */ - withEnemyPartyConfig(enemyPartyConfig: EnemyPartyConfig): this & Required> { - this.enemyPartyConfigs.push(enemyPartyConfig); - return Object.assign(this, { enemyPartyConfigs: this.enemyPartyConfigs }); - } - /** * Can set whether catching is allowed or not on the encounter * This flag can also be programmatically set inside option event functions or elsewhere * @param catchAllowed - if true, allows enemy pokemon to be caught during the encounter * @returns */ - withCatchAllowed(catchAllowed: boolean): this & Required> { + withCatchAllowed(catchAllowed: boolean): this & Required> { return Object.assign(this, { catchAllowed: catchAllowed }); } @@ -709,7 +739,7 @@ export class MysteryEncounterBuilder implements Partial { * @param hideBattleIntroMessage - if true, will not show the trainerAppeared/wildAppeared/bossAppeared message for an encounter * @returns */ - withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required> { + withHideWildIntroMessage(hideBattleIntroMessage: boolean): this & Required> { return Object.assign(this, { hideBattleIntroMessage: hideBattleIntroMessage }); } @@ -717,7 +747,7 @@ export class MysteryEncounterBuilder implements Partial { * @param autoHideIntroVisuals - if false, will not hide the intro visuals that are displayed at the beginning of encounter * @returns */ - withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required> { + withAutoHideIntroVisuals(autoHideIntroVisuals: boolean): this & Required> { return Object.assign(this, { autoHideIntroVisuals: autoHideIntroVisuals }); } @@ -726,7 +756,7 @@ export class MysteryEncounterBuilder implements Partial { * Default false * @returns */ - withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required> { + withEnterIntroVisualsFromRight(enterIntroVisualsFromRight: boolean): this & Required> { return Object.assign(this, { enterIntroVisualsFromRight: enterIntroVisualsFromRight }); } @@ -806,7 +836,7 @@ export class MysteryEncounterBuilder implements Partial { * * @returns */ - build(this: IMysteryEncounter): IMysteryEncounter { - return new IMysteryEncounter(this); + build(this: MysteryEncounter): MysteryEncounter { + return new MysteryEncounter(this); } } diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index f73673706ea..44f4ecf2420 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -10,7 +10,7 @@ import { MysteriousChestEncounter } from "./encounters/mysterious-chest-encounte import { ShadyVitaminDealerEncounter } from "./encounters/shady-vitamin-dealer-encounter"; import { SlumberingSnorlaxEncounter } from "./encounters/slumbering-snorlax-encounter"; import { TrainingSessionEncounter } from "./encounters/training-session-encounter"; -import IMysteryEncounter from "./mystery-encounter"; +import MysteryEncounter from "./mystery-encounter"; import { SafariZoneEncounter } from "#app/data/mystery-encounters/encounters/safari-zone-encounter"; import { FieryFalloutEncounter } from "#app/data/mystery-encounters/encounters/fiery-fallout-encounter"; import { TheStrongStuffEncounter } from "#app/data/mystery-encounters/encounters/the-strong-stuff-encounter"; @@ -25,6 +25,7 @@ import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters import { PartTimerEncounter } from "#app/data/mystery-encounters/encounters/part-timer-encounter"; import { DancingLessonsEncounter } from "#app/data/mystery-encounters/encounters/dancing-lessons-encounter"; import { WeirdDreamEncounter } from "#app/data/mystery-encounters/encounters/weird-dream-encounter"; +import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; @@ -132,7 +133,7 @@ export const CIVILIZATION_ENCOUNTER_BIOMES = [ Biome.ISLAND ]; -export const allMysteryEncounters: { [encounterType: number]: IMysteryEncounter } = {}; +export const allMysteryEncounters: { [encounterType: number]: MysteryEncounter } = {}; const extremeBiomeEncounters: MysteryEncounterType[] = []; @@ -147,6 +148,7 @@ const humanTransitableBiomeEncounters: MysteryEncounterType[] = [ MysteryEncounterType.SHADY_VITAMIN_DEALER, MysteryEncounterType.THE_POKEMON_SALESMAN, MysteryEncounterType.AN_OFFER_YOU_CANT_REFUSE, + MysteryEncounterType.THE_WINSTRATE_CHALLENGE ]; const civilizationBiomeEncounters: MysteryEncounterType[] = [ @@ -270,6 +272,7 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.PART_TIMER] = PartTimerEncounter; allMysteryEncounters[MysteryEncounterType.DANCING_LESSONS] = DancingLessonsEncounter; allMysteryEncounters[MysteryEncounterType.WEIRD_DREAM] = WeirdDreamEncounter; + allMysteryEncounters[MysteryEncounterType.THE_WINSTRATE_CHALLENGE] = TheWinstrateChallengeEncounter; // Add extreme encounters to biome map extremeBiomeEncounters.forEach(encounter => { diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index a4be81a3e08..c58e0fbad1f 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -3,9 +3,9 @@ import { biomeLinks, BiomePoolTier } from "#app/data/biomes"; import MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import { WEIGHT_INCREMENT_ON_SPAWN_MISS } from "#app/data/mystery-encounters/mystery-encounters"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; -import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import Pokemon, { FieldPosition, PlayerPokemon, PokemonMove, PokemonSummonData } from "#app/field/pokemon"; import { ExpBalanceModifier, ExpShareModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier } from "#app/modifier/modifier"; -import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; +import { CustomModifierSettings, ModifierPoolType, ModifierType, ModifierTypeGenerator, ModifierTypeOption, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { BattleEndPhase, EggLapsePhase, ExpPhase, GameOverPhase, MovePhase, SelectModifierPhase, ShowPartyExpBarPhase, TrainerVictoryPhase } from "#app/phases"; import { MysteryEncounterBattlePhase, MysteryEncounterBattleStartCleanupPhase, MysteryEncounterPhase, MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; import PokemonData from "#app/system/pokemon-data"; @@ -30,8 +30,8 @@ import { TrainerConfig, trainerConfigs, TrainerSlot } from "#app/data/trainer-co import PokemonSpecies from "#app/data/pokemon-species"; import Overrides from "#app/overrides"; import { Egg, IEggOptions } from "#app/data/egg"; -import { Abilities } from "#enums/abilities"; import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; +import HeldModifierConfig from "#app/interfaces/held-modifier-config"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -67,18 +67,18 @@ export interface EnemyPokemonConfig { bossSegmentModifier?: number; // Additive to the determined segment number mysteryEncounterData?: MysteryEncounterPokemonData; formIndex?: number; + abilityIndex?: number; level?: number; gender?: Gender; passive?: boolean; moveSet?: Moves[]; nature?: Nature; ivs?: [integer, integer, integer, integer, integer, integer]; - ability?: Abilities; shiny?: boolean; /** Can set just the status, or pass a timer on the status turns */ status?: StatusEffect | [StatusEffect, number]; mysteryEncounterBattleEffects?: (pokemon: Pokemon) => void; - modifierTypes?: PokemonHeldItemModifierType[]; + modifierConfigs?: HeldModifierConfig[]; tags?: BattlerTagType[]; dataSource?: PokemonData; } @@ -258,10 +258,13 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: } // Set summon data fields + if (!enemyPokemon.summonData) { + enemyPokemon.summonData = new PokemonSummonData(); + } // Set ability - if (!isNullOrUndefined(config.ability)) { - enemyPokemon.summonData.ability = config.ability; + if (!isNullOrUndefined(config.abilityIndex)) { + enemyPokemon.abilityIndex = config.abilityIndex; } // Set gender @@ -293,6 +296,7 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemyPokemon.initBattleInfo(); enemyPokemon.getBattleInfo().initInfo(enemyPokemon); + enemyPokemon.generateName(); } loadEnemyAssets.push(enemyPokemon.loadAssets()); @@ -315,8 +319,8 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: }); if (!loaded) { regenerateModifierPoolThresholds(scene.getEnemyField(), battle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD); - const customModifiers = partyConfig?.pokemonConfigs?.map(config => config?.modifierTypes); - scene.generateEnemyModifiers(customModifiers); + const customModifierTypes = partyConfig?.pokemonConfigs?.map(config => config?.modifierConfigs); + scene.generateEnemyModifiers(customModifierTypes); } } @@ -363,7 +367,7 @@ export function updatePlayerMoney(scene: BattleScene, changeValue: number, playS * @param modifier * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. */ -export function generateModifierTypeOption(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption { +export function generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType { const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier); let result: ModifierType = modifierTypes[modifierId]?.(); @@ -373,6 +377,17 @@ export function generateModifierTypeOption(scene: BattleScene, modifier: () => M .withTierFromPool(); result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result; + return result; +} + +/** + * Converts modifier bullshit to an actual item + * @param scene - Battle Scene + * @param modifier + * @param pregenArgs - can specify BerryType for berries, TM for TMs, AttackBoostType for item, etc. + */ +export function generateModifierTypeOption(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierTypeOption { + const result = generateModifierType(scene, modifier, pregenArgs); return new ModifierTypeOption(result, 0); } @@ -619,9 +634,10 @@ export function initSubsequentOptionSelect(scene: BattleScene, optionSelectSetti * Will skip any shops and rewards, and queue the next encounter phase as normal * @param scene * @param addHealPhase - when true, will add a shop phase to end of encounter with 0 rewards but healing items are available + * @param encounterMode - Can set custom encounter mode if necessary (may be required for forcing Pokemon to return before next phase) */ -export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false) { - scene.currentBattle.mysteryEncounter.encounterMode = MysteryEncounterMode.NO_BATTLE; +export function leaveEncounterWithoutBattle(scene: BattleScene, addHealPhase: boolean = false, encounterMode: MysteryEncounterMode = MysteryEncounterMode.NO_BATTLE) { + scene.currentBattle.mysteryEncounter.encounterMode = encounterMode; scene.clearPhaseQueue(); scene.clearPhaseQueueSplice(); handleMysteryEncounterVictory(scene, addHealPhase); @@ -644,18 +660,22 @@ export function handleMysteryEncounterVictory(scene: BattleScene, addHealPhase: // If in repeated encounter variant, do nothing // Variant must eventually be swapped in order to handle "true" end of the encounter - if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.CONTINUOUS_ENCOUNTER || doNotContinue) { + const encounter = scene.currentBattle.mysteryEncounter; + if (encounter.continuousEncounter || doNotContinue) { return; - } else if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { + } else if (encounter.encounterMode === MysteryEncounterMode.NO_BATTLE) { scene.pushPhase(new EggLapsePhase(scene)); scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); - } else if (!scene.getEnemyParty().find(p => scene.currentBattle.mysteryEncounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) { + } else if (!scene.getEnemyParty().find(p => encounter.encounterMode !== MysteryEncounterMode.TRAINER_BATTLE ? p.isOnField() : !p?.isFainted(true))) { scene.pushPhase(new BattleEndPhase(scene)); - if (scene.currentBattle.mysteryEncounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { + if (encounter.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { scene.pushPhase(new TrainerVictoryPhase(scene)); } if (scene.gameMode.isEndless || !scene.gameMode.isWaveFinal(scene.currentBattle.waveIndex)) { - scene.pushPhase(new EggLapsePhase(scene)); + if (!encounter.doContinueEncounter) { + // Only lapse eggs once for multi-battle encounters + scene.pushPhase(new EggLapsePhase(scene)); + } scene.pushPhase(new MysteryEncounterRewardsPhase(scene, addHealPhase)); } } diff --git a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts index 271cdaf391e..9da36ee6846 100644 --- a/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts +++ b/src/data/mystery-encounters/utils/encounter-transformation-sequence.ts @@ -11,13 +11,13 @@ export enum TransformationScreenPosition { } /** - * Initiates an "evolution-like" animation to transform a pokemon (presumably from the player's party) into a new one, not necessarily an evolution species. + * Initiates an "evolution-like" animation to transform a previousPokemon (presumably from the player's party) into a new one, not necessarily an evolution species. * @param scene - * @param pokemon - * @param transformedPokemon + * @param previousPokemon + * @param transformPokemon * @param screenPosition */ -export function doPokemonTransformationSequence(scene: BattleScene, pokemon: PlayerPokemon, transformedPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) { +export function doPokemonTransformationSequence(scene: BattleScene, previousPokemon: PlayerPokemon, transformPokemon: PlayerPokemon, screenPosition: TransformationScreenPosition) { return new Promise(resolve => { const transformationContainer = scene.fieldUI.getByName("Dream Background") as Phaser.GameObjects.Container; const transformationBaseBg = scene.add.image(0, 0, "default_bg"); @@ -36,7 +36,7 @@ export function doPokemonTransformationSequence(scene: BattleScene, pokemon: Pla const yOffset = screenPosition !== TransformationScreenPosition.CENTER ? -15 : 0; const getPokemonSprite = () => { - const ret = scene.addPokemonSprite(pokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub"); + const ret = scene.addPokemonSprite(previousPokemon, transformationBaseBg.displayWidth / 2 + xOffset, transformationBaseBg.displayHeight / 2 + yOffset, "pkmn__sub"); ret.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); return ret; }; @@ -54,31 +54,31 @@ export function doPokemonTransformationSequence(scene: BattleScene, pokemon: Pla pokemonEvoTintSprite.setTintFill(0xFFFFFF); [ pokemonSprite, pokemonTintSprite, pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => { - sprite.play(pokemon.getSpriteKey(true)); - sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(pokemon.getTeraType()) }); + sprite.play(previousPokemon.getSpriteKey(true)); + sprite.setPipeline(scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) }); sprite.setPipelineData("ignoreTimeTint", true); - sprite.setPipelineData("spriteKey", pokemon.getSpriteKey()); - sprite.setPipelineData("shiny", pokemon.shiny); - sprite.setPipelineData("variant", pokemon.variant); + sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey()); + sprite.setPipelineData("shiny", previousPokemon.shiny); + sprite.setPipelineData("variant", previousPokemon.variant); [ "spriteColors", "fusionSpriteColors" ].map(k => { - if (pokemon.summonData?.speciesForm) { + if (previousPokemon.summonData?.speciesForm) { k += "Base"; } - sprite.pipelineData[k] = pokemon.getSprite().pipelineData[k]; + sprite.pipelineData[k] = previousPokemon.getSprite().pipelineData[k]; }); }); [ pokemonEvoSprite, pokemonEvoTintSprite ].map(sprite => { - sprite.play(transformedPokemon.getSpriteKey(true)); + sprite.play(transformPokemon.getSpriteKey(true)); sprite.setPipelineData("ignoreTimeTint", true); - sprite.setPipelineData("spriteKey", transformedPokemon.getSpriteKey()); - sprite.setPipelineData("shiny", transformedPokemon.shiny); - sprite.setPipelineData("variant", transformedPokemon.variant); + sprite.setPipelineData("spriteKey", transformPokemon.getSpriteKey()); + sprite.setPipelineData("shiny", transformPokemon.shiny); + sprite.setPipelineData("variant", transformPokemon.variant); [ "spriteColors", "fusionSpriteColors" ].map(k => { - if (transformedPokemon.summonData?.speciesForm) { + if (transformPokemon.summonData?.speciesForm) { k += "Base"; } - sprite.pipelineData[k] = transformedPokemon.getSprite().pipelineData[k]; + sprite.pipelineData[k] = transformPokemon.getSprite().pipelineData[k]; }); }); @@ -123,9 +123,11 @@ export function doPokemonTransformationSequence(scene: BattleScene, pokemon: Pla duration: 2000, delay: 150, easing: "Sine.easeIn", - // onComplete: () => { - // transformedPokemon.destroy(); - // } + onComplete: () => { + previousPokemon.destroy(); + transformPokemon.setVisible(false); + transformPokemon.setAlpha(1); + } }); }); } diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index f3e6009e02e..eeffd02bc15 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -1958,4 +1958,19 @@ export const trainerConfigs: TrainerConfigs = { } p.pokeball = PokeballType.MASTER_BALL; })), + [TrainerType.VICTOR]: new TrainerConfig(++t).setName("Victor").setTitle("The Winstrates") + .setMoneyMultiplier(1) // The Winstrate trainers have total money multiplier of 6 + .setPartyTemplates(trainerPartyTemplates.ONE_AVG_ONE_STRONG), + [TrainerType.VICTORIA]: new TrainerConfig(++t).setName("Victoria").setTitle("The Winstrates") + .setMoneyMultiplier(1) + .setPartyTemplates(trainerPartyTemplates.ONE_AVG_ONE_STRONG), + [TrainerType.VIVI]: new TrainerConfig(++t).setName("Vivi").setTitle("The Winstrates") + .setMoneyMultiplier(1) + .setPartyTemplates(trainerPartyTemplates.TWO_AVG_ONE_STRONG), + [TrainerType.VICKY]: new TrainerConfig(++t).setName("Vicky").setTitle("The Winstrates") + .setMoneyMultiplier(1) + .setPartyTemplates(trainerPartyTemplates.ONE_AVG), + [TrainerType.VITO]: new TrainerConfig(++t).setName("Vito").setTitle("The Winstrates") + .setMoneyMultiplier(2) + .setPartyTemplates(new TrainerPartyCompoundTemplate(new TrainerPartyTemplate(3, PartyMemberStrength.AVERAGE), new TrainerPartyTemplate(2, PartyMemberStrength.STRONG))) }; diff --git a/src/enums/mystery-encounter-mode.ts b/src/enums/mystery-encounter-mode.ts index 03094040ce0..3acab7b4797 100644 --- a/src/enums/mystery-encounter-mode.ts +++ b/src/enums/mystery-encounter-mode.ts @@ -2,8 +2,7 @@ export enum MysteryEncounterMode { DEFAULT, TRAINER_BATTLE, WILD_BATTLE, + /** Enables wild boss music during encounter */ BOSS_BATTLE, - NO_BATTLE, - /** For spawning new encounter queries instead of continuing to next wave */ - CONTINUOUS_ENCOUNTER + NO_BATTLE } diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index 0db41e71ac5..f9871a1a3dd 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -22,5 +22,6 @@ export enum MysteryEncounterType { CLOWNING_AROUND, PART_TIMER, DANCING_LESSONS, - WEIRD_DREAM + WEIRD_DREAM, + THE_WINSTRATE_CHALLENGE } diff --git a/src/enums/trainer-type.ts b/src/enums/trainer-type.ts index 0d3a0771604..65c3370b510 100644 --- a/src/enums/trainer-type.ts +++ b/src/enums/trainer-type.ts @@ -75,6 +75,11 @@ export enum TrainerType { MARLEY, MIRA, RILEY, + VICTOR, + VICTORIA, + VIVI, + VICKY, + VITO, BROCK = 200, MISTY, diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 0f13f486eb2..1192e1b5369 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -1,6 +1,6 @@ import { GameObjects } from "phaser"; import BattleScene from "../battle-scene"; -import IMysteryEncounter from "../data/mystery-encounters/mystery-encounter"; +import MysteryEncounter from "../data/mystery-encounters/mystery-encounter"; import { Species } from "#enums/species"; import { isNullOrUndefined } from "#app/utils"; import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; @@ -70,11 +70,11 @@ export class MysteryEncounterSpriteConfig { * Note: intro visuals are not "Trainers" or any other specific game object, though they may contain trainer sprites */ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Container { - public encounter: IMysteryEncounter; + public encounter: MysteryEncounter; public spriteConfigs: MysteryEncounterSpriteConfig[]; public enterFromRight: boolean; - constructor(scene: BattleScene, encounter: IMysteryEncounter) { + constructor(scene: BattleScene, encounter: MysteryEncounter) { super(scene, -72, 76); this.encounter = encounter; this.enterFromRight = encounter.enterIntroVisualsFromRight ?? false; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index feca608038a..37aa9b1afef 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -10,7 +10,7 @@ import * as Utils from "../utils"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { getLevelTotalExp } from "../data/exp"; import { Stat } from "../data/pokemon-stat"; -import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatTotalModifier } from "../modifier/modifier"; +import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, PokemonBaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempBattleStatBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier } from "../modifier/modifier"; import { PokeballType } from "../data/pokeball"; import { Gender } from "../data/gender"; import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims"; @@ -804,6 +804,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.stats[s] = value; } + this.scene.applyModifier(PokemonIncrementingStatModifier, this.isPlayer(), this, this.stats); } getNature(): Nature { diff --git a/src/interfaces/held-modifier-config.ts b/src/interfaces/held-modifier-config.ts new file mode 100644 index 00000000000..304de72f01b --- /dev/null +++ b/src/interfaces/held-modifier-config.ts @@ -0,0 +1,7 @@ +import { PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; + +export default interface HeldModifierConfig { + modifierType: PokemonHeldItemModifierType; + stackCount?: number; + isTransferable?: boolean; +} diff --git a/src/locales/en/dialogue.ts b/src/locales/en/dialogue.ts index 50dee033782..a8be04c8718 100644 --- a/src/locales/en/dialogue.ts +++ b/src/locales/en/dialogue.ts @@ -627,6 +627,51 @@ export const PGMdialogue: DialogueTranslationEntries = { 1: "You put up quite the display.\nBetter luck next time." } }, + "winstrates_victor": { + "encounter": { + 1: "That's the spirit! I like you!", + }, + "victory": { + 1: "A-ha! You're stronger than I thought!" + } + }, + "winstrates_victoria": { + "encounter": { + 1: `My goodness! Aren't you young? + $You must be quite the trainer to beat my husband, though. + $Now I suppose it's my turn to battle!`, + }, + "victory": { + 1: "Uwah! Just how strong are you?!" + } + }, + "winstrates_vivi": { + "encounter": { + 1: `You're stronger than Mom? Wow! + $But I'm strong, too!\nReally! Honestly!`, + }, + "victory": { + 1: "Huh? Did I really lose?\nSnivel... Grandmaaa!" + } + }, + "winstrates_vicky": { + "encounter": { + 1: `How dare you make my precious\ngranddaughter cry! + $I see I need to teach you a lesson.\nPrepare to feel the sting of defeat!`, + }, + "victory": { + 1: "Whoa! So strong!\nMy granddaughter wasn't lying." + } + }, + "winstrates_vito": { + "encounter": { + 1: `I trained together with my whole family,\nevery one of us! + $I'm not losing to anyone!`, + }, + "victory": { + 1: "I was better than everyone in my family.\nI've never lost before..." + } + }, "brock": { "encounter": { 1: "My expertise on Rock-type Pokémon will take you down! Come on!", diff --git a/src/locales/en/modifier-type.ts b/src/locales/en/modifier-type.ts index 1266f6442e9..cc54a0c5c08 100644 --- a/src/locales/en/modifier-type.ts +++ b/src/locales/en/modifier-type.ts @@ -262,6 +262,7 @@ export const modifierType: ModifierTypeTranslationEntries = { "MYSTERY_ENCOUNTER_SHUCKLE_JUICE": { name: "Shuckle Juice" }, "MYSTERY_ENCOUNTER_BLACK_SLUDGE": { name: "Black Sludge", description: "The stench is so powerful that healing items are no longer available to purchase in shops." }, + "MYSTERY_ENCOUNTER_MACHO_BRACE": { name: "Macho Brace", description: "Defeating a Pokémon grants the holder a Macho Brace stack. Each stack slightly boosts stats, with an extra bonus at max stacks." }, "MYSTERY_ENCOUNTER_OLD_GATEAU": { name: "Old Gateau", description: "Increases the holder's {{stats}} stats by {{statValue}}." }, }, SpeciesBoosterItem: { diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index 1358c8949a0..2779900eeff 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -22,6 +22,7 @@ import { clowningAroundDialogue } from "#app/locales/en/mystery-encounters/clown import { partTimerDialogue } from "#app/locales/en/mystery-encounters/part-timer-dialogue"; import { dancingLessonsDialogue } from "#app/locales/en/mystery-encounters/dancing-lessons-dialogue"; import { weirdDreamDialogue } from "#app/locales/en/mystery-encounters/weird-dream-dialogue"; +import { theWinstrateChallengeDialogue } from "#app/locales/en/mystery-encounters/the-winstrate-challenge-dialogue"; /** * Injection patterns that can be used: @@ -71,5 +72,6 @@ export const mysteryEncounter = { clowningAround: clowningAroundDialogue, partTimer: partTimerDialogue, dancingLessons: dancingLessonsDialogue, - weirdDream: weirdDreamDialogue + weirdDream: weirdDreamDialogue, + theWinstrateChallenge: theWinstrateChallengeDialogue } as const; diff --git a/src/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.ts b/src/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.ts new file mode 100644 index 00000000000..bd5d5560315 --- /dev/null +++ b/src/locales/en/mystery-encounters/the-winstrate-challenge-dialogue.ts @@ -0,0 +1,24 @@ +export const theWinstrateChallengeDialogue = { + intro: "It's a family standing outside their house!", + speaker: "The Winstrates", + intro_dialogue: `We're the Winstrates! + $What do you say to taking on our family in a series of Pokémon battles?`, + title: "The Winstrate Challenge", + description: "The Winstrates are a family of 5 trainers, and they want to battle! If you beat all of them back-to-back, they'll give you a grand prize. But can you handle the heat?", + query: "What will you do?", + option: { + 1: { + label: "Accept the Challenge", + tooltip: "(-) Brutal Battle\n(+) Special Item Reward", + selected: "That's the spirit! I like you!", + }, + 2: { + label: "Refuse the Challenge", + tooltip: "(+) Full Heal Party\n(+) Gain a Rarer Candy", + selected: "That's too bad. Say, your team looks worn out, why don't you stay awhile and rest?" + }, + }, + victory: `Congratulations on beating our challenge! + $Our family uses this Macho Brace to strengthen our Pokémon more effectively during their training. + $You may not need it, considering that you beat the whole lot of us, but we hope you'll accept it anyway!`, +}; diff --git a/src/locales/en/trainers.ts b/src/locales/en/trainers.ts index 0581a6698de..2adc2da9d5e 100644 --- a/src/locales/en/trainers.ts +++ b/src/locales/en/trainers.ts @@ -19,6 +19,7 @@ export const titles: SimpleTranslationEntries = { "galactic_boss": "Team Galactic Boss", "plasma_boss": "Team Plasma Boss", "flare_boss": "Team Flare Boss", + "the_winstrates": "The Winstrates'" // Maybe if we add the evil teams we can add "Team Rocket" and "Team Aqua" etc. here as well as "Team Rocket Boss" and "Team Aqua Admin" etc. } as const; @@ -275,6 +276,11 @@ export const trainerNames: SimpleTranslationEntries = { "marley": "Marley", "mira": "Mira", "riley": "Riley", + "victor": "Victor", + "victoria": "Victoria", + "vivi": "Vivi", + "vicky": "Vicky", + "vito": "Vito", // Double Names "blue_red_double": "Blue & Red", diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index f783e2baa99..791db6facc7 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1506,6 +1506,7 @@ export const modifierTypes = { return new PokemonBaseStatFlatModifierType(Utils.randSeedInt(20), [Stat.HP, Stat.ATK, Stat.DEF]); }), MYSTERY_ENCOUNTER_BLACK_SLUDGE: () => new ModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_BLACK_SLUDGE", "black_sludge", (type, _args) => new Modifiers.RemoveHealShopModifier(type)), + MYSTERY_ENCOUNTER_MACHO_BRACE: () => new PokemonHeldItemModifierType("modifierType:ModifierType.MYSTERY_ENCOUNTER_MACHO_BRACE", "macho_brace", (type, args) => new Modifiers.PokemonIncrementingStatModifier(type, (args[0] as Pokemon).id)), }; interface ModifierPool { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 3778310c8f5..e0a8ec0d493 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -793,6 +793,54 @@ export class PokemonBaseStatFlatModifier extends PokemonHeldItemModifier { } } +export class PokemonIncrementingStatModifier extends PokemonHeldItemModifier { + readonly isTransferrable: boolean = false; + + constructor (type: ModifierType, pokemonId: integer, stackCount?: integer) { + super(type, pokemonId, stackCount); + } + + matchType(modifier: Modifier): boolean { + return modifier instanceof PokemonIncrementingStatModifier; + } + + clone(): PersistentModifier { + return new PokemonIncrementingStatModifier(this.type, this.pokemonId); + } + + getArgs(): any[] { + return super.getArgs(); + } + + shouldApply(args: any[]): boolean { + return super.shouldApply(args) && args.length === 2 && args[1] instanceof Array; + } + + apply(args: any[]): boolean { + // Modifies the passed in stats[] array by +1 per stack for HP, +2 per stack for other stats + // If the Macho Brace is at max stacks (50), adds additional 5% to total HP and 10% to other stats + args[1].forEach((v, i) => { + const isHp = i === 0; + let mult = 1; + if (this.stackCount === this.getMaxHeldItemCount(null)) { + mult = isHp ? 1.05 : 1.1; + } + const newVal = Math.floor((v + this.stackCount * (isHp ? 1 : 2)) * mult); + args[1][i] = Math.min(Math.max(newVal, 1), 999999); + }); + + return true; + } + + getScoreMultiplier(): number { + return 1.2; + } + + getMaxHeldItemCount(pokemon: Pokemon): integer { + return 50; + } +} + /** * Modifier used for held items that apply {@linkcode Stat} boost(s) * using a multiplier. diff --git a/src/phases.ts b/src/phases.ts index 24cf33a98f6..6d9054da327 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -6,7 +6,7 @@ import { allMoves, applyFilteredMoveAttrs, applyMoveAttrs, AttackMove, BypassRed import { Mode } from "./ui/ui"; import { Command } from "./ui/command-ui-handler"; import { Stat } from "./data/pokemon-stat"; -import { BerryModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, IvScannerModifier, LapsingPersistentModifier, LapsingPokemonHeldItemModifier, MapModifier, Modifier, MoneyInterestModifier, MoneyMultiplierModifier, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, PokemonMultiHitModifier, PokemonResetNegativeStatStageModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier } from "./modifier/modifier"; +import { BerryModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, IvScannerModifier, LapsingPersistentModifier, LapsingPokemonHeldItemModifier, MapModifier, Modifier, MoneyInterestModifier, MoneyMultiplierModifier, MultipleParticipantExpBonusModifier, overrideHeldItems, overrideModifiers, PersistentModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonMultiHitModifier, PokemonResetNegativeStatStageModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier } from "./modifier/modifier"; import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler"; import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; import { CommonAnim, CommonBattleAnim, initEncounterAnims, initMoveAnim, loadEncounterAnimAssets, loadMoveAnimAssets, MoveAnim } from "./data/battle-anims"; @@ -1302,8 +1302,7 @@ export class NextEncounterPhase extends EncounterPhase { this.scene.lastEnemyTrainer.destroy(); } if (lastEncounterVisuals) { - this.scene.field.remove(lastEncounterVisuals); - lastEncounterVisuals.destroy(); + this.scene.field.remove(lastEncounterVisuals, true); this.scene.lastMysteryEncounter.introVisuals = null; } @@ -1583,7 +1582,7 @@ export class SummonPhase extends PartyMemberPokemonPhase { onComplete: () => this.scene.trainer.setVisible(false) }); this.scene.time.delayedCall(750, () => this.summon()); - } else if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene?.currentBattle?.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { + } else if (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE) { const trainerName = this.scene.currentBattle.trainer.getName(!(this.fieldIndex % 2) ? TrainerSlot.TRAINER : TrainerSlot.TRAINER_PARTNER); const pokemonName = getPokemonNameWithAffix(this.getPokemon()); const message = i18next.t("battle:trainerSendOut", { trainerName, pokemonName }); @@ -2287,7 +2286,7 @@ export class CommandPhase extends FieldPhase { this.scene.ui.showText(null, 0); this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); }, null, true); - } else if (!isSwitch && this.scene.currentBattle.battleType === BattleType.TRAINER) { + } else if (!isSwitch && (this.scene.currentBattle.battleType === BattleType.TRAINER || this.scene.currentBattle.mysteryEncounter?.encounterMode === MysteryEncounterMode.TRAINER_BATTLE)) { this.scene.ui.setMode(Mode.COMMAND, this.fieldIndex); this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.showText(i18next.t("battle:noEscapeTrainer"), null, () => { @@ -4138,6 +4137,12 @@ export class VictoryPhase extends PokemonPhase { const participated = participantIds.has(pId); if (participated) { partyMember.addFriendship(2); + const machoBraceModifier = partyMember.getHeldItems().find(m => m instanceof PokemonIncrementingStatModifier); + if (!isNullOrUndefined(machoBraceModifier) && machoBraceModifier.stackCount < machoBraceModifier.getMaxStackCount(this.scene)) { + machoBraceModifier.stackCount++; + this.scene.updateModifiers(true, true); + partyMember.updateInfo(); + } } if (!expPartyMembers.includes(partyMember)) { continue; diff --git a/src/phases/mystery-encounter-phases.ts b/src/phases/mystery-encounter-phases.ts index fe6de6973da..160a081fda1 100644 --- a/src/phases/mystery-encounter-phases.ts +++ b/src/phases/mystery-encounter-phases.ts @@ -381,6 +381,10 @@ export class MysteryEncounterBattlePhase extends Phase { /** * Will handle (in order): + * - doContinueEncounter() callback for continuous encounters with back-to-back battles (this should push/shift its own phases as needed) + * + * OR + * * - Any encounter reward logic that is set within MysteryEncounter doEncounterExp * - Any encounter reward logic that is set within MysteryEncounter doEncounterRewards * - Otherwise, can add a no-reward-item shop with only Potions, etc. if addHealPhase is true @@ -396,23 +400,30 @@ export class MysteryEncounterRewardsPhase extends Phase { start() { super.start(); + const encounter = this.scene.currentBattle.mysteryEncounter; - this.scene.executeWithSeedOffset(() => { - if (this.scene.currentBattle.mysteryEncounter.doEncounterExp) { - this.scene.currentBattle.mysteryEncounter.doEncounterExp(this.scene); - } + if (encounter.doContinueEncounter) { + encounter.doContinueEncounter(this.scene).then(() => { + this.end(); + }); + } else { + this.scene.executeWithSeedOffset(() => { + if (this.scene.currentBattle.mysteryEncounter.doEncounterExp) { + this.scene.currentBattle.mysteryEncounter.doEncounterExp(this.scene); + } - if (this.scene.currentBattle.mysteryEncounter.doEncounterRewards) { - this.scene.currentBattle.mysteryEncounter.doEncounterRewards(this.scene); - } else if (this.addHealPhase) { - this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase); - this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 })); - } - // Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave) - }, this.scene.currentBattle.waveIndex * 1000); + if (this.scene.currentBattle.mysteryEncounter.doEncounterRewards) { + this.scene.currentBattle.mysteryEncounter.doEncounterRewards(this.scene); + } else if (this.addHealPhase) { + this.scene.tryRemovePhase(p => p instanceof SelectModifierPhase); + this.scene.unshiftPhase(new SelectModifierPhase(this.scene, 0, null, { fillRemaining: false, rerollMultiplier: 0 })); + } + // Do not use ME's seedOffset for rewards, these should always be consistent with waveIndex (once per wave) + }, this.scene.currentBattle.waveIndex * 1000); - this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene)); - this.end(); + this.scene.pushPhase(new PostMysteryEncounterPhase(this.scene)); + this.end(); + } } } diff --git a/src/system/game-data.ts b/src/system/game-data.ts index af465278e39..62c8c6b8aef 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -43,7 +43,7 @@ import { Species } from "#enums/species"; import { applyChallenges, ChallengeType } from "#app/data/challenge.js"; import { Abilities } from "#app/enums/abilities.js"; import { MysteryEncounterData } from "../data/mystery-encounters/mystery-encounter-data"; -import IMysteryEncounter from "../data/mystery-encounters/mystery-encounter"; +import MysteryEncounter from "../data/mystery-encounters/mystery-encounter"; export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, @@ -126,7 +126,7 @@ export interface SessionSaveData { gameVersion: string; timestamp: integer; challenges: ChallengeData[]; - mysteryEncounter: IMysteryEncounter; + mysteryEncounter: MysteryEncounter; mysteryEncounterData: MysteryEncounterData; } @@ -1167,7 +1167,7 @@ export class GameData { } if (k === "mysteryEncounter") { - return new IMysteryEncounter(v); + return new MysteryEncounter(v); } if (k === "mysteryEncounterData") { 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 3acd8d1ef99..80f022a3e3e 100644 --- a/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/clowning-around-encounter.test.ts @@ -7,7 +7,7 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { getPokemonSpecies } from "#app/data/pokemon-species"; import * as BattleAnims from "#app/data/battle-anims"; import * as EncounterPhaseUtils from "#app/data/mystery-encounters/utils/encounter-phase-utils"; -import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { runMysteryEncounterToEnd, skipBattleRunMysteryEncounterRewardsPhase } from "#test/mystery-encounter/encounterTestUtils"; import { CommandPhase, MovePhase, NewBattlePhase, SelectModifierPhase } from "#app/phases"; import { Moves } from "#enums/moves"; @@ -123,7 +123,6 @@ describe("Clowning Around - Mystery Encounter", () => { }); expect(config.pokemonConfigs[1]).toEqual({ species: getPokemonSpecies(Species.BLACEPHALON), - ability: expect.any(Number), mysteryEncounterData: expect.anything(), isBoss: true, moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN] @@ -145,8 +144,8 @@ describe("Clowning Around - Mystery Encounter", () => { Abilities.MAGICIAN, Abilities.SHEER_FORCE, Abilities.PRANKSTER - ]).toContain(config.pokemonConfigs[1].ability); - expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].ability); + ]).toContain(config.pokemonConfigs[1].mysteryEncounterData.ability); + expect(ClowningAroundEncounter.misc.ability).toBe(config.pokemonConfigs[1].mysteryEncounterData.ability); await vi.waitFor(() => expect(moveInitSpy).toHaveBeenCalled()); await vi.waitFor(() => expect(moveLoadSpy).toHaveBeenCalled()); expect(onInitResult).toBe(true); @@ -258,26 +257,26 @@ describe("Clowning Around - Mystery Encounter", () => { // 2 Sitrus Berries on lead scene.modifiers = []; - let itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type as PokemonHeldItemModifierType; + let itemType = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getParty()[0], 2, itemType); // 2 Ganlon Berries on lead - itemType = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.GANLON]).type as PokemonHeldItemModifierType; + itemType = generateModifierType(scene, modifierTypes.BERRY, [BerryType.GANLON]) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getParty()[0], 2, itemType); // 5 Golden Punch on lead (ultra) - itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_PUNCH).type as PokemonHeldItemModifierType; + itemType = generateModifierType(scene, modifierTypes.GOLDEN_PUNCH) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getParty()[0], 5, itemType); // 5 Lucky Egg on lead (ultra) - itemType = generateModifierTypeOption(scene, modifierTypes.LUCKY_EGG).type as PokemonHeldItemModifierType; + itemType = generateModifierType(scene, modifierTypes.LUCKY_EGG) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getParty()[0], 5, itemType); // 5 Soul Dew on lead (rogue) - itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType; + itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getParty()[0], 5, itemType); // 2 Golden Egg on lead (rogue) - itemType = generateModifierTypeOption(scene, modifierTypes.GOLDEN_EGG).type as PokemonHeldItemModifierType; + itemType = generateModifierType(scene, modifierTypes.GOLDEN_EGG) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getParty()[0], 2, itemType); // 5 Soul Dew on second party pokemon (these should not change) - itemType = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type as PokemonHeldItemModifierType; + itemType = generateModifierType(scene, modifierTypes.SOUL_DEW) as PokemonHeldItemModifierType; await addItemToPokemon(scene, scene.getParty()[1], 5, itemType); await runMysteryEncounterToEnd(game, 2); diff --git a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts index 8934dbb5f5c..43f9c5612db 100644 --- a/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/delibirdy-encounter.test.ts @@ -13,7 +13,7 @@ import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encount import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { BerryModifier, HealingBoosterModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, LevelIncrementBoosterModifier, PokemonInstantReviveModifier, PokemonNatureWeightModifier, PreserveBerryModifier } from "#app/modifier/modifier"; import { MysteryEncounterPhase } from "#app/phases/mystery-encounter-phases"; -import { generateModifierTypeOption } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; +import { generateModifierType } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import { modifierTypes } from "#app/modifier/modifier-type"; import { BerryType } from "#enums/berry-type"; @@ -137,7 +137,7 @@ describe("Delibird-y - Mystery Encounter", () => { // 5 Healing Charms scene.modifiers = []; - const abilityCharm = generateModifierTypeOption(scene, modifierTypes.ABILITY_CHARM).type.newModifier() as HiddenAbilityRateBoosterModifier; + const abilityCharm = generateModifierType(scene, modifierTypes.ABILITY_CHARM).newModifier() as HiddenAbilityRateBoosterModifier; abilityCharm.stackCount = 4; await scene.addModifier(abilityCharm, true, false, false, true); await scene.updateModifiers(true); @@ -206,7 +206,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 2 Sitrus berries on party lead scene.modifiers = []; - const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type; + const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]); const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier; sitrusMod.stackCount = 2; await scene.addModifier(sitrusMod, true, false, false, true); @@ -227,7 +227,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Reviver Seed on party lead scene.modifiers = []; - const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type; + const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED); const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -248,10 +248,10 @@ describe("Delibird-y - Mystery Encounter", () => { // 99 Candy Jars scene.modifiers = []; - const candyJar = generateModifierTypeOption(scene, modifierTypes.CANDY_JAR).type.newModifier() as LevelIncrementBoosterModifier; + const candyJar = generateModifierType(scene, modifierTypes.CANDY_JAR).newModifier() as LevelIncrementBoosterModifier; candyJar.stackCount = 99; await scene.addModifier(candyJar, true, false, false, true); - const sitrus = generateModifierTypeOption(scene, modifierTypes.BERRY, [BerryType.SITRUS]).type; + const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]); // Sitrus berries on party const sitrusMod = sitrus.newModifier(scene.getParty()[0]) as BerryModifier; @@ -277,12 +277,12 @@ describe("Delibird-y - Mystery Encounter", () => { // 5 Healing Charms scene.modifiers = []; - const healingCharm = generateModifierTypeOption(scene, modifierTypes.HEALING_CHARM).type.newModifier() as HealingBoosterModifier; + const healingCharm = generateModifierType(scene, modifierTypes.HEALING_CHARM).newModifier() as HealingBoosterModifier; healingCharm.stackCount = 5; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Reviver Seed on party lead - const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type; + const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED); const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -306,7 +306,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type; + const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW); const modifier = soulDew.newModifier(scene.getParty()[0]); await scene.addModifier(modifier, true, false, false, true); await scene.updateModifiers(true); @@ -334,7 +334,7 @@ describe("Delibird-y - Mystery Encounter", () => { await game.runToMysteryEncounter(MysteryEncounterType.DELIBIRDY, defaultParty); // Set 1 Reviver Seed on party lead - const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type; + const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED); const modifier = revSeed.newModifier(scene.getParty()[0]) as PokemonInstantReviveModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -368,7 +368,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 2 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type; + const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW); const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 2; await scene.addModifier(modifier, true, false, false, true); @@ -389,7 +389,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type; + const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW); const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -410,12 +410,12 @@ describe("Delibird-y - Mystery Encounter", () => { // 5 Healing Charms scene.modifiers = []; - const healingCharm = generateModifierTypeOption(scene, modifierTypes.BERRY_POUCH).type.newModifier() as PreserveBerryModifier; + const healingCharm = generateModifierType(scene, modifierTypes.BERRY_POUCH).newModifier() as PreserveBerryModifier; healingCharm.stackCount = 3; await scene.addModifier(healingCharm, true, false, false, true); // Set 1 Soul Dew on party lead - const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type; + const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW); const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); @@ -439,7 +439,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Reviver Seed on party lead scene.modifiers = []; - const revSeed = generateModifierTypeOption(scene, modifierTypes.REVIVER_SEED).type; + const revSeed = generateModifierType(scene, modifierTypes.REVIVER_SEED); const modifier = revSeed.newModifier(scene.getParty()[0]); await scene.addModifier(modifier, true, false, false, true); await scene.updateModifiers(true); @@ -468,7 +468,7 @@ describe("Delibird-y - Mystery Encounter", () => { // Set 1 Soul Dew on party lead scene.modifiers = []; - const soulDew = generateModifierTypeOption(scene, modifierTypes.SOUL_DEW).type; + const soulDew = generateModifierType(scene, modifierTypes.SOUL_DEW); const modifier = soulDew.newModifier(scene.getParty()[0]) as PokemonNatureWeightModifier; modifier.stackCount = 1; await scene.addModifier(modifier, true, false, false, true); diff --git a/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts b/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts index 7fe6b1a3f95..31fb03cb7d3 100644 --- a/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts +++ b/src/test/mystery-encounter/encounters/mysterious-challengers-encounter.test.ts @@ -18,7 +18,7 @@ import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyTemplate } fro import { PartyMemberStrength } from "#enums/party-member-strength"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; -import IMysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; const namespace = "mysteryEncounter:mysteriousChallengers"; const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; @@ -96,7 +96,7 @@ describe("Mysterious Challengers - Mystery Encounter", () => { it("should initialize fully", async () => { initSceneWithoutEncounterPhase(scene, defaultParty); - scene.currentBattle.mysteryEncounter = new IMysteryEncounter(MysteriousChallengersEncounter); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(MysteriousChallengersEncounter); const encounter = scene.currentBattle.mysteryEncounter; scene.currentBattle.waveIndex = defaultWave; 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 21d0678c858..5c526ddeb57 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 @@ -123,7 +123,7 @@ describe("The Strong Stuff - Mystery Encounter", () => { mysteryEncounterData: new MysteryEncounterPokemonData(1.5), nature: Nature.BOLD, moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER], - modifierTypes: expect.any(Array), + modifierConfigs: expect.any(Array), tags: [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON], mysteryEncounterBattleEffects: expect.any(Function) } diff --git a/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts new file mode 100644 index 00000000000..667c2d2ec3d --- /dev/null +++ b/src/test/mystery-encounter/encounters/the-winstrate-challenge-encounter.test.ts @@ -0,0 +1,385 @@ +import * as MysteryEncounters from "#app/data/mystery-encounters/mystery-encounters"; +import { HUMAN_TRANSITABLE_BIOMES } from "#app/data/mystery-encounters/mystery-encounters"; +import { Biome } from "#app/enums/biome"; +import { MysteryEncounterType } from "#app/enums/mystery-encounter-type"; +import { Species } from "#app/enums/species"; +import GameManager from "#app/test/utils/gameManager"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { runMysteryEncounterToEnd } from "#test/mystery-encounter/encounterTestUtils"; +import { CommandPhase, PartyHealPhase, SelectModifierPhase, VictoryPhase } from "#app/phases"; +import BattleScene from "#app/battle-scene"; +import { Mode } from "#app/ui/ui"; +import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; +import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; +import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; +import ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import { TrainerType } from "#enums/trainer-type"; +import { Nature } from "#enums/nature"; +import { Moves } from "#enums/moves"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { TheWinstrateChallengeEncounter } from "#app/data/mystery-encounters/encounters/the-winstrate-challenge-encounter"; +import { Status, StatusEffect } from "#app/data/status-effect"; +import { MysteryEncounterRewardsPhase } from "#app/phases/mystery-encounter-phases"; + +const namespace = "mysteryEncounter:theWinstrateChallenge"; +const defaultParty = [Species.LAPRAS, Species.GENGAR, Species.ABRA]; +const defaultBiome = Biome.CAVE; +const defaultWave = 45; + +describe("The Winstrate Challenge - Mystery Encounter", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + let scene: BattleScene; + + beforeAll(() => { + phaserGame = new Phaser.Game({ type: Phaser.HEADLESS }); + }); + + beforeEach(async () => { + game = new GameManager(phaserGame); + scene = game.scene; + game.override.mysteryEncounterChance(100); + game.override.startingWave(defaultWave); + game.override.startingBiome(defaultBiome); + game.override.disableTrainerWaves(true); + + const biomeMap = new Map([ + [Biome.VOLCANO, [MysteryEncounterType.FIGHT_OR_FLIGHT]], + ]); + HUMAN_TRANSITABLE_BIOMES.forEach(biome => { + biomeMap.set(biome, [MysteryEncounterType.THE_WINSTRATE_CHALLENGE]); + }); + vi.spyOn(MysteryEncounters, "mysteryEncountersByBiome", "get").mockReturnValue(biomeMap); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.clearAllMocks(); + vi.resetAllMocks(); + }); + + it("should have the correct properties", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); + + expect(TheWinstrateChallengeEncounter.encounterType).toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE); + expect(TheWinstrateChallengeEncounter.encounterTier).toBe(MysteryEncounterTier.ROGUE); + expect(TheWinstrateChallengeEncounter.dialogue).toBeDefined(); + expect(TheWinstrateChallengeEncounter.dialogue.intro).toStrictEqual([ + { text: `${namespace}.intro` }, + { + speaker: `${namespace}.speaker`, + text: `${namespace}.intro_dialogue`, + } + ]); + expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.title).toBe(`${namespace}.title`); + expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.description).toBe(`${namespace}.description`); + expect(TheWinstrateChallengeEncounter.dialogue.encounterOptionsDialogue.query).toBe(`${namespace}.query`); + expect(TheWinstrateChallengeEncounter.options.length).toBe(2); + }); + + it("should not spawn outside of HUMAN_TRANSITABLE_BIOMES", async () => { + game.override.mysteryEncounterTier(MysteryEncounterTier.GREAT); + game.override.startingBiome(Biome.VOLCANO); + await game.runToMysteryEncounter(); + + expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE); + }); + + it("should not run below wave 10", async () => { + game.override.startingWave(9); + + await game.runToMysteryEncounter(); + + expect(scene.currentBattle?.mysteryEncounter?.encounterType).not.toBe(MysteryEncounterType.THE_WINSTRATE_CHALLENGE); + }); + + it("should not run above wave 179", async () => { + game.override.startingWave(181); + + await game.runToMysteryEncounter(); + + expect(scene.currentBattle.mysteryEncounter).toBeUndefined(); + }); + + it("should initialize fully", async () => { + initSceneWithoutEncounterPhase(scene, defaultParty); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(TheWinstrateChallengeEncounter); + const encounter = scene.currentBattle.mysteryEncounter; + scene.currentBattle.waveIndex = defaultWave; + + const { onInit } = encounter; + + expect(encounter.onInit).toBeDefined(); + + encounter.populateDialogueTokensFromRequirements(scene); + const onInitResult = onInit(scene); + + expect(encounter.enemyPartyConfigs).toBeDefined(); + expect(encounter.enemyPartyConfigs.length).toBe(5); + expect(encounter.enemyPartyConfigs).toEqual([ + { + trainerType: TrainerType.VITO, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.HISUI_ELECTRODE), + isBoss: false, + abilityIndex: 0, // Soundproof + nature: Nature.MODEST, + moveSet: [Moves.THUNDERBOLT, Moves.GIGA_DRAIN, Moves.FOUL_PLAY, Moves.THUNDER_WAVE], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.SWALOT), + isBoss: false, + abilityIndex: 2, // Gluttony + nature: Nature.QUIET, + moveSet: [Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.ICE_BEAM, Moves.EARTHQUAKE], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.DODRIO), + isBoss: false, + abilityIndex: 2, // Tangled Feet + nature: Nature.JOLLY, + moveSet: [Moves.DRILL_PECK, Moves.QUICK_ATTACK, Moves.THRASH, Moves.KNOCK_OFF], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.ALAKAZAM), + isBoss: false, + formIndex: 1, + nature: Nature.BOLD, + moveSet: [Moves.PSYCHIC, Moves.SHADOW_BALL, Moves.FOCUS_BLAST, Moves.THUNDERBOLT], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.DARMANITAN), + isBoss: false, + abilityIndex: 0, // Sheer Force + nature: Nature.IMPISH, + moveSet: [Moves.EARTHQUAKE, Moves.U_TURN, Moves.FLARE_BLITZ, Moves.ROCK_SLIDE], + modifierConfigs: expect.any(Array) + } + ] + }, + { + trainerType: TrainerType.VICKY, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.MEDICHAM), + isBoss: false, + formIndex: 1, + nature: Nature.IMPISH, + moveSet: [Moves.AXE_KICK, Moves.ICE_PUNCH, Moves.ZEN_HEADBUTT, Moves.BULLET_PUNCH], + modifierConfigs: expect.any(Array) + } + ] + }, + { + trainerType: TrainerType.VIVI, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.SEAKING), + isBoss: false, + abilityIndex: 3, // Lightning Rod + nature: Nature.ADAMANT, + moveSet: [Moves.WATERFALL, Moves.MEGAHORN, Moves.KNOCK_OFF, Moves.REST], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.BRELOOM), + isBoss: false, + abilityIndex: 1, // Poison Heal + nature: Nature.JOLLY, + moveSet: [Moves.SPORE, Moves.SWORDS_DANCE, Moves.SEED_BOMB, Moves.DRAIN_PUNCH], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.CAMERUPT), + isBoss: false, + formIndex: 1, + nature: Nature.CALM, + moveSet: [Moves.EARTH_POWER, Moves.FIRE_BLAST, Moves.YAWN, Moves.PROTECT], + modifierConfigs: expect.any(Array) + } + ] + }, + { + trainerType: TrainerType.VICTORIA, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.ROSERADE), + isBoss: false, + abilityIndex: 0, // Natural Cure + nature: Nature.CALM, + moveSet: [Moves.SYNTHESIS, Moves.SLUDGE_BOMB, Moves.GIGA_DRAIN, Moves.SLEEP_POWDER], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.GARDEVOIR), + isBoss: false, + formIndex: 1, + nature: Nature.TIMID, + moveSet: [Moves.PSYSHOCK, Moves.MOONBLAST, Moves.SHADOW_BALL, Moves.WILL_O_WISP], + modifierConfigs: expect.any(Array) + } + ] + }, + { + trainerType: TrainerType.VICTOR, + pokemonConfigs: [ + { + species: getPokemonSpecies(Species.SWELLOW), + isBoss: false, + abilityIndex: 0, // Guts + nature: Nature.ADAMANT, + moveSet: [Moves.FACADE, Moves.BRAVE_BIRD, Moves.PROTECT, Moves.QUICK_ATTACK], + modifierConfigs: expect.any(Array) + }, + { + species: getPokemonSpecies(Species.OBSTAGOON), + isBoss: false, + abilityIndex: 1, // Guts + nature: Nature.ADAMANT, + moveSet: [Moves.FACADE, Moves.OBSTRUCT, Moves.NIGHT_SLASH, Moves.FIRE_PUNCH], + modifierConfigs: expect.any(Array) + } + ] + } + ]); + expect(encounter.spriteConfigs).toBeDefined(); + expect(encounter.spriteConfigs.length).toBe(5); + expect(onInitResult).toBe(true); + }); + + describe("Option 1 - Normal Battle", () => { + it("should have the correct properties", () => { + const option = TheWinstrateChallengeEncounter.options[0]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}.option.1.label`, + buttonTooltip: `${namespace}.option.1.tooltip`, + selected: [ + { + speaker: "trainerNames:victor", + text: `${namespace}.option.1.selected`, + }, + ], + }); + }); + + it("should battle all 5 trainers for a Macho Brace reward", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); + await runMysteryEncounterToEnd(game, 1, null, true); + + expect(scene.getCurrentPhase().constructor.name).toBe(CommandPhase.name); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICTOR); + expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(4); + expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + + await skipBattleToNextBattle(game); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICTORIA); + expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(3); + expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + + await skipBattleToNextBattle(game); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VIVI); + expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(2); + expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + + await skipBattleToNextBattle(game); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VICKY); + expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(1); + expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + + await skipBattleToNextBattle(game); + expect(scene.currentBattle.trainer).toBeDefined(); + expect(scene.currentBattle.trainer.config.trainerType).toBe(TrainerType.VITO); + expect(scene.currentBattle.mysteryEncounter.enemyPartyConfigs.length).toBe(0); + expect(scene.currentBattle.mysteryEncounter.encounterMode).toBe(MysteryEncounterMode.TRAINER_BATTLE); + + // Should have Macho Brace in the rewards + await skipBattleToNextBattle(game, true); + 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(1); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("MYSTERY_ENCOUNTER_MACHO_BRACE"); + }, 15000); + }); + + describe("Option 2 - Refuse the Challenge", () => { + it("should have the correct properties", () => { + const option = TheWinstrateChallengeEncounter.options[1]; + expect(option.optionMode).toBe(MysteryEncounterOptionMode.DEFAULT); + expect(option.dialogue).toBeDefined(); + expect(option.dialogue).toStrictEqual({ + buttonLabel: `${namespace}.option.2.label`, + buttonTooltip: `${namespace}.option.2.tooltip`, + selected: [ + { + speaker: `${namespace}.speaker`, + text: `${namespace}.option.2.selected`, + }, + ], + }); + }); + + it("Should fully heal the party", async () => { + const phaseSpy = vi.spyOn(scene, "unshiftPhase"); + + await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); + await runMysteryEncounterToEnd(game, 2); + + const partyHealPhases = phaseSpy.mock.calls.filter(p => p[0] instanceof PartyHealPhase).map(p => p[0]); + expect(partyHealPhases.length).toBe(1); + }); + + it("should have a Rarer Candy in the rewards", async () => { + await game.runToMysteryEncounter(MysteryEncounterType.THE_WINSTRATE_CHALLENGE, defaultParty); + await runMysteryEncounterToEnd(game, 2); + 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(1); + expect(modifierSelectHandler.options[0].modifierTypeOption.type.id).toBe("RARER_CANDY"); + }); + }); +}); + +/** + * For any MysteryEncounter that has a battle, can call this to skip battle and proceed to MysteryEncounterRewardsPhase + * @param game + * @param isFinalBattle + */ +async function skipBattleToNextBattle(game: GameManager, isFinalBattle: boolean = false) { + game.scene.clearPhaseQueue(); + game.scene.clearPhaseQueueSplice(); + const commandUiHandler = game.scene.ui.handlers[Mode.COMMAND]; + commandUiHandler.clear(); + game.scene.getEnemyParty().forEach(p => { + p.hp = 0; + p.status = new Status(StatusEffect.FAINT); + game.scene.field.remove(p); + }); + game.phaseInterceptor["onHold"] = []; + game.scene.pushPhase(new VictoryPhase(game.scene, 0)); + game.phaseInterceptor.superEndPhase(); + if (isFinalBattle) { + await game.phaseInterceptor.to(MysteryEncounterRewardsPhase); + } else { + await game.phaseInterceptor.to(CommandPhase); + } +} diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index a336af41b6b..a0e055e3bcf 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -5,7 +5,7 @@ import { initSceneWithoutEncounterPhase } from "#test/utils/gameManagerUtils"; import { Species } from "#enums/species"; import BattleScene from "#app/battle-scene"; import { StatusEffect } from "#app/data/status-effect"; -import IMysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; +import MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import { MessagePhase } from "#app/phases"; import { getPokemonSpecies, speciesStarters } from "#app/data/pokemon-species"; import { Type } from "#app/data/type"; @@ -250,7 +250,7 @@ describe("Mystery Encounter Utils", () => { describe("getTextWithEncounterDialogueTokens", () => { it("injects dialogue tokens and color styling", () => { - scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); const result = getEncounterText(scene, "mysteryEncounter:unit_test_dialogue"); @@ -258,7 +258,7 @@ describe("Mystery Encounter Utils", () => { }); it("can perform nested dialogue token injection", () => { - scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new"); @@ -269,7 +269,7 @@ describe("Mystery Encounter Utils", () => { describe("queueEncounterMessage", () => { it("queues a message with encounter dialogue tokens", async () => { - scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); const spy = vi.spyOn(game.scene, "queueMessage"); const phaseSpy = vi.spyOn(game.scene, "unshiftPhase"); @@ -282,7 +282,7 @@ describe("Mystery Encounter Utils", () => { describe("showEncounterText", () => { it("showText with dialogue tokens", async () => { - scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); const spy = vi.spyOn(game.scene.ui, "showText"); @@ -293,7 +293,7 @@ describe("Mystery Encounter Utils", () => { describe("showEncounterDialogue", () => { it("showText with dialogue tokens", async () => { - scene.currentBattle.mysteryEncounter = new IMysteryEncounter(null); + scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); const spy = vi.spyOn(game.scene.ui, "showDialogue"); diff --git a/src/test/utils/gameWrapper.ts b/src/test/utils/gameWrapper.ts index b1b00c8e15d..2eb924a6f19 100644 --- a/src/test/utils/gameWrapper.ts +++ b/src/test/utils/gameWrapper.ts @@ -127,6 +127,7 @@ export default class GameWrapper { manager: { game: this.game, }, + destroy: () => null, setVolume: () => null, stopByKey: () => null, on: (evt, callback) => callback(),