diff --git a/public/battle-anims/encounter-smokescreen.json b/public/battle-anims/encounter-smokescreen.json new file mode 100644 index 00000000000..00e552dc503 --- /dev/null +++ b/public/battle-anims/encounter-smokescreen.json @@ -0,0 +1,1694 @@ +{ + "graphic": "PRAS- Smokescreen", + "frames": [ + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": 12.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 100, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": 8.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 50, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": 0.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -4, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 100, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": -3.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 2, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -4, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -11, + "y": 21.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 50, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": -7.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 2, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -8, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 7, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -11, + "y": 17.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 100, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": -11.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 3, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -12, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 6, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -11, + "y": 13.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": 21, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 50, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": -15.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -16, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 6, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -11, + "y": 5.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": 17, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 100, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": -19.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 100, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -20, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 5, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -11, + "y": 0.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 2, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": 13, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -12.5, + "y": 8.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 50, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 15.5, + "y": -23.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 50, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -24, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 5, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -11, + "y": -2.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 2, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": 9, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 7, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -12.5, + "y": 4.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 100, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": -11, + "y": -6.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 3, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -28, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": 5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 6, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -12.5, + "y": 0.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 8, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 4.5, + "y": 23, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 50, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": -11, + "y": -10.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -32, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 100, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": 1, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 6, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -12.5, + "y": -3.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 7, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 4.5, + "y": 19, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 100, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": -11, + "y": -14.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 100, + "priority": -1, + "focus": 2 + }, + { + "x": 0, + "y": -36, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 50, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": -3, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 5, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -12.5, + "y": -7.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 6, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 4.5, + "y": 15, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 0, + "opacity": 150, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": -11, + "y": -18.5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 50, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": -7, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": -12.5, + "y": -11.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 6, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 4.5, + "y": 7, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 1, + "opacity": 150, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": -12.5, + "y": -15.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 5, + "opacity": 150, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": -11, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 100, + "priority": -1, + "focus": 2 + }, + { + "x": 4.5, + "y": 3, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 2, + "opacity": 150, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": -12.5, + "y": -19.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 5, + "opacity": 100, + "priority": -1, + "focus": 2 + }, + { + "x": 11, + "y": -15, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 50, + "priority": -1, + "focus": 2 + }, + { + "x": 4.5, + "y": -1, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 2, + "opacity": 150, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": -12.5, + "y": -23.5, + "zoomX": 100, + "zoomY": 100, + "mirror": true, + "visible": true, + "target": 2, + "graphicFrame": 5, + "opacity": 50, + "priority": -1, + "focus": 2 + }, + { + "x": 4.5, + "y": -5, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 3, + "opacity": 150, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 4.5, + "y": -9, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 150, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 4.5, + "y": -13, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 100, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 4.5, + "y": -17, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 2, + "graphicFrame": 4, + "opacity": 50, + "priority": -1, + "focus": 2 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 3 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ], + [ + { + "x": 0, + "y": 0, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 0, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 2 + }, + { + "x": 128, + "y": -64, + "zoomX": 100, + "zoomY": 100, + "visible": true, + "target": 1, + "graphicFrame": 0, + "opacity": 255, + "locked": true, + "priority": -1, + "focus": 1 + } + ] + ], + "frameTimedEvents": { + "0": [ + { + "frameIndex": 0, + "resourceName": "PRSFX- Haze.wav", + "volume": 100, + "pitch": 85, + "eventType": "AnimTimedSoundEvent" + }, + { + "frameIndex": 0, + "resourceName": "Explosion1.m4a", + "volume": 100, + "pitch": 85, + "eventType": "AnimTimedSoundEvent" + } + ] + }, + "position": 2, + "hue": 0 +} \ No newline at end of file diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 6f2f293d099..6af8fedae8a 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -110,7 +110,8 @@ export enum CommonAnim { */ export enum EncounterAnim { MAGMA_BG, - MAGMA_SPOUT + MAGMA_SPOUT, + SMOKESCREEN } export class AnimConfig { @@ -533,16 +534,16 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise { export async function initEncounterAnims(scene: BattleScene, encounterAnim: EncounterAnim | EncounterAnim[]): Promise { const anims = Array.isArray(encounterAnim) ? encounterAnim : [encounterAnim]; const encounterAnimNames = Utils.getEnumKeys(EncounterAnim); - const encounterAnimIds = Utils.getEnumValues(EncounterAnim); + // const encounterAnimIds = Utils.getEnumValues(EncounterAnim); const encounterAnimFetches = []; for (const anim of anims) { if (encounterAnims.has(anim) && !isNullOrUndefined(encounterAnims.get(anim))) { continue; } - const encounterAnimId = encounterAnimIds[anim]; + // const encounterAnimId = encounterAnimIds[anim]; encounterAnimFetches.push(scene.cachedFetch(`./battle-anims/encounter-${encounterAnimNames[anim].toLowerCase().replace(/\_/g, "-")}.json`) .then(response => response.json()) - .then(cas => encounterAnims.set(encounterAnimId, new AnimConfig(cas)))); + .then(cas => encounterAnims.set(anim, new AnimConfig(cas)))); } await Promise.allSettled(encounterAnimFetches); } 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 0bf3145ea7e..8eabbbd8d05 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 @@ -62,7 +62,7 @@ export const AnOfferYouCantRefuseEncounter: IMysteryEncounter = const pokemon = getHighestStatTotalPlayerPokemon(scene, false); const price = scene.getWaveMoneyAmount(10); - encounter.setDialogueToken("strongestPokemon", pokemon.name); + encounter.setDialogueToken("strongestPokemon", pokemon.getNameToRender()); encounter.setDialogueToken("price", price.toString()); // Store pokemon and price diff --git a/src/data/mystery-encounters/encounters/clowing-around-encounter.ts b/src/data/mystery-encounters/encounters/clowing-around-encounter.ts deleted file mode 100644 index a3f1d78da89..00000000000 --- a/src/data/mystery-encounters/encounters/clowing-around-encounter.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { - EnemyPartyConfig, - initBattleWithEnemyConfig, - setEncounterRewards, -} 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 } 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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; -import { Species } from "#enums/species"; -import { TrainerType } from "#enums/trainer-type"; -import { getPokemonSpecies } from "#app/data/pokemon-species"; - -/** the i18n namespace for the encounter */ -const namespace = "mysteryEncounter:clowningAround"; - -/** - * Clowning Around encounter. - * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69} - * @see For biome requirements check {@linkcode mysteryEncountersByBiome} - */ -export const ClowningAroundEncounter: IMysteryEncounter = - MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) - .withEncounterTier(MysteryEncounterTier.ULTRA) - .withSceneWaveRangeRequirement(10, 180) // waves 10 to 180 - .withIntroSpriteConfigs([ - { - spriteKey: Species.MR_MIME.toString(), - fileRoot: "pokemon", - hasShadow: true, - repeat: true, - x: -25, - tint: 0.3, - y: -3, - yShadow: -3 - }, - { - spriteKey: Species.BLACEPHALON.toString(), - fileRoot: "pokemon/exp", - hasShadow: true, - repeat: true, - x: 25, - tint: 0.3, - y: -3, - yShadow: -3 - }, - { - spriteKey: "harlequin", - fileRoot: "trainer", - hasShadow: true, - x: 0 - }, - ]) - .withIntroDialogue([ - { - text: `${namespace}.intro`, - }, - { - text: `${namespace}.intro_dialogue`, - speaker: `${namespace}.speaker` - }, - ]) - .withOnInit((scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter; - - // Clown trainer is pulled from pool of boss trainers (gym leaders) for the biome - // They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons - const clownTrainerType = TrainerType.HARLEQUIN; - const clownPartyTemplate = new TrainerPartyCompoundTemplate( - new TrainerPartyTemplate(1, PartyMemberStrength.STRONG), - new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER), - new TrainerPartyTemplate(1, PartyMemberStrength.STRONG)); - const clownConfig = trainerConfigs[clownTrainerType].copy(); - clownConfig.setPartyTemplates(clownPartyTemplate); - clownConfig.partyTemplateFunc = null; // Overrides party template func - - encounter.enemyPartyConfigs.push({ - trainerConfig: clownConfig, - pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon - { - species: getPokemonSpecies(Species.MR_MIME), - isBoss: false - }, - { - species: getPokemonSpecies(Species.BLACEPHALON), - isBoss: true - }, - ] - }); - - return true; - }) - .withTitle(`${namespace}.title`) - .withDescription(`${namespace}.description`) - .withQuery(`${namespace}.query`) - .withSimpleOption( - { - buttonLabel: `${namespace}.option.1.label`, - buttonTooltip: `${namespace}.option.1.tooltip`, - selected: [ - { - text: `${namespace}.option.selected`, - }, - ], - }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter; - // Spawn battle - const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; - - setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true }); - await initBattleWithEnemyConfig(scene, config); - } - ) - .withSimpleOption( - { - buttonLabel: `${namespace}.option.2.label`, - buttonTooltip: `${namespace}.option.2.tooltip`, - selected: [ - { - text: `${namespace}.option.selected`, - }, - ], - }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter; - // Spawn hard fight with ULTRA/GREAT reward (can improve with luck) - const config: EnemyPartyConfig = encounter.enemyPartyConfigs[1]; - - setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ULTRA, ModifierTier.ULTRA, ModifierTier.GREAT, ModifierTier.GREAT], fillRemaining: true }); - - // Seed offsets to remove possibility of different trainers having exact same teams - let ret; - scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); - }, scene.currentBattle.waveIndex * 100); - return ret; - } - ) - .withSimpleOption( - { - buttonLabel: `${namespace}.option.3.label`, - buttonTooltip: `${namespace}.option.3.tooltip`, - selected: [ - { - text: `${namespace}.option.selected`, - }, - ], - }, - async (scene: BattleScene) => { - const encounter = scene.currentBattle.mysteryEncounter; - // Spawn brutal fight with ROGUE/ULTRA/GREAT reward (can improve with luck) - const config: EnemyPartyConfig = encounter.enemyPartyConfigs[2]; - - // To avoid player level snowballing from picking this option - encounter.expMultiplier = 0.9; - - setEncounterRewards(scene, { guaranteedModifierTiers: [ModifierTier.ROGUE, ModifierTier.ROGUE, ModifierTier.ULTRA, ModifierTier.GREAT], fillRemaining: true }); - - // Seed offsets to remove possibility of different trainers having exact same teams - let ret; - scene.executeWithSeedOffset(() => { - ret = initBattleWithEnemyConfig(scene, config); - }, scene.currentBattle.waveIndex * 1000); - return ret; - } - ) - .withOutroDialogue([ - { - text: `${namespace}.outro`, - }, - ]) - .build(); diff --git a/src/data/mystery-encounters/encounters/clowning-around-encounter.ts b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts new file mode 100644 index 00000000000..7f8f622c41f --- /dev/null +++ b/src/data/mystery-encounters/encounters/clowning-around-encounter.ts @@ -0,0 +1,469 @@ +import { EnemyPartyConfig, generateModifierTypeOption, 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 { BerryModifierType, 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 { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { Species } from "#enums/species"; +import { TrainerType } from "#enums/trainer-type"; +import { getPokemonSpecies } from "#app/data/pokemon-species"; +import { Abilities } from "#enums/abilities"; +import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { Type } from "#app/data/type"; +import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option"; +import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; +import { randSeedInt, randSeedShuffle } from "#app/utils"; +import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; +import { Mode } from "#app/ui/ui"; +import i18next from "i18next"; +import { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; +import { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; +import { Ability } from "#app/data/ability"; +import { BerryModifier } from "#app/modifier/modifier"; +import { BerryType } from "#enums/berry-type"; +import { BattlerIndex } from "#app/battle"; +import { Moves } from "#enums/moves"; +import { EncounterAnim, EncounterBattleAnim } from "#app/data/battle-anims"; +import { MoveCategory } from "#app/data/move"; +import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; + +/** the i18n namespace for the encounter */ +const namespace = "mysteryEncounter:clowningAround"; + +const RANDOM_ABILITY_POOL = [ + Abilities.STURDY, + Abilities.PICKUP, + Abilities.INTIMIDATE, + Abilities.GUTS, + Abilities.DROUGHT, + Abilities.DRIZZLE, + Abilities.SNOW_WARNING, + Abilities.SAND_STREAM, + Abilities.ELECTRIC_SURGE, + Abilities.PSYCHIC_SURGE, + Abilities.GRASSY_SURGE, + Abilities.MISTY_SURGE, + Abilities.MAGICIAN, + Abilities.SHEER_FORCE, + Abilities.PRANKSTER +]; + +/** + * Clowning Around encounter. + * @see {@link https://github.com/AsdarDevelops/PokeRogue-Events/issues/69 | GitHub Issue #69} + * @see For biome requirements check {@linkcode mysteryEncountersByBiome} + */ +export const ClowningAroundEncounter: IMysteryEncounter = + MysteryEncounterBuilder.withEncounterType(MysteryEncounterType.CLOWNING_AROUND) + .withEncounterTier(MysteryEncounterTier.ULTRA) + .withSceneWaveRangeRequirement(80, 180) + .withAnimations(EncounterAnim.SMOKESCREEN) + .withAutoHideIntroVisuals(false) + .withIntroSpriteConfigs([ + { + spriteKey: Species.MR_MIME.toString(), + fileRoot: "pokemon", + hasShadow: true, + repeat: true, + x: -25, + tint: 0.3, + y: -3, + yShadow: -3 + }, + { + spriteKey: Species.BLACEPHALON.toString(), + fileRoot: "pokemon/exp", + hasShadow: true, + repeat: true, + x: 25, + tint: 0.3, + y: -3, + yShadow: -3 + }, + { + spriteKey: "harlequin", + fileRoot: "trainer", + hasShadow: true, + x: 0, + y: 2, + yShadow: 2 + }, + ]) + .withIntroDialogue([ + { + text: `${namespace}.intro`, + }, + { + text: `${namespace}.intro_dialogue`, + speaker: `${namespace}.speaker` + }, + ]) + .withOnInit((scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + + // Clown trainer is pulled from pool of boss trainers (gym leaders) for the biome + // They are given an E4 template team, so will be stronger than usual boss encounter and always have 6 mons + const clownTrainerType = TrainerType.HARLEQUIN; + const clownPartyTemplate = new TrainerPartyCompoundTemplate( + new TrainerPartyTemplate(1, PartyMemberStrength.STRONG), + new TrainerPartyTemplate(1, PartyMemberStrength.STRONGER)); + const clownConfig = trainerConfigs[clownTrainerType].copy(); + clownConfig.setPartyTemplates(clownPartyTemplate); + clownConfig.setDoubleOnly(); + clownConfig.partyTemplateFunc = null; // Overrides party template func + + // Generate random ability for Blacephalon from pool + const ability = RANDOM_ABILITY_POOL[randSeedInt(RANDOM_ABILITY_POOL.length)]; + encounter.setDialogueToken("ability", new Ability(ability, 3).name); + encounter.misc = { ability }; + + encounter.enemyPartyConfigs.push({ + trainerConfig: clownConfig, + pokemonConfigs: [ // Overrides first 2 pokemon to be Mr. Mime and Blacephalon + { + species: getPokemonSpecies(Species.MR_MIME), + isBoss: true, + moveSet: [Moves.TEETER_DANCE, Moves.ALLY_SWITCH, Moves.DAZZLING_GLEAM, Moves.PSYCHIC] + }, + { // 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)]), + isBoss: true, + moveSet: [Moves.TRICK, Moves.HYPNOSIS, Moves.SHADOW_BALL, Moves.MIND_BLOWN] + }, + ], + doubleBattle: true + }); + + // Load animations/sfx for start of fight moves + loadCustomMovesForEncounter(scene, [Moves.ROLE_PLAY, Moves.TAUNT]); + + // These have to be defined at runtime so that modifierTypes exist + encounter.misc.RANDOM_ULTRA_POOL = [ + modifierTypes.REVIVER_SEED, + modifierTypes.GOLDEN_PUNCH, + modifierTypes.ATTACK_TYPE_BOOSTER, + modifierTypes.QUICK_CLAW, + modifierTypes.WIDE_LENS, + modifierTypes.WHITE_HERB + ]; + + encounter.misc.RANDOM_ROGUE_POOL = [ + modifierTypes.LEFTOVERS, + modifierTypes.SHELL_BELL, + modifierTypes.SOUL_DEW, + modifierTypes.SOOTHE_BELL, + modifierTypes.SCOPE_LENS, + modifierTypes.BATON, + modifierTypes.FOCUS_BAND, + modifierTypes.KINGS_ROCK, + modifierTypes.GRIP_CLAW + ]; + + return true; + }) + .withTitle(`${namespace}.title`) + .withDescription(`${namespace}.description`) + .withQuery(`${namespace}.query`) + .withOption( + new MysteryEncounterOptionBuilder() + .withOptionMode(MysteryEncounterOptionMode.DEFAULT) + .withDialogue({ + buttonLabel: `${namespace}.option.1.label`, + buttonTooltip: `${namespace}.option.1.tooltip`, + selected: [ + { + text: `${namespace}.option.1.selected`, + speaker: `${namespace}.speaker` + }, + ], + }) + .withOptionPhase(async (scene: BattleScene) => { + const encounter = scene.currentBattle.mysteryEncounter; + // Spawn battle + const config: EnemyPartyConfig = encounter.enemyPartyConfigs[0]; + + setEncounterRewards(scene, { guaranteedModifierTypeFuncs: [modifierTypes.TM_COMMON, modifierTypes.TM_GREAT, modifierTypes.MEMORY_MUSHROOM], fillRemaining: true }); + + // TODO: when Magic Room and Wonder Room are implemented, add those to start of battle + encounter.startOfBattleEffects.push( + { // Mr. Mime copies the Blacephalon's random ability + sourceBattlerIndex: BattlerIndex.ENEMY, + targets: [BattlerIndex.ENEMY_2], + move: new PokemonMove(Moves.ROLE_PLAY), + ignorePp: true + }, + { + sourceBattlerIndex: BattlerIndex.ENEMY_2, + targets: [BattlerIndex.PLAYER], + move: new PokemonMove(Moves.TAUNT), + ignorePp: true + }, + { + sourceBattlerIndex: BattlerIndex.ENEMY_2, + targets: [BattlerIndex.PLAYER_2], + move: new PokemonMove(Moves.TAUNT), + ignorePp: true + }); + + await transitionMysteryEncounterIntroVisuals(scene); + await initBattleWithEnemyConfig(scene, config); + }) + .withPostOptionPhase(async (scene: BattleScene): Promise => { + // After the battle, offer the player the opportunity to permanently swap ability + const abilityWasSwapped = await handleSwapAbility(scene); + if (abilityWasSwapped) { + await scene.ui.setMode(Mode.MESSAGE); + await showEncounterText(scene, `${namespace}.option.1.ability_gained`); + } + + // Play animations once ability swap is complete + // Trainer sprite that is shown at end of battle is not the same as mystery encounter intro visuals + scene.tweens.add({ + targets: scene.currentBattle.trainer, + x: "+=16", + y: "-=16", + alpha: 0, + ease: "Sine.easeInOut", + duration: 250 + }); + const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon()); + background.playWithoutTargets(scene, 230, 40, 2); + return true; + }) + .build() + ) + .withOption( + new MysteryEncounterOptionBuilder() + .withOptionMode(MysteryEncounterOptionMode.DEFAULT) + .withDialogue({ + buttonLabel: `${namespace}.option.2.label`, + buttonTooltip: `${namespace}.option.2.tooltip`, + selected: [ + { + text: `${namespace}.option.2.selected`, + speaker: `${namespace}.speaker` + }, + { + text: `${namespace}.option.2.selected_2`, + }, + { + text: `${namespace}.option.2.selected_3`, + speaker: `${namespace}.speaker` + }, + ], + }) + .withPreOptionPhase(async (scene: BattleScene) => { + // Swap player's items on pokemon with the most items + // Item comparisons look at whichever Pokemon has the greatest number of TRANSFERABLE, non-berry items + // So Vitamins, form change items, etc. are not included + const encounter = scene.currentBattle.mysteryEncounter; + + const party = scene.getParty(); + let mostHeldItemsPokemon = party[0]; + let count = mostHeldItemsPokemon.getHeldItems() + .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) + .reduce((v, m) => v + m.stackCount, 0); + + party.forEach(pokemon => { + const nextCount = pokemon.getHeldItems() + .filter(m => m.isTransferrable && !(m instanceof BerryModifier)) + .reduce((v, m) => v + m.stackCount, 0); + if (nextCount > count) { + mostHeldItemsPokemon = pokemon; + count = nextCount; + } + }); + + encounter.setDialogueToken("switchPokemon", mostHeldItemsPokemon.getNameToRender()); + + const items = mostHeldItemsPokemon.getHeldItems(); + + // Shuffles Berries (if they have any) + const berries = items.filter(m => m instanceof BerryModifier); + + berries.forEach(berry => { + const stackCount = berry.stackCount; + scene.removeModifier(berry); + const newBerry = generateModifierTypeOption(scene, modifierTypes.BERRY, [randSeedInt(Object.keys(BerryType).filter(s => !isNaN(Number(s))).length) as BerryType]).type as BerryModifierType; + for (let i = 0; i < stackCount; i++) { + applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newBerry); + } + }); + + // Shuffle Transferable held items in the same tier (only shuffles Ultra and Rogue atm) + const transferableItems = items.filter(m => m.isTransferrable && !(m instanceof BerryModifier)); + + transferableItems.forEach(transferableItem => { + const stackCount = transferableItem.stackCount; + transferableItem.type.withTierFromPool(); + + // Lucky Eggs and other items that do not appear in item pools are treated as Ultra rarity + const tier = transferableItem.type.tier ?? ModifierTier.ULTRA; + + if (tier === ModifierTier.ULTRA) { + scene.removeModifier(transferableItem); + for (let i = 0; i < stackCount; i++) { + const newItemType = encounter.misc.RANDOM_ULTRA_POOL[randSeedInt(encounter.misc.RANDOM_ULTRA_POOL.length)]; + const newMod = generateModifierTypeOption(scene, newItemType).type as PokemonHeldItemModifierType; + applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newMod); + } + } else if (tier === ModifierTier.ROGUE) { + scene.removeModifier(transferableItem); + for (let i = 0; i < stackCount; i++) { + const newItemType = encounter.misc.RANDOM_ROGUE_POOL[randSeedInt(encounter.misc.RANDOM_ROGUE_POOL.length)]; + const newMod = generateModifierTypeOption(scene, newItemType).type as PokemonHeldItemModifierType; + applyModifierTypeToPlayerPokemon(scene, mostHeldItemsPokemon, newMod); + } + } + }); + + }) + .withOptionPhase(async (scene: BattleScene) => { + leaveEncounterWithoutBattle(scene, true); + }) + .withPostOptionPhase(async (scene: BattleScene) => { + // Play animations + const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon()); + background.playWithoutTargets(scene, 230, 40, 2); + await transitionMysteryEncounterIntroVisuals(scene); + }) + .build() + ) + .withOption( + new MysteryEncounterOptionBuilder() + .withOptionMode(MysteryEncounterOptionMode.DEFAULT) + .withDialogue({ + buttonLabel: `${namespace}.option.3.label`, + buttonTooltip: `${namespace}.option.3.tooltip`, + selected: [ + { + text: `${namespace}.option.3.selected`, + speaker: `${namespace}.speaker` + }, + { + text: `${namespace}.option.3.selected_2`, + }, + { + text: `${namespace}.option.3.selected_3`, + speaker: `${namespace}.speaker` + }, + ], + }) + .withPreOptionPhase(async (scene: BattleScene) => { + // Swap player's types on all party pokemon + // If a Pokemon had a single type prior, they will still have a single type after + for (const pokemon of scene.getParty()) { + const originalTypes = pokemon.getTypes(false, false, true); + + // If the Pokemon has non-status moves that don't match the Pokemon's type, prioritizes those as the new type + // Makes the "randomness" of the shuffle slightly less punishing + let priorityTypes = pokemon.moveset + .filter(move => !originalTypes.includes(move.getMove().type) && move.getMove().category !== MoveCategory.STATUS) + .map(move => move.getMove().type); + if (priorityTypes?.length > 0) { + priorityTypes = [...new Set(priorityTypes)]; + randSeedShuffle(priorityTypes); + } + + let newTypes; + if (!originalTypes || originalTypes.length < 1) { + newTypes = priorityTypes?.length > 0 ? [priorityTypes.pop()] : [(randSeedInt(18) as Type)]; + } else { + newTypes = originalTypes.map(m => { + if (priorityTypes?.length > 0) { + const ret = priorityTypes.pop(); + randSeedShuffle(priorityTypes); + return ret; + } + + return randSeedInt(18) as Type; + }); + } + + if (!pokemon.mysteryEncounterData) { + pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, null, null, newTypes); + } else { + pokemon.mysteryEncounterData.types = newTypes; + } + } + }) + .withOptionPhase(async (scene: BattleScene) => { + leaveEncounterWithoutBattle(scene, true); + }) + .withPostOptionPhase(async (scene: BattleScene) => { + // Play animations + const background = new EncounterBattleAnim(EncounterAnim.SMOKESCREEN, scene.getPlayerPokemon(), scene.getPlayerPokemon()); + background.playWithoutTargets(scene, 230, 40, 2); + await transitionMysteryEncounterIntroVisuals(scene); + }) + .build() + ) + .withOutroDialogue([ + { + text: `${namespace}.outro`, + }, + ]) + .build(); + +async function handleSwapAbility(scene: BattleScene) { + return new Promise(async resolve => { + await showEncounterDialogue(scene, `${namespace}.option.1.apply_ability_dialogue`, `${namespace}.speaker`); + await showEncounterText(scene, `${namespace}.option.1.apply_ability_message`); + + scene.ui.setMode(Mode.MESSAGE).then(() => { + displayYesNoOptions(scene, resolve); + }); + }); +} + +function displayYesNoOptions(scene: BattleScene, resolve) { + showEncounterText(scene, `${namespace}.option.1.ability_prompt`, 500, false); + const fullOptions = [ + { + label: i18next.t("menu:yes"), + handler: () => { + onYesAbilitySwap(scene, resolve); + return true; + } + }, + { + label: i18next.t("menu:no"), + handler: () => { + resolve(false); + return true; + } + } + ]; + + const config: OptionSelectConfig = { + options: fullOptions, + maxOptions: 7, + yOffset: 0 + }; + scene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true); +} + +function onYesAbilitySwap(scene: BattleScene, resolve) { + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Do ability swap + if (!pokemon.mysteryEncounterData) { + pokemon.mysteryEncounterData = new MysteryEncounterPokemonData(null, Abilities.AERILATE); + } + pokemon.mysteryEncounterData.ability = scene.currentBattle.mysteryEncounter.misc.ability; + scene.currentBattle.mysteryEncounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender()); + resolve(true); + }; + + const onPokemonNotSelected = () => { + scene.ui.setMode(Mode.MESSAGE).then(() => { + displayYesNoOptions(scene, resolve); + }); + }; + + selectPokemonForOption(scene, onPokemonSelected, onPokemonNotSelected); +} diff --git a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts index e8a8272df0e..fde28a2ab00 100644 --- a/src/data/mystery-encounters/encounters/dark-deal-encounter.ts +++ b/src/data/mystery-encounters/encounters/dark-deal-encounter.ts @@ -127,7 +127,7 @@ export const DarkDealEncounter: IMysteryEncounter = const removedPokemon = getRandomPlayerPokemon(scene, false, true); scene.removePokemonFromPlayerParty(removedPokemon); - scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.name); + scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", removedPokemon.getNameToRender()); // Store removed pokemon types scene.currentBattle.mysteryEncounter.misc = [ diff --git a/src/data/mystery-encounters/encounters/field-trip-encounter.ts b/src/data/mystery-encounters/encounters/field-trip-encounter.ts index e67aaa1ffad..93e2d71aa1a 100644 --- a/src/data/mystery-encounters/encounters/field-trip-encounter.ts +++ b/src/data/mystery-encounters/encounters/field-trip-encounter.ts @@ -95,7 +95,7 @@ export const FieldTripEncounter: IMysteryEncounter = ]; setEncounterExp(scene, scene.getParty().map((p) => p.id), 50); } else { - encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("pokeName", pokemon.getNameToRender()); encounter.setDialogueToken("move", move.getName()); encounter.options[0].dialogue.selected = [ { @@ -187,7 +187,7 @@ export const FieldTripEncounter: IMysteryEncounter = ]; setEncounterExp(scene, scene.getParty().map((p) => p.id), 50); } else { - encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("pokeName", pokemon.getNameToRender()); encounter.setDialogueToken("move", move.getName()); encounter.options[1].dialogue.selected = [ { @@ -273,7 +273,7 @@ export const FieldTripEncounter: IMysteryEncounter = ]; setEncounterExp(scene, scene.getParty().map((p) => p.id), 50); } else { - encounter.setDialogueToken("pokeName", pokemon.name); + encounter.setDialogueToken("pokeName", pokemon.getNameToRender()); encounter.setDialogueToken("move", move.getName()); encounter.options[2].dialogue.selected = [ { diff --git a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts index 71311481ed1..5b4b582da01 100644 --- a/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts +++ b/src/data/mystery-encounters/encounters/fiery-fallout-encounter.ts @@ -191,7 +191,7 @@ export const FieryFalloutEncounter: IMysteryEncounter = const chosenPokemon = burnable[roll]; if (chosenPokemon.trySetStatus(StatusEffect.BURN)) { // Burn applied - encounter.setDialogueToken("burnedPokemon", chosenPokemon.name); + encounter.setDialogueToken("burnedPokemon", chosenPokemon.getNameToRender()); queueEncounterMessage(scene, `${namespace}.option.2.target_burned`); } } @@ -245,7 +245,7 @@ function giveLeadPokemonCharcoal(scene: BattleScene) { if (leadPokemon) { const charcoal = generateModifierTypeOption(scene, modifierTypes.ATTACK_TYPE_BOOSTER, [Type.FIRE]).type as AttackTypeBoosterModifierType; applyModifierTypeToPlayerPokemon(scene, leadPokemon, charcoal); - scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.name); + scene.currentBattle.mysteryEncounter.setDialogueToken("leadPokemon", leadPokemon.getNameToRender()); queueEncounterMessage(scene, `${namespace}.found_charcoal`); } } diff --git a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts index 5f6f235efc2..f0a96356eef 100644 --- a/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts +++ b/src/data/mystery-encounters/encounters/mysterious-chest-encounter.ts @@ -113,7 +113,7 @@ export const MysteriousChestEncounter: IMysteryEncounter = ); koPlayerPokemon(scene, highestLevelPokemon); - scene.currentBattle.mysteryEncounter.setDialogueToken("pokeName", highestLevelPokemon.name); + 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(() => { 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 26ddf86719d..80b7354b747 100644 --- a/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-pokemon-salesman-encounter.ts @@ -92,7 +92,7 @@ export const ThePokemonSalesmanEncounter: IMysteryEncounter = encounter.options[0].dialogue.buttonTooltip = `${namespace}.option.1.tooltip_shiny`; } const price = scene.getWaveMoneyAmount(priceMultiplier); - encounter.setDialogueToken("purchasePokemon", pokemon.name); + encounter.setDialogueToken("purchasePokemon", pokemon.getNameToRender()); encounter.setDialogueToken("price", price.toString()); encounter.misc = { price: price, 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 e3e410b5b93..36ac31f8046 100644 --- a/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-strong-stuff-encounter.ts @@ -16,6 +16,7 @@ import { BattleStat } from "#app/data/battle-stat"; import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; +import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; /** the i18n namespace for the encounter */ const namespace = "mysteryEncounter:theStrongStuff"; @@ -70,7 +71,7 @@ export const TheStrongStuffEncounter: IMysteryEncounter = species: getPokemonSpecies(Species.SHUCKLE), isBoss: true, bossSegments: 5, - spriteScale: 1.5, + mysteryEncounterData: new MysteryEncounterPokemonData(1.5), nature: Nature.BOLD, moveSet: [Moves.INFESTATION, Moves.SALT_CURE, Moves.GASTRO_ACID, Moves.HEAL_ORDER], modifierTypes: [ @@ -147,7 +148,7 @@ export const TheStrongStuffEncounter: IMysteryEncounter = modifyPlayerPokemonBST(pokemon, 10); } - encounter.setDialogueToken("highBstPokemon", highestBst.name); + encounter.setDialogueToken("highBstPokemon", highestBst.getNameToRender()); await showEncounterText(scene, `${namespace}.option.1.selected_2`, null, true); setEncounterRewards(scene, { fillRemaining: true }); diff --git a/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts b/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts new file mode 100644 index 00000000000..cab6d1ce82f --- /dev/null +++ b/src/data/mystery-encounters/mystery-encounter-pokemon-data.ts @@ -0,0 +1,16 @@ +import { Abilities } from "#enums/abilities"; +import { Type } from "#app/data/type"; + +export class MysteryEncounterPokemonData { + public spriteScale: number; + public ability: Abilities; + public passive: Abilities; + public types: Type[] = []; + + constructor(spriteScale?: number, ability?: Abilities, passive?: Abilities, types?: Type[]) { + this.spriteScale = spriteScale; + this.ability = ability; + this.passive = passive; + this.types = types; + } +} diff --git a/src/data/mystery-encounters/mystery-encounter.ts b/src/data/mystery-encounters/mystery-encounter.ts index be496fe1133..b1b47cef8ef 100644 --- a/src/data/mystery-encounters/mystery-encounter.ts +++ b/src/data/mystery-encounters/mystery-encounter.ts @@ -305,7 +305,7 @@ export default class IMysteryEncounter implements IMysteryEncounter { } } if (this.primaryPokemon?.length > 0) { - this.setDialogueToken("primaryName", this.primaryPokemon.name); + this.setDialogueToken("primaryName", this.primaryPokemon.getNameToRender()); for (const req of this.primaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, this.primaryPokemon); @@ -316,7 +316,7 @@ export default class IMysteryEncounter implements IMysteryEncounter { } } if (this.secondaryPokemonRequirements?.length > 0 && this.secondaryPokemon?.length > 0) { - this.setDialogueToken("secondaryName", this.secondaryPokemon[0].name); + this.setDialogueToken("secondaryName", this.secondaryPokemon[0].getNameToRender()); for (const req of this.secondaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, this.secondaryPokemon[0]); @@ -342,7 +342,7 @@ export default class IMysteryEncounter implements IMysteryEncounter { } } if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) { - this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.name); + this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.getNameToRender()); for (const req of opt.primaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, opt.primaryPokemon); @@ -353,7 +353,7 @@ export default class IMysteryEncounter implements IMysteryEncounter { } } if (opt.secondaryPokemonRequirements?.length > 0 && opt.secondaryPokemon?.length > 0) { - this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].name); + this.setDialogueToken("option" + j + "SecondaryName", opt.secondaryPokemon[0].getNameToRender()); for (const req of opt.secondaryPokemonRequirements) { if (!req.invertQuery) { const value = req.getDialogueToken(scene, opt.secondaryPokemon[0]); diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 53cf834dcad..b1c4ada12e7 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -21,7 +21,7 @@ import { AbsoluteAvariceEncounter } from "#app/data/mystery-encounters/encounter import { ATrainersTestEncounter } from "#app/data/mystery-encounters/encounters/a-trainers-test-encounter"; import { TrashToTreasureEncounter } from "#app/data/mystery-encounters/encounters/trash-to-treasure-encounter"; import { BerriesAboundEncounter } from "#app/data/mystery-encounters/encounters/berries-abound-encounter"; -import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowing-around-encounter"; +import { ClowningAroundEncounter } from "#app/data/mystery-encounters/encounters/clowning-around-encounter"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; diff --git a/src/data/mystery-encounters/utils/encounter-phase-utils.ts b/src/data/mystery-encounters/utils/encounter-phase-utils.ts index c166f287030..675e864f976 100644 --- a/src/data/mystery-encounters/utils/encounter-phase-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-phase-utils.ts @@ -30,6 +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"; /** * Animates exclamation sprite over trainer's head at start of encounter @@ -63,7 +65,7 @@ export interface EnemyPokemonConfig { isBoss: boolean; bossSegments?: number; bossSegmentModifier?: number; // Additive to the determined segment number - spriteScale?: number; + mysteryEncounterData?: MysteryEncounterPokemonData; formIndex?: number; level?: number; gender?: Gender; @@ -71,6 +73,8 @@ export interface EnemyPokemonConfig { 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; @@ -210,11 +214,14 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: enemyPokemon.formIndex = config.formIndex; } - // Set scale - if (!isNullOrUndefined(config.spriteScale)) { - enemyPokemon.mysteryEncounterData = { - spriteScale: config.spriteScale - }; + // Set shiny + if (!isNullOrUndefined(config.shiny)) { + enemyPokemon.shiny = config.shiny; + } + + // Set custom mystery encounter data fields (such as sprite scale, custom abilities, types, etc.) + if (!isNullOrUndefined(config.mysteryEncounterData)) { + enemyPokemon.mysteryEncounterData = config.mysteryEncounterData; } // Set Boss @@ -252,6 +259,11 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: // Set summon data fields + // Set ability + if (!isNullOrUndefined(config.ability)) { + enemyPokemon.summonData.ability = config.ability; + } + // Set gender if (!isNullOrUndefined(config.gender)) { enemyPokemon.gender = config.gender; @@ -381,7 +393,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p const pokemon = scene.getParty()[slotIndex]; const secondaryOptions = onPokemonSelected(pokemon); if (!secondaryOptions) { - scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.name); + scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); resolve(true); return; } @@ -395,7 +407,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p const onSelect = option.handler; option.handler = () => { onSelect(); - scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.name); + scene.currentBattle.mysteryEncounter.setDialogueToken("selectedPokemon", pokemon.getNameToRender()); resolve(true); return true; }; diff --git a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts index e3dd3106aeb..03066314a5b 100644 --- a/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts +++ b/src/data/mystery-encounters/utils/encounter-pokemon-utils.ts @@ -20,10 +20,6 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { modifierTypes, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import { Gender } from "#app/data/gender"; -export interface MysteryEncounterPokemonData { - spriteScale?: number -} - export function getSpriteKeysFromSpecies(species: Species, female?: boolean, formIndex?: integer, shiny?: boolean, variant?: integer): { spriteKey: string, fileRoot: string } { const spriteKey = getPokemonSpecies(species).getSpriteKey(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0); const fileRoot = getPokemonSpecies(species).getSpriteAtlasPath(female ?? false, formIndex ?? 0, shiny ?? false, variant ?? 0); @@ -447,7 +443,7 @@ function failCatch(scene: BattleScene, pokemon: EnemyPokemon, originalY: number, scene.currentBattle.lastUsedPokeball = pokeballType; removePb(scene, pokeball); - scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.name }), null, () => resolve(), null, true); + scene.ui.showText(i18next.t("battle:pokemonBrokeFree", { pokemonName: pokemon.getNameToRender() }), null, () => resolve(), null, true); }); } @@ -516,7 +512,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po Promise.all([pokemon.hideInfo(), scene.gameData.setPokemonCaught(pokemon)]).then(() => { if (scene.getParty().length === 6) { const promptRelease = () => { - scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => { + scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.getNameToRender() }), null, () => { scene.pokemonInfoContainer.makeRoomForConfirmUi(); scene.ui.setMode(Mode.CONFIRM, () => { scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, 0, (slotIndex: integer, _option: PartyOption) => { @@ -544,7 +540,7 @@ export async function catchPokemon(scene: BattleScene, pokemon: EnemyPokemon, po }; if (showCatchObtainMessage) { - scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.name }), null, doPokemonCatchMenu, 0, true); + scene.ui.showText(i18next.t(isObtain ? "battle:pokemonObtained" : "battle:pokemonCaught", { pokemonName: pokemon.getNameToRender() }), null, doPokemonCatchMenu, 0, true); } else { doPokemonCatchMenu(); } @@ -581,7 +577,7 @@ export async function doPokemonFlee(scene: BattleScene, pokemon: EnemyPokemon): onComplete: () => { pokemon.setVisible(false); scene.field.remove(pokemon, true); - showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.name }), 600, false) + showEncounterText(scene, i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), 600, false) .then(() => { resolve(); }); @@ -604,7 +600,7 @@ export function doPlayerFlee(scene: BattleScene, pokemon: EnemyPokemon): Promise onComplete: () => { pokemon.setVisible(false); scene.field.remove(pokemon, true); - showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.name }), 600, false) + showEncounterText(scene, i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), 600, false) .then(() => { resolve(); }); diff --git a/src/enums/battler-tag-type.ts b/src/enums/battler-tag-type.ts index c6e638c2426..b50cc4f7f10 100644 --- a/src/enums/battler-tag-type.ts +++ b/src/enums/battler-tag-type.ts @@ -64,5 +64,5 @@ export enum BattlerTagType { STOCKPILING = "STOCKPILING", RECEIVE_DOUBLE_DAMAGE = "RECEIVE_DOUBLE_DAMAGE", ALWAYS_GET_HIT = "ALWAYS_GET_HIT", - MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON" // Provides effects on post-summon for MEs + MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON", // Provides effects on post-summon for MEs } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e812d47fe1a..feca608038a 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -51,7 +51,7 @@ import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { getPokemonNameWithAffix } from "#app/messages.js"; -import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; +import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; export enum FieldPosition { CENTER, @@ -187,6 +187,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { this.fusionVariant = dataSource.fusionVariant || 0; this.fusionGender = dataSource.fusionGender; this.fusionLuck = dataSource.fusionLuck; + this.mysteryEncounterData = dataSource.mysteryEncounterData; } else { this.id = Utils.randSeedInt(4294967296); this.ivs = ivs || Utils.getIvsFromId(this.id); @@ -233,6 +234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } this.luck = (this.shiny ? this.variant + 1 : 0) + (this.fusionShiny ? this.fusionVariant + 1 : 0); this.fusionLuck = this.luck; + this.mysteryEncounterData = new MysteryEncounterPokemonData(); } this.generateName(); @@ -927,7 +929,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { } if (!types.length || !includeTeraType) { - if (!ignoreOverride && this.summonData?.types) { + if (this.mysteryEncounterData?.types?.length > 0) { + // "Permanent" override for a Pokemon's normal types, currently only used by Mystery Encounters + this.mysteryEncounterData.types.forEach(t => types.push(t)); + } else if (!ignoreOverride && this.summonData?.types) { this.summonData.types.forEach(t => types.push(t)); } else { const speciesForm = this.getSpeciesForm(ignoreOverride); @@ -994,6 +999,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.OPP_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_ABILITY_OVERRIDE]; } + if (this.mysteryEncounterData?.ability) { + return allAbilities[this.mysteryEncounterData.ability]; + } if (this.isFusion()) { return allAbilities[this.getFusionSpeciesForm(ignoreOverride).getAbility(this.fusionAbilityIndex)]; } @@ -1018,6 +1026,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE && !this.isPlayer()) { return allAbilities[Overrides.OPP_PASSIVE_ABILITY_OVERRIDE]; } + if (this.mysteryEncounterData?.passive) { + return allAbilities[this.mysteryEncounterData.passive]; + } let starterSpeciesId = this.species.speciesId; while (pokemonPrevolutions.hasOwnProperty(starterSpeciesId)) { @@ -4059,6 +4070,7 @@ export class PokemonSummonData { public speciesForm: PokemonSpeciesForm; public fusionSpeciesForm: PokemonSpeciesForm; public ability: Abilities = Abilities.NONE; + public passiveAbility: Abilities = Abilities.NONE; public gender: Gender; public fusionGender: Gender; public stats: integer[]; diff --git a/src/locales/en/mystery-encounters/clowning-around-dialogue.ts b/src/locales/en/mystery-encounters/clowning-around-dialogue.ts index 4b70c39eefb..dd7797a3035 100644 --- a/src/locales/en/mystery-encounters/clowning-around-dialogue.ts +++ b/src/locales/en/mystery-encounters/clowning-around-dialogue.ts @@ -1,31 +1,32 @@ export const clowningAroundDialogue = { intro: "It's...@d{64} a clown?", speaker: "Clown", - intro_dialogue: `Bumbling buffoon,\nbrace for a brilliant battle! - $You’ll be beaten by this brawling busker!\nBring it!`, + intro_dialogue: "Bumbling buffoon, brace for a brilliant battle!\nYou’ll be beaten by this brawling busker!", title: "Clowning Around", - description: "The clown seems eager to goad you into a battle, but to what end?\n\nSomething is off about this encounter.", + description: "Something is off about this encounter. The clown seems eager to goad you into a battle, but to what end?\n\nThe Blacephalon is especially strange, like it has @[TOOLTIP_TITLE]{weird types and ability.}", query: "What will you do?", option: { 1: { label: "Battle the Clown", tooltip: "(-) Strange Battle\n(?) Affects Pokémon Abilities", - selected: "Your pitiful Pokémon are poised for a pathetic performance!" + selected: "Your pitiful Pokémon are poised for a pathetic performance!", + apply_ability_dialogue: "A sensational showcase!\nYour savvy suits a sensational skill as spoils!", + apply_ability_message: "The clown is offering to permanently Skill Swap one of your Pokémon's ability to {{ability}}!", + ability_prompt: "Would you like to permanently teach a Pokémon the {{ability}} ability?", + ability_gained: "@s{level_up_fanfare}{{chosenPokemon}} gained the {{ability}} ability!" }, 2: { label: "Remain Unprovoked", tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Items", selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!", - selected_2: `The clown's Blacephalon uses Trick! - All of your {{switchPokemon}}'s items were randomly swapped!`, + selected_2: "The clown's Blacephalon uses Trick!\nAll of your {{switchPokemon}}'s items were randomly swapped!", selected_3: "Flustered fool, fall for my flawless deception!", }, 3: { label: "Return the Insults", tooltip: "(-) Upsets the Clown\n(?) Affects Pokémon Types", - selected: "I'm appalled at your absurd antics!\nTaste my temper!", - selected_2: `The clown's Blacephalon uses\na move you've never seen before! - All of your team's types were randomly swapped!`, + selected: "Dismal dodger, you deny a delightful duel?\nFeel my fury!", + selected_2: "The clown's Blacephalon uses a strange move!\nAll of your team's types were randomly swapped!", selected_3: "Flustered fool, fall for my flawless deception!", }, }, diff --git a/src/overrides.ts b/src/overrides.ts index 8e83692925d..1196797fac0 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -127,9 +127,9 @@ class DefaultOverrides { // ------------------------- // 1 to 256, set to null to ignore - readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256; + readonly MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null; readonly MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; - readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.CLOWNING_AROUND; + readonly MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null; // ------------------------- // MODIFIER / ITEM OVERRIDES diff --git a/src/phases.ts b/src/phases.ts index b0bfc739ae8..56206a97f1a 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1369,7 +1369,7 @@ export class PostSummonPhase extends PokemonPhase { } this.scene.arena.applyTags(ArenaTrapTag, pokemon); - // If this is fight or flight mystery encounter and is enemy pokemon summon phase, add enraged tag + // If this is mystery encounter and has post summon phase tag, apply post summon effects if (pokemon.findTags(t => t instanceof MysteryEncounterPostSummonTag)) { pokemon.lapseTag(BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON); } diff --git a/src/system/pokemon-data.ts b/src/system/pokemon-data.ts index 7e8f1e21c07..0601e883878 100644 --- a/src/system/pokemon-data.ts +++ b/src/system/pokemon-data.ts @@ -12,6 +12,7 @@ import { loadBattlerTag } from "../data/battler-tags"; import { Biome } from "#enums/biome"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data"; export default class PokemonData { public id: integer; @@ -54,6 +55,7 @@ export default class PokemonData { public bossSegments?: integer; public summonData: PokemonSummonData; + public mysteryEncounterData: MysteryEncounterPokemonData; constructor(source: Pokemon | any, forHistory: boolean = false) { const sourcePokemon = source instanceof Pokemon ? source : null; @@ -108,6 +110,7 @@ export default class PokemonData { this.status = sourcePokemon.status; if (this.player) { this.summonData = sourcePokemon.summonData; + this.mysteryEncounterData = sourcePokemon.mysteryEncounterData; } } } else { @@ -137,6 +140,14 @@ export default class PokemonData { this.summonData.tags = []; } } + + this.mysteryEncounterData = new MysteryEncounterPokemonData(); + if (!forHistory && source.mysteryEncounterData) { + this.mysteryEncounterData.spriteScale = source.mysteryEncounterData.spriteScale; + this.mysteryEncounterData.ability = source.mysteryEncounterData.ability; + this.mysteryEncounterData.passive = source.mysteryEncounterData.passive; + this.mysteryEncounterData.types = source.mysteryEncounterData.types; + } } }