diff --git a/public/images/mystery-encounters/b2w2_lady.json b/public/images/mystery-encounters/b2w2_lady.json new file mode 100644 index 00000000000..e143086e157 --- /dev/null +++ b/public/images/mystery-encounters/b2w2_lady.json @@ -0,0 +1,734 @@ +{ + "textures": [ + { + "image": "b2w2_lady.png", + "format": "RGBA8888", + "size": { + "w": 399, + "h": 360 + }, + "scale": 1, + "frames": [ + { + "filename": "0000.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 0, + "y": 0, + "w": 56, + "h": 72 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 57, + "y": 0, + "w": 56, + "h": 72 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 114, + "y": 0, + "w": 56, + "h": 72 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 9, + "y": 8, + "w": 55, + "h": 72 + }, + "frame": { + "x": 171, + "y": 0, + "w": 55, + "h": 72 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 11, + "y": 8, + "w": 54, + "h": 72 + }, + "frame": { + "x": 228, + "y": 0, + "w": 54, + "h": 72 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 11, + "y": 8, + "w": 54, + "h": 72 + }, + "frame": { + "x": 285, + "y": 0, + "w": 54, + "h": 72 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 14, + "y": 8, + "w": 52, + "h": 72 + }, + "frame": { + "x": 342, + "y": 0, + "w": 52, + "h": 72 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 20, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 0, + "y": 72, + "w": 48, + "h": 72 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 47, + "h": 72 + }, + "frame": { + "x": 57, + "y": 72, + "w": 47, + "h": 72 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 47, + "h": 72 + }, + "frame": { + "x": 114, + "y": 72, + "w": 47, + "h": 72 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 171, + "y": 72, + "w": 48, + "h": 72 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 228, + "y": 72, + "w": 48, + "h": 72 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 285, + "y": 72, + "w": 48, + "h": 72 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 342, + "y": 72, + "w": 48, + "h": 72 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 49, + "h": 72 + }, + "frame": { + "x": 0, + "y": 144, + "w": 49, + "h": 72 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 49, + "h": 72 + }, + "frame": { + "x": 57, + "y": 144, + "w": 49, + "h": 72 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 49, + "h": 72 + }, + "frame": { + "x": 114, + "y": 144, + "w": 49, + "h": 72 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 49, + "h": 72 + }, + "frame": { + "x": 171, + "y": 144, + "w": 49, + "h": 72 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 228, + "y": 144, + "w": 48, + "h": 72 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 285, + "y": 144, + "w": 48, + "h": 72 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 342, + "y": 144, + "w": 48, + "h": 72 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 22, + "y": 8, + "w": 48, + "h": 72 + }, + "frame": { + "x": 0, + "y": 216, + "w": 48, + "h": 72 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 20, + "y": 8, + "w": 50, + "h": 72 + }, + "frame": { + "x": 57, + "y": 216, + "w": 50, + "h": 72 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 18, + "y": 8, + "w": 51, + "h": 72 + }, + "frame": { + "x": 114, + "y": 216, + "w": 51, + "h": 72 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 18, + "y": 8, + "w": 51, + "h": 72 + }, + "frame": { + "x": 171, + "y": 216, + "w": 51, + "h": 72 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 15, + "y": 8, + "w": 53, + "h": 72 + }, + "frame": { + "x": 228, + "y": 216, + "w": 53, + "h": 72 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 57, + "h": 72 + }, + "frame": { + "x": 285, + "y": 216, + "w": 57, + "h": 72 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 342, + "y": 216, + "w": 56, + "h": 72 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 10, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 0, + "y": 288, + "w": 56, + "h": 72 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 9, + "y": 8, + "w": 55, + "h": 72 + }, + "frame": { + "x": 57, + "y": 288, + "w": 55, + "h": 72 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 114, + "y": 288, + "w": 56, + "h": 72 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 171, + "y": 288, + "w": 56, + "h": 72 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 228, + "y": 288, + "w": 56, + "h": 72 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 8, + "y": 8, + "w": 56, + "h": 72 + }, + "frame": { + "x": 285, + "y": 288, + "w": 56, + "h": 72 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:e7f062304401dbd7b3ec79512f0ff4cb:0136dac01331f88892a3df26aeab78f5:1ed1e22abb9b55d76337a5a599835c06$" + } +} diff --git a/public/images/mystery-encounters/b2w2_lady.png b/public/images/mystery-encounters/b2w2_lady.png new file mode 100644 index 00000000000..9dcc1281c9e Binary files /dev/null and b/public/images/mystery-encounters/b2w2_lady.png differ diff --git a/public/images/mystery-encounters/b2w2_veteran_m.json b/public/images/mystery-encounters/b2w2_veteran_m.json new file mode 100644 index 00000000000..8f07c7d44e2 --- /dev/null +++ b/public/images/mystery-encounters/b2w2_veteran_m.json @@ -0,0 +1,797 @@ +{ + "textures": [ + { + "image": "b2w2_veteran_m.png", + "format": "RGBA8888", + "size": { + "w": 424, + "h": 390 + }, + "scale": 1, + "frames": [ + { + "filename": "0000.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 0, + "y": 0, + "w": 43, + "h": 78 + } + }, + { + "filename": "0001.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 53, + "y": 0, + "w": 43, + "h": 78 + } + }, + { + "filename": "0002.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 106, + "y": 0, + "w": 43, + "h": 78 + } + }, + { + "filename": "0003.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 159, + "y": 0, + "w": 43, + "h": 78 + } + }, + { + "filename": "0004.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 212, + "y": 0, + "w": 44, + "h": 78 + } + }, + { + "filename": "0005.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 265, + "y": 0, + "w": 44, + "h": 78 + } + }, + { + "filename": "0006.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 318, + "y": 0, + "w": 44, + "h": 78 + } + }, + { + "filename": "0007.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 371, + "y": 0, + "w": 44, + "h": 78 + } + }, + { + "filename": "0008.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 0, + "y": 78, + "w": 44, + "h": 78 + } + }, + { + "filename": "0009.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 53, + "y": 78, + "w": 44, + "h": 78 + } + }, + { + "filename": "0010.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 48, + "h": 78 + }, + "frame": { + "x": 106, + "y": 78, + "w": 48, + "h": 78 + } + }, + { + "filename": "0011.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 50, + "h": 78 + }, + "frame": { + "x": 159, + "y": 78, + "w": 50, + "h": 78 + } + }, + { + "filename": "0012.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 53, + "h": 78 + }, + "frame": { + "x": 212, + "y": 78, + "w": 53, + "h": 78 + } + }, + { + "filename": "0013.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 53, + "h": 78 + }, + "frame": { + "x": 265, + "y": 78, + "w": 53, + "h": 78 + } + }, + { + "filename": "0014.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 52, + "h": 78 + }, + "frame": { + "x": 318, + "y": 78, + "w": 52, + "h": 78 + } + }, + { + "filename": "0015.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 51, + "h": 78 + }, + "frame": { + "x": 371, + "y": 78, + "w": 51, + "h": 78 + } + }, + { + "filename": "0016.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 52, + "h": 78 + }, + "frame": { + "x": 0, + "y": 156, + "w": 52, + "h": 78 + } + }, + { + "filename": "0017.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 52, + "h": 78 + }, + "frame": { + "x": 53, + "y": 156, + "w": 52, + "h": 78 + } + }, + { + "filename": "0018.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 53, + "h": 78 + }, + "frame": { + "x": 106, + "y": 156, + "w": 53, + "h": 78 + } + }, + { + "filename": "0019.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 53, + "h": 78 + }, + "frame": { + "x": 159, + "y": 156, + "w": 53, + "h": 78 + } + }, + { + "filename": "0020.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 53, + "h": 78 + }, + "frame": { + "x": 212, + "y": 156, + "w": 53, + "h": 78 + } + }, + { + "filename": "0021.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 52, + "h": 78 + }, + "frame": { + "x": 265, + "y": 156, + "w": 52, + "h": 78 + } + }, + { + "filename": "0022.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 51, + "h": 78 + }, + "frame": { + "x": 318, + "y": 156, + "w": 51, + "h": 78 + } + }, + { + "filename": "0023.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 51, + "h": 78 + }, + "frame": { + "x": 371, + "y": 156, + "w": 51, + "h": 78 + } + }, + { + "filename": "0024.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 51, + "h": 78 + }, + "frame": { + "x": 0, + "y": 234, + "w": 51, + "h": 78 + } + }, + { + "filename": "0025.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 50, + "h": 78 + }, + "frame": { + "x": 53, + "y": 234, + "w": 50, + "h": 78 + } + }, + { + "filename": "0026.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 48, + "h": 78 + }, + "frame": { + "x": 106, + "y": 234, + "w": 48, + "h": 78 + } + }, + { + "filename": "0027.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 46, + "h": 78 + }, + "frame": { + "x": 159, + "y": 234, + "w": 46, + "h": 78 + } + }, + { + "filename": "0028.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 46, + "h": 78 + }, + "frame": { + "x": 212, + "y": 234, + "w": 46, + "h": 78 + } + }, + { + "filename": "0029.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 265, + "y": 234, + "w": 44, + "h": 78 + } + }, + { + "filename": "0030.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 318, + "y": 234, + "w": 44, + "h": 78 + } + }, + { + "filename": "0031.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 371, + "y": 234, + "w": 44, + "h": 78 + } + }, + { + "filename": "0032.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 44, + "h": 78 + }, + "frame": { + "x": 0, + "y": 312, + "w": 44, + "h": 78 + } + }, + { + "filename": "0033.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 53, + "y": 312, + "w": 43, + "h": 78 + } + }, + { + "filename": "0034.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 106, + "y": 312, + "w": 43, + "h": 78 + } + }, + { + "filename": "0035.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 159, + "y": 312, + "w": 43, + "h": 78 + } + }, + { + "filename": "0036.png", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 80, + "h": 80 + }, + "spriteSourceSize": { + "x": 13, + "y": 2, + "w": 43, + "h": 78 + }, + "frame": { + "x": 212, + "y": 312, + "w": 43, + "h": 78 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:4deb068879a8ac195cb4f00c8b17b7f5:b32f0f90436649264b6f3c49b09ac06a:05e903aa75b8e50c28334d9b5e14c85a$" + } +} diff --git a/public/images/mystery-encounters/b2w2_veteran_m.png b/public/images/mystery-encounters/b2w2_veteran_m.png new file mode 100644 index 00000000000..967d82973e6 Binary files /dev/null and b/public/images/mystery-encounters/b2w2_veteran_m.png differ diff --git a/src/data/mystery-encounter-requirements.ts b/src/data/mystery-encounter-requirements.ts index 1a105f2f83b..f55319d881b 100644 --- a/src/data/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounter-requirements.ts @@ -1,18 +1,18 @@ -import { PlayerPokemon } from "#app/field/pokemon"; -import { ModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; +import {PlayerPokemon} from "#app/field/pokemon"; +import {ModifierType, PokemonHeldItemModifierType} from "#app/modifier/modifier-type"; import BattleScene from "../battle-scene"; -import { isNullOrUndefined } from "../utils"; -import { Abilities } from "#enums/abilities"; -import { Moves } from "#enums/moves"; -import { Species } from "#enums/species"; -import { TimeOfDay } from "#enums/time-of-day"; -import { Nature } from "./nature"; -import { EvolutionItem, pokemonEvolutions } from "./pokemon-evolutions"; -import { FormChangeItem, SpeciesFormChangeItemTrigger, pokemonFormChanges } from "./pokemon-forms"; -import { SpeciesFormKey } from "./pokemon-species"; -import { StatusEffect } from "./status-effect"; -import { Type } from "./type"; -import { WeatherType } from "./weather"; +import {isNullOrUndefined} from "../utils"; +import {Abilities} from "#enums/abilities"; +import {Moves} from "#enums/moves"; +import {Species} from "#enums/species"; +import {TimeOfDay} from "#enums/time-of-day"; +import {Nature} from "./nature"; +import {EvolutionItem, pokemonEvolutions} from "./pokemon-evolutions"; +import {FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger} from "./pokemon-forms"; +import {SpeciesFormKey} from "./pokemon-species"; +import {StatusEffect} from "./status-effect"; +import {Type} from "./type"; +import {WeatherType} from "./weather"; import {MysteryEncounterType} from "#enums/mystery-encounter-type"; export interface EncounterRequirement { @@ -39,7 +39,7 @@ export abstract class EncounterPokemonRequirement implements EncounterRequiremen } // Returns all party members that are compatible with this requirement. For non pokemon related requirements, the entire party is returned.. - queryParty(partyPokemon: PlayerPokemon[]) { + queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { return []; } @@ -215,23 +215,31 @@ export class PersistentModifierRequirement extends EncounterSceneRequirement { } export class MoneyRequirement extends EncounterSceneRequirement { - requiredMoney: number; + requiredMoney: number; // Static value + scalingMultiplier: number; // Calculates required money based off wave index - constructor(requiredMoney: number) { + constructor(requiredMoney: number, scalingMultiplier?: number) { super(); this.requiredMoney = requiredMoney; + this.scalingMultiplier = scalingMultiplier ? scalingMultiplier : 0; } meetsRequirement(scene: BattleScene): boolean { const money = scene.money; - if (!isNullOrUndefined(money) && this?.requiredMoney > 0 && this.requiredMoney > money) { + if (isNullOrUndefined(money)) { return false; } - return true; + + if (this?.scalingMultiplier > 0) { + this.requiredMoney = scene.getWaveMoneyAmount(this.scalingMultiplier); + } + return !(this?.requiredMoney > 0 && this.requiredMoney > money); } getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - return ["money", "₽" + scene.money.toString()]; + const value = this?.scalingMultiplier > 0 ? scene.getWaveMoneyAmount(this.scalingMultiplier).toString() : this.requiredMoney.toString(); + // Colors money text + return ["money", "@ecCol[MONEY]{₽" + value + "}"]; } } @@ -399,9 +407,9 @@ export class MoveRequirement extends EncounterPokemonRequirement { } getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { - const includedMoves = this.requiredMoves.filter((reqMove) => pokemon.moveset.filter((move) => move.moveId === reqMove).length > 0); + const includedMoves = pokemon.moveset.filter((move) => this.requiredMoves.includes(move.moveId)); if (includedMoves.length > 0) { - return ["move", Moves[includedMoves[0]].replace("_", " ")]; + return ["move", includedMoves[0].getName()]; } return null; } @@ -552,15 +560,15 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { minNumberOfPokemon:number; invertQuery:boolean; - constructor(StatusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { + constructor(statusEffect: StatusEffect | StatusEffect[], minNumberOfPokemon: number = 1, invertQuery: boolean = false) { super(); this.minNumberOfPokemon = minNumberOfPokemon; this.invertQuery = invertQuery; - if (StatusEffect instanceof Array) { - this.requiredStatusEffect = StatusEffect; + if (statusEffect instanceof Array) { + this.requiredStatusEffect = statusEffect; } else { this.requiredStatusEffect = []; - this.requiredStatusEffect.push(StatusEffect); + this.requiredStatusEffect.push(statusEffect); } } @@ -576,16 +584,38 @@ export class StatusEffectRequirement extends EncounterPokemonRequirement { queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => this.requiredStatusEffect.filter((StatusEffect) => pokemon.status?.effect === StatusEffect).length > 0); + return partyPokemon.filter((pokemon) => { + return this.requiredStatusEffect.some((statusEffect) => { + if (statusEffect === StatusEffect.NONE) { + // StatusEffect.NONE also checks for null or undefined status + return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status?.effect === statusEffect; + } else { + return pokemon.status?.effect === statusEffect; + } + }); + }); } else { // for an inverted query, we only want to get the pokemon that don't have ANY of the listed StatusEffects - return partyPokemon.filter((pokemon) => this.requiredStatusEffect.filter((StatusEffect) => pokemon.status?.effect === StatusEffect).length === 0); + // return partyPokemon.filter((pokemon) => this.requiredStatusEffect.filter((statusEffect) => pokemon.status?.effect === statusEffect).length === 0); + return partyPokemon.filter((pokemon) => { + return !this.requiredStatusEffect.some((statusEffect) => { + if (statusEffect === StatusEffect.NONE) { + // StatusEffect.NONE also checks for null or undefined status + return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status?.effect === statusEffect; + } else { + return pokemon.status?.effect === statusEffect; + } + }); + }); } } getDialogueToken(scene: BattleScene, pokemon?: PlayerPokemon): [string, string] { const reqStatus = this.requiredStatusEffect.filter((a) => { - pokemon.status?.effect ===(a); + if (a === StatusEffect.NONE) { + return isNullOrUndefined(pokemon.status) || isNullOrUndefined(pokemon.status.effect) || pokemon.status?.effect === a; + } + return pokemon.status?.effect === a; }); if (reqStatus.length > 0) { return ["status", StatusEffect[reqStatus[0]]]; @@ -863,7 +893,9 @@ export class HealthRatioRequirement extends EncounterPokemonRequirement { queryParty(partyPokemon: PlayerPokemon[]): PlayerPokemon[] { if (!this.invertQuery) { - return partyPokemon.filter((pokemon) => pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1]); + return partyPokemon.filter((pokemon) => { + return pokemon.getHpRatio() >= this.requiredHealthRange[0] && pokemon.getHpRatio() <= this.requiredHealthRange[1]; + }); } else { // for an inverted query, we only want to get the pokemon that don't have ANY of the listed requiredHealthRanges return partyPokemon.filter((pokemon) => pokemon.getHpRatio() < this.requiredHealthRange[0] || pokemon.getHpRatio() > this.requiredHealthRange[1]); diff --git a/src/data/mystery-encounter.ts b/src/data/mystery-encounter.ts index 8325a26d29d..d768a672632 100644 --- a/src/data/mystery-encounter.ts +++ b/src/data/mystery-encounter.ts @@ -5,10 +5,13 @@ import MysteryEncounterDialogue, { allMysteryEncounterDialogue } from "./mystery-encounters/dialogue/mystery-encounter-dialogue"; import MysteryEncounterOption from "./mystery-encounter-option"; -import { EncounterPokemonRequirement, EncounterSceneRequirement } from "./mystery-encounter-requirements"; +import { + EncounterPokemonRequirement, + EncounterSceneRequirement +} from "./mystery-encounter-requirements"; import * as Utils from "../utils"; import {EnemyPartyConfig} from "#app/data/mystery-encounters/mystery-encounter-utils"; -import { PlayerPokemon } from "#app/field/pokemon"; +import Pokemon, { PlayerPokemon } from "#app/field/pokemon"; import {isNullOrUndefined} from "../utils"; export enum MysteryEncounterVariant { @@ -167,6 +170,10 @@ export default class MysteryEncounter implements MysteryEncounter { return sceneReq && secReqs && priReqs; } + pokemonMeetsPrimaryRequirements?(scene: BattleScene, pokemon: Pokemon) { + return !this.primaryPokemonRequirements.some(req => !req.queryParty(scene.getParty()).map(p => p.id).includes(pokemon.id)); + } + private meetsPrimaryRequirementAndPrimaryPokemonSelected?(scene: BattleScene) { if (this.primaryPokemonRequirements.length === 0) { const activeMon = scene.getParty().filter(p => p.isActive(true)); @@ -263,6 +270,12 @@ export default class MysteryEncounter implements MysteryEncounter { * For multiple support pokemon in the dialogue token, it will have to be overridden. */ populateDialogueTokensFromRequirements?(scene: BattleScene) { + if (this.requirements?.length > 0) { + for (const req of this.requirements) { + const dialogueToken = req.getDialogueToken(scene); + this.setDialogueToken(...dialogueToken); + } + } if (this.primaryPokemon?.length > 0) { this.setDialogueToken("primaryName", this.primaryPokemon.name); for (const req of this.primaryPokemonRequirements) { @@ -281,9 +294,17 @@ export default class MysteryEncounter implements MysteryEncounter { } } } + + // Dialogue tokens for options for (let i = 0; i < this.options.length; i++) { const opt = this.options[i]; const j = i + 1; + if (opt.requirements?.length > 0) { + for (const req of opt.requirements) { + const dialogueToken = req.getDialogueToken(scene); + this.setDialogueToken("option" + j + this.capitalizeFirstLetter(dialogueToken[0]), dialogueToken[1]); + } + } if (opt.primaryPokemonRequirements?.length > 0 && opt.primaryPokemon?.length > 0) { this.setDialogueToken("option" + j + "PrimaryName", opt.primaryPokemon.name); for (const req of opt.primaryPokemonRequirements) { diff --git a/src/data/mystery-encounters/department-store-sale.ts b/src/data/mystery-encounters/department-store-sale.ts new file mode 100644 index 00000000000..3837e94af60 --- /dev/null +++ b/src/data/mystery-encounters/department-store-sale.ts @@ -0,0 +1,120 @@ +import BattleScene from "../../battle-scene"; +import { + leaveEncounterWithoutBattle, + setCustomEncounterRewards, +} from "#app/data/mystery-encounters/mystery-encounter-utils"; +import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter"; +import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import {WaveCountRequirement} from "../mystery-encounter-requirements"; +import { MysteryEncounterOptionBuilder } from "../mystery-encounter-option"; +import {modifierTypes} from "#app/modifier/modifier-type"; +import {Species} from "#enums/species"; +import {randSeedInt} from "#app/utils"; + +export const DepartmentStoreSaleEncounter: MysteryEncounter = new MysteryEncounterBuilder() + .withEncounterType(MysteryEncounterType.DEPARTMENT_STORE_SALE) + .withEncounterTier(MysteryEncounterTier.COMMON) + .withIntroSpriteConfigs([ + { + spriteKey: "b2w2_lady", + fileRoot: "mystery-encounters", + hasShadow: true, + x: -20 + }, + { + spriteKey: Species.FURFROU.toString(), + fileRoot: "pokemon", + hasShadow: true, + repeat: true, + x: 30 + } + ]) + // .withHideIntroVisuals(false) + .withSceneRequirement(new WaveCountRequirement([10, 100])) + .withOption(new MysteryEncounterOptionBuilder() + .withOptionPhase(async (scene: BattleScene) => { + // Choose TMs + const modifiers = []; + let i = 0; + while (i < 4) { + // 2/2/1 weight on TM rarity + const roll = randSeedInt(5); + if (roll < 2) { + modifiers.push(modifierTypes.TM_COMMON); + } else if (roll < 4) { + modifiers.push(modifierTypes.TM_GREAT); + } else { + modifiers.push(modifierTypes.TM_ULTRA); + } + i++; + } + + setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false}); + leaveEncounterWithoutBattle(scene); + }) + .build()) + .withOption(new MysteryEncounterOptionBuilder() + .withOptionPhase(async (scene: BattleScene) => { + // Choose Vitamins + const modifiers = []; + let i = 0; + while (i < 3) { + // 2/1 weight on base stat booster vs PP Up + const roll = randSeedInt(3); + if (roll === 0) { + modifiers.push(modifierTypes.PP_UP); + } else { + modifiers.push(modifierTypes.BASE_STAT_BOOSTER); + } + i++; + } + + setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false}); + leaveEncounterWithoutBattle(scene); + }) + .build()) + .withOption(new MysteryEncounterOptionBuilder() + .withOptionPhase(async (scene: BattleScene) => { + // Choose X Items + const modifiers = []; + let i = 0; + while (i < 5) { + // 4/1 weight on base stat booster vs Dire Hit + const roll = randSeedInt(5); + if (roll === 0) { + modifiers.push(modifierTypes.DIRE_HIT); + } else { + modifiers.push(modifierTypes.TEMP_STAT_BOOSTER); + } + i++; + } + + setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false}); + leaveEncounterWithoutBattle(scene); + }) + .build()) + .withOption(new MysteryEncounterOptionBuilder() + .withOptionPhase(async (scene: BattleScene) => { + // Choose Pokeballs + const modifiers = []; + let i = 0; + while (i < 4) { + // 10/30/20/5 weight on pokeballs + const roll = randSeedInt(65); + if (roll < 10) { + modifiers.push(modifierTypes.POKEBALL); + } else if (roll < 40) { + modifiers.push(modifierTypes.GREAT_BALL); + } else if (roll < 60) { + modifiers.push(modifierTypes.ULTRA_BALL); + } else { + modifiers.push(modifierTypes.ROGUE_BALL); + } + i++; + } + + setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false}); + leaveEncounterWithoutBattle(scene); + }) + .build()) + .build(); diff --git a/src/data/mystery-encounters/dialogue/department-store-sale-dialogue.ts b/src/data/mystery-encounters/dialogue/department-store-sale-dialogue.ts new file mode 100644 index 00000000000..3a9b75b9f83 --- /dev/null +++ b/src/data/mystery-encounters/dialogue/department-store-sale-dialogue.ts @@ -0,0 +1,36 @@ +import MysteryEncounterDialogue from "#app/data/mystery-encounters/dialogue/mystery-encounter-dialogue"; + +export const DepartmentStoreSaleDialogue: MysteryEncounterDialogue = { + intro: [ + { + text: "mysteryEncounter:department_store_sale_intro_message" + }, + { + text: "mysteryEncounter:department_store_sale_intro_dialogue", + speaker: "mysteryEncounter:department_store_sale_speaker" + } + ], + encounterOptionsDialogue: { + title: "mysteryEncounter:department_store_sale_title", + description: "mysteryEncounter:department_store_sale_description", + query: "mysteryEncounter:department_store_sale_query", + options: [ + { + buttonLabel: "mysteryEncounter:department_store_sale_option_1_label", + buttonTooltip: "mysteryEncounter:department_store_sale_option_1_tooltip" + }, + { + buttonLabel: "mysteryEncounter:department_store_sale_option_2_label", + buttonTooltip: "mysteryEncounter:department_store_sale_option_2_tooltip" + }, + { + buttonLabel: "mysteryEncounter:department_store_sale_option_3_label", + buttonTooltip: "mysteryEncounter:department_store_sale_option_3_tooltip" + }, + { + buttonLabel: "mysteryEncounter:department_store_sale_option_4_label", + buttonTooltip: "mysteryEncounter:department_store_sale_option_4_tooltip" + } + ] + } +}; diff --git a/src/data/mystery-encounters/dialogue/mystery-encounter-dialogue.ts b/src/data/mystery-encounters/dialogue/mystery-encounter-dialogue.ts index e390b0e706a..c64d9627388 100644 --- a/src/data/mystery-encounters/dialogue/mystery-encounter-dialogue.ts +++ b/src/data/mystery-encounters/dialogue/mystery-encounter-dialogue.ts @@ -5,10 +5,14 @@ import {DarkDealDialogue} from "#app/data/mystery-encounters/dialogue/dark-deal- import {FightOrFlightDialogue} from "#app/data/mystery-encounters/dialogue/fight-or-flight-dialogue"; import {TrainingSessionDialogue} from "#app/data/mystery-encounters/dialogue/training-session-dialogue"; import { SleepingSnorlaxDialogue } from "./sleeping-snorlax-dialogue"; +import {DepartmentStoreSaleDialogue} from "#app/data/mystery-encounters/dialogue/department-store-sale-dialogue"; +import {ShadyVitaminDealerDialogue} from "#app/data/mystery-encounters/dialogue/shady-vitamin-dealer"; +import {TextStyle} from "#app/ui/text"; export class TextDisplay { speaker?: TemplateStringsArray | `mysteryEncounter:${string}`; text: TemplateStringsArray | `mysteryEncounter:${string}`; + style?: TextStyle; } export class OptionTextDisplay { @@ -17,6 +21,7 @@ export class OptionTextDisplay { disabledTooltip?: TemplateStringsArray | `mysteryEncounter:${string}`; secondOptionPrompt?: TemplateStringsArray | `mysteryEncounter:${string}`; selected?: TextDisplay[]; + style?: TextStyle; } export class EncounterOptionsDialogue { @@ -91,4 +96,6 @@ export function initMysteryEncounterDialogue() { allMysteryEncounterDialogue[MysteryEncounterType.FIGHT_OR_FLIGHT] = FightOrFlightDialogue; allMysteryEncounterDialogue[MysteryEncounterType.TRAINING_SESSION] = TrainingSessionDialogue; allMysteryEncounterDialogue[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxDialogue; + allMysteryEncounterDialogue[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleDialogue; + allMysteryEncounterDialogue[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerDialogue; } diff --git a/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts b/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts new file mode 100644 index 00000000000..4a9d48913c5 --- /dev/null +++ b/src/data/mystery-encounters/dialogue/shady-vitamin-dealer.ts @@ -0,0 +1,42 @@ +import MysteryEncounterDialogue from "#app/data/mystery-encounters/dialogue/mystery-encounter-dialogue"; + +export const ShadyVitaminDealerDialogue: MysteryEncounterDialogue = { + intro: [ + { + text: "mysteryEncounter:shady_vitamin_dealer_intro_message" + }, + { + text: "mysteryEncounter:shady_vitamin_dealer_intro_dialogue", + speaker: "mysteryEncounter:shady_vitamin_dealer_speaker" + } + ], + encounterOptionsDialogue: { + title: "mysteryEncounter:shady_vitamin_dealer_title", + description: "mysteryEncounter:shady_vitamin_dealer_description", + query: "mysteryEncounter:shady_vitamin_dealer_query", + options: [ + { + buttonLabel: "mysteryEncounter:shady_vitamin_dealer_option_1_label", + buttonTooltip: "mysteryEncounter:shady_vitamin_dealer_option_1_tooltip", + selected: [ + { + text: "mysteryEncounter:shady_vitamin_dealer_option_selected" + }, + ] + }, + { + buttonLabel: "mysteryEncounter:shady_vitamin_dealer_option_2_label", + buttonTooltip: "mysteryEncounter:shady_vitamin_dealer_option_2_tooltip", + selected: [ + { + text: "mysteryEncounter:shady_vitamin_dealer_option_selected" + }, + ] + }, + { + buttonLabel: "mysteryEncounter:shady_vitamin_dealer_option_3_label", + buttonTooltip: "mysteryEncounter:shady_vitamin_dealer_option_3_tooltip" + } + ] + } +}; diff --git a/src/data/mystery-encounters/fight-or-flight.ts b/src/data/mystery-encounters/fight-or-flight.ts index 90cc57bbc13..9ee4166662e 100644 --- a/src/data/mystery-encounters/fight-or-flight.ts +++ b/src/data/mystery-encounters/fight-or-flight.ts @@ -9,7 +9,7 @@ import { } from "#app/data/mystery-encounters/mystery-encounter-utils"; import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter"; import {MysteryEncounterType} from "#enums/mystery-encounter-type"; -import {WaveCountRequirement} from "../mystery-encounter-requirements"; +import {MoveRequirement, WaveCountRequirement} from "../mystery-encounter-requirements"; import {MysteryEncounterOptionBuilder} from "../mystery-encounter-option"; import { getPartyLuckValue, @@ -23,6 +23,18 @@ import {StatChangePhase} from "#app/phases"; import {BattleStat} from "#app/data/battle-stat"; import Pokemon from "#app/field/pokemon"; import {randSeedInt} from "#app/utils"; +import {Moves} from "#enums/moves"; +import {TextStyle} from "#app/ui/text"; + +const validMovesForSteal = [ + Moves.PLUCK, + Moves.COVET, + Moves.FAKE_OUT, + Moves.THIEF, + Moves.TRICK, + Moves.SWITCHEROO, + Moves.GIGA_DRAIN +]; export const FightOrFlightEncounter: MysteryEncounter = new MysteryEncounterBuilder() .withEncounterType(MysteryEncounterType.FIGHT_OR_FLIGHT) @@ -70,6 +82,21 @@ export const FightOrFlightEncounter: MysteryEncounter = new MysteryEncounterBuil } ]; + // If player has a stealing move, they succeed automatically + const moveRequirement = new MoveRequirement(validMovesForSteal); + const validPokemon = moveRequirement.queryParty(scene.getParty()); + if (validPokemon?.length > 0) { + // Use first valid pokemon to execute the theivery + const pokemon = validPokemon[0]; + encounter.setDialogueToken("thiefPokemon", pokemon.name); + encounter.setDialogueToken(...moveRequirement.getDialogueToken(scene, pokemon)); + encounter.dialogue.encounterOptionsDialogue.options[1].buttonTooltip = "mysteryEncounter:fight_or_flight_option_2_steal_tooltip"; + encounter.dialogue.encounterOptionsDialogue.options[1].style = TextStyle.SUMMARY_GREEN; + } else { + encounter.dialogue.encounterOptionsDialogue.options[1].buttonTooltip = "mysteryEncounter:fight_or_flight_option_2_tooltip"; + encounter.dialogue.encounterOptionsDialogue.options[1].style = null; + } + return true; }) .withOption(new MysteryEncounterOptionBuilder() @@ -83,9 +110,23 @@ export const FightOrFlightEncounter: MysteryEncounter = new MysteryEncounterBuil .withOption(new MysteryEncounterOptionBuilder() .withOptionPhase(async (scene: BattleScene) => { // Pick steal + const encounter = scene.currentBattle.mysteryEncounter; const item = scene.currentBattle.mysteryEncounter.misc as ModifierTypeOption; setCustomEncounterRewards(scene, { guaranteedModifierTypeOptions: [item], fillRemaining: false}); + // If player has a stealing move, they succeed automatically + const moveRequirement = new MoveRequirement(validMovesForSteal); + const validPokemon = moveRequirement.queryParty(scene.getParty()); + if (validPokemon?.length > 0) { + // Use first valid pokemon to execute the theivery + const pokemon = validPokemon[0]; + encounter.setDialogueToken("thiefPokemon", pokemon.name); + encounter.setDialogueToken(...moveRequirement.getDialogueToken(scene, pokemon)); + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_steal_result"); + leaveEncounterWithoutBattle(scene); + return; + } + const roll = randSeedInt(16); if (roll > 6) { // Noticed and attacked by boss, gets +1 to all stats at start of fight (62.5%) @@ -101,8 +142,8 @@ export const FightOrFlightEncounter: MysteryEncounter = new MysteryEncounterBuil } else { // Steal item (37.5%) // Display result message then proceed to rewards - await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_good_result") - .then(() => leaveEncounterWithoutBattle(scene)); + await showEncounterText(scene, "mysteryEncounter:fight_or_flight_option_2_good_result"); + leaveEncounterWithoutBattle(scene); } }) .build()) diff --git a/src/data/mystery-encounters/mystery-encounter-utils.ts b/src/data/mystery-encounters/mystery-encounter-utils.ts index 8cd4963daa1..dcbcc753d32 100644 --- a/src/data/mystery-encounters/mystery-encounter-utils.ts +++ b/src/data/mystery-encounters/mystery-encounter-utils.ts @@ -10,8 +10,12 @@ import Trainer, {TrainerVariant} from "../../field/trainer"; import {PokemonExpBoosterModifier} from "#app/modifier/modifier"; import { CustomModifierSettings, + getModifierPoolForType, ModifierPoolType, + ModifierType, ModifierTypeFunc, + ModifierTypeGenerator, + modifierTypes, PokemonHeldItemModifierType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; @@ -31,6 +35,7 @@ import {Mode} from "#app/ui/ui"; import {PartyOption, PartyUiMode} from "#app/ui/party-ui-handler"; import {OptionSelectConfig, OptionSelectItem} from "#app/ui/abstact-option-select-ui-handler"; import {WIGHT_INCREMENT_ON_SPAWN_MISS} from "#app/data/mystery-encounters/mystery-encounters"; +import {getBBCodeFrag, TextStyle} from "#app/ui/text"; /** * @@ -162,21 +167,35 @@ export function koPlayerPokemon(pokemon: PlayerPokemon) { pokemon.updateInfo(); } -export function getTextWithEncounterDialogueTokens(scene: BattleScene, textKey: TemplateStringsArray | `mysteryEncounter:${string}`): string { +export function getTextWithEncounterDialogueTokensAndColor(scene: BattleScene, textKey: TemplateStringsArray | `mysteryEncounter:${string}`, primaryStyle: TextStyle = TextStyle.MESSAGE): string { if (isNullOrUndefined(textKey)) { return null; } let textString: string = i18next.t(textKey); - const dialogueTokens = scene.currentBattle?.mysteryEncounter?.dialogueTokens; + // Apply primary styling before anything else, if it exists + textString = getBBCodeFrag(textString, primaryStyle) + "[/color][/shadow]"; + const primaryStyleString = [...textString.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0]; + // Apply dialogue tokens + const dialogueTokens = scene.currentBattle?.mysteryEncounter?.dialogueTokens; if (dialogueTokens) { dialogueTokens.forEach((value) => { textString = textString.replace(value[0], value[1]); }); } + // Set custom colors + // Looks for any pattern like this: @ecCol[SUMMARY_BLUE]{my text to color} + // Resulting in: "my text to color" string with TextStyle.SUMMARY_BLUE + textString = textString.replace(/@ecCol\[([^{]*)\]{([^}]*)}/gi, (substring, textStyle: string, textToColor: string) => { + return "[/color][/shadow]" + getBBCodeFrag(textToColor, TextStyle[textStyle]) + "[/color][/shadow]" + primaryStyleString; + }); + + // Remove extra style block at the end + textString = textString.replace(/\[color=[^\[]*\]\[shadow=[^\[]*\]\[\/color\]\[\/shadow\]/gi, ""); + return textString; } @@ -186,7 +205,7 @@ export function getTextWithEncounterDialogueTokens(scene: BattleScene, textKey: * @param contentKey */ export function queueEncounterMessage(scene: BattleScene, contentKey: TemplateStringsArray | `mysteryEncounter:${string}`): void { - const text: string = getTextWithEncounterDialogueTokens(scene, contentKey); + const text: string = getTextWithEncounterDialogueTokensAndColor(scene, contentKey, TextStyle.MESSAGE); scene.queueMessage(text, null, true); } @@ -197,7 +216,7 @@ export function queueEncounterMessage(scene: BattleScene, contentKey: TemplateSt */ export function showEncounterText(scene: BattleScene, contentKey: TemplateStringsArray | `mysteryEncounter:${string}`): Promise { return new Promise(resolve => { - const text: string = getTextWithEncounterDialogueTokens(scene, contentKey); + const text: string = getTextWithEncounterDialogueTokensAndColor(scene, contentKey, TextStyle.MESSAGE); scene.ui.showText(text, null, () => resolve(), 0, true); }); } @@ -210,8 +229,8 @@ export function showEncounterText(scene: BattleScene, contentKey: TemplateString * @param callback */ export function showEncounterDialogue(scene: BattleScene, textContentKey: TemplateStringsArray | `mysteryEncounter:${string}`, speakerContentKey: TemplateStringsArray | `mysteryEncounter:${string}`, callback?: Function) { - const text: string = getTextWithEncounterDialogueTokens(scene, textContentKey); - const speaker: string = getTextWithEncounterDialogueTokens(scene, speakerContentKey); + const text: string = getTextWithEncounterDialogueTokensAndColor(scene, textContentKey, TextStyle.MESSAGE); + const speaker: string = getTextWithEncounterDialogueTokensAndColor(scene, speakerContentKey); scene.ui.showDialogue(text, speaker, null, callback, 0, 0); } @@ -399,6 +418,50 @@ export async function initBattleWithEnemyConfig(scene: BattleScene, partyConfig: } } +/** + * Will update player money, and animate change (sound optional) + * @param scene - Battle Scene + * @param changeValue + * @param playSound + */ +export function updatePlayerMoney(scene: BattleScene, changeValue: number, playSound: boolean = true) { + scene.money += changeValue; + scene.updateMoneyText(); + scene.animateMoneyChanged(false); + if (playSound) { + scene.playSound("buy"); + } +} + +/** + * 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 generateModifierType(scene: BattleScene, modifier: () => ModifierType, pregenArgs?: any[]): ModifierType { + const modifierId = Object.keys(modifierTypes).find(k => modifierTypes[k] === modifier); + let result: ModifierType = modifierTypes[modifierId]?.(); + + // Gets tier of item by checking player item pool + const modifierPool = getModifierPoolForType(ModifierPoolType.PLAYER); + Object.keys(modifierPool).every(modifierTier => { + const modType = modifierPool[modifierTier].find(m => { + if (m.modifierType.id === modifierId) { + return m; + } + }); + if (modType) { + result = modType.modifierType; + return false; + } + return true; + }); + + result = result instanceof ModifierTypeGenerator ? result.generateType(scene.getParty(), pregenArgs) : result; + return result; +} + /** * Will initialize reward phases to follow the mystery encounter * Can have shop displayed or skipped @@ -439,8 +502,9 @@ export function setCustomEncounterRewards(scene: BattleScene, customShopRewards? * @param onPokemonSelected - Any logic that needs to be performed when Pokemon is chosen * If a second option needs to be selected, onPokemonSelected should return a OptionSelectItem[] object * @param onPokemonNotSelected - Any logic that needs to be performed if no Pokemon is chosen + * @param selectablePokemonFilter */ -export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void): Promise { +export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (pokemon: PlayerPokemon) => void | OptionSelectItem[], onPokemonNotSelected?: () => void, selectablePokemonFilter?: (pokemon: PlayerPokemon) => string): Promise { return new Promise(resolve => { // Open party screen to choose pokemon to train scene.ui.setMode(Mode.PARTY, PartyUiMode.SELECT, -1, (slotIndex: integer, option: PartyOption) => { @@ -493,7 +557,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p if (!textPromptKey) { displayOptions(); } else { - const secondOptionSelectPrompt = getTextWithEncounterDialogueTokens(scene, textPromptKey); + const secondOptionSelectPrompt = getTextWithEncounterDialogueTokensAndColor(scene, textPromptKey, TextStyle.MESSAGE); scene.ui.showText(secondOptionSelectPrompt, null, displayOptions, null, true); } }); @@ -506,7 +570,7 @@ export function selectPokemonForOption(scene: BattleScene, onPokemonSelected: (p resolve(false); }); } - }); + }, selectablePokemonFilter); }); } diff --git a/src/data/mystery-encounters/mystery-encounters.ts b/src/data/mystery-encounters/mystery-encounters.ts index 918868d3d2c..003b544ce50 100644 --- a/src/data/mystery-encounters/mystery-encounters.ts +++ b/src/data/mystery-encounters/mystery-encounters.ts @@ -7,6 +7,8 @@ import {TrainingSessionEncounter} from "#app/data/mystery-encounters/training-se import { Biome } from "#app/enums/biome"; import { SleepingSnorlaxEncounter } from "./sleeping-snorlax"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import {DepartmentStoreSaleEncounter} from "#app/data/mystery-encounters/department-store-sale"; +import {ShadyVitaminDealerEncounter} from "#app/data/mystery-encounters/shady-vitamin-dealer"; // Spawn chance: (BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT + WIGHT_INCREMENT_ON_SPAWN_MISS * ) / 256 export const BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT = 1; @@ -19,18 +21,20 @@ export const allMysteryEncounters : {[encounterType:string]: MysteryEncounter} = // To enable an encounter in all biomes, do not add to this map export const mysteryEncountersByBiome = new Map([ [Biome.TOWN, [ + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.PLAINS,[ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.GRASS, [ - MysteryEncounterType.SLEEPING_SNORLAX + MysteryEncounterType.SLEEPING_SNORLAX, + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.TALL_GRASS, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.METROPOLIS, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.FOREST, [ MysteryEncounterType.SLEEPING_SNORLAX @@ -43,7 +47,7 @@ export const mysteryEncountersByBiome = new Map([ ]], [Biome.BEACH, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.LAKE, [ @@ -67,10 +71,10 @@ export const mysteryEncountersByBiome = new Map([ ]], [Biome.MEADOW, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.POWER_PLANT, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.VOLCANO, [ @@ -82,7 +86,7 @@ export const mysteryEncountersByBiome = new Map([ ]], [Biome.FACTORY, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.RUINS, [ @@ -97,7 +101,7 @@ export const mysteryEncountersByBiome = new Map([ ]], [Biome.CONSTRUCTION_SITE, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.JUNGLE, [ @@ -109,7 +113,7 @@ export const mysteryEncountersByBiome = new Map([ ]], [Biome.SLUM, [ - + MysteryEncounterType.DEPARTMENT_STORE_SALE ]], [Biome.SNOWY_FOREST, [ @@ -131,6 +135,8 @@ export function initMysteryEncounters() { allMysteryEncounters[MysteryEncounterType.FIGHT_OR_FLIGHT] = FightOrFlightEncounter; allMysteryEncounters[MysteryEncounterType.TRAINING_SESSION] = TrainingSessionEncounter; allMysteryEncounters[MysteryEncounterType.SLEEPING_SNORLAX] = SleepingSnorlaxEncounter; + allMysteryEncounters[MysteryEncounterType.DEPARTMENT_STORE_SALE] = DepartmentStoreSaleEncounter; + allMysteryEncounters[MysteryEncounterType.SHADY_VITAMIN_DEALER] = ShadyVitaminDealerEncounter; // Append encounters that can occur in any biome to biome map const anyBiomeEncounters: MysteryEncounterType[] = Object.keys(MysteryEncounterType).filter(e => !isNaN(Number(e))).map(k => Number(k) as MysteryEncounterType); diff --git a/src/data/mystery-encounters/shady-vitamin-dealer.ts b/src/data/mystery-encounters/shady-vitamin-dealer.ts new file mode 100644 index 00000000000..bd2e6094748 --- /dev/null +++ b/src/data/mystery-encounters/shady-vitamin-dealer.ts @@ -0,0 +1,148 @@ +import BattleScene from "../../battle-scene"; +import { + generateModifierType, + leaveEncounterWithoutBattle, + queueEncounterMessage, + selectPokemonForOption, + setCustomEncounterRewards, + updatePlayerMoney, +} from "#app/data/mystery-encounters/mystery-encounter-utils"; +import MysteryEncounter, {MysteryEncounterBuilder, MysteryEncounterTier} from "../mystery-encounter"; +import {MysteryEncounterType} from "#enums/mystery-encounter-type"; +import { + HealthRatioRequirement, + MoneyRequirement, + StatusEffectRequirement, + WaveCountRequirement +} from "../mystery-encounter-requirements"; +import {MysteryEncounterOptionBuilder} from "../mystery-encounter-option"; +import {modifierTypes} from "#app/modifier/modifier-type"; +import {Species} from "#enums/species"; +import {randSeedInt} from "#app/utils"; +import Pokemon, {PlayerPokemon} from "#app/field/pokemon"; +import {StatusEffect} from "#app/data/status-effect"; + +export const ShadyVitaminDealerEncounter: MysteryEncounter = new MysteryEncounterBuilder() + .withEncounterType(MysteryEncounterType.SHADY_VITAMIN_DEALER) + .withEncounterTier(MysteryEncounterTier.COMMON) + .withIntroSpriteConfigs([ + { + spriteKey: Species.KROOKODILE.toString(), + fileRoot: "pokemon", + hasShadow: true, + repeat: true, + x: 10, + y: -1 + }, + { + spriteKey: "b2w2_veteran_m", + fileRoot: "mystery-encounters", + hasShadow: true, + x: -10, + y: 2 + } + ]) + .withSceneRequirement(new WaveCountRequirement([10, 180])) + .withPrimaryPokemonRequirement(new StatusEffectRequirement([StatusEffect.NONE])) // Pokemon must not have status + .withPrimaryPokemonRequirement(new HealthRatioRequirement([0.34, 1])) // Pokemon must have above 1/3rd HP + .withOption(new MysteryEncounterOptionBuilder() + .withSceneRequirement(new MoneyRequirement(0, 2)) // Wave scaling multiplier of 2 for cost + .withPreOptionPhase(async (scene: BattleScene): Promise => { + const encounter = scene.currentBattle.mysteryEncounter; + const onPokemonSelected = (pokemon: PlayerPokemon) => { + // Update money + updatePlayerMoney(scene, -(encounter.options[0].requirements[0] as MoneyRequirement).requiredMoney); + // Calculate modifiers and dialogue tokens + const modifiers = [ + generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER), + generateModifierType(scene, modifierTypes.BASE_STAT_BOOSTER) + ]; + encounter.setDialogueToken("boost1", modifiers[0].name); + encounter.setDialogueToken("boost2", modifiers[1].name); + encounter.misc = { + chosenPokemon: pokemon, + modifiers: modifiers + }; + }; + + // Only Pokemon that can gain benefits are unfainted with no status + const selectableFilter = (pokemon: Pokemon) => { + // If pokemon meets primary pokemon reqs, it can be selected + const meetsReqs = encounter.pokemonMeetsPrimaryRequirements(scene, pokemon); + if (!meetsReqs) { + return "Pokémon must be healthy enough."; + } + + return null; + }; + + return selectPokemonForOption(scene, onPokemonSelected, null, selectableFilter); + }) + .withOptionPhase(async (scene: BattleScene) => { + // Choose Cheap Option + const encounter = scene.currentBattle.mysteryEncounter; + const chosenPokemon = encounter.misc.chosenPokemon; + const modifiers = encounter.misc.modifiers; + + for (const modType of modifiers) { + const modifier = modType.newModifier(chosenPokemon); + await scene.addModifier(modifier, true, false, false, true); + } + scene.updateModifiers(true); + + leaveEncounterWithoutBattle(scene); + }) + .withPostOptionPhase(async (scene: BattleScene) => { + // Damage and status applied after dealer leaves (to make thematic sense) + const encounter = scene.currentBattle.mysteryEncounter; + const chosenPokemon = encounter.misc.chosenPokemon; + + // Pokemon takes 1/3 max HP damage + const damage = Math.round(chosenPokemon.getMaxHp() / 3); + chosenPokemon.hp = Math.max(chosenPokemon.hp - damage, 0); + + // Roll for poison (80%) + if (randSeedInt(10) < 10) { + if (chosenPokemon.trySetStatus(StatusEffect.TOXIC)) { + // Toxic applied + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_bad_poison"); + } else { + // Pokemon immune or something else prevents status + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_damage_only"); + } + } else { + queueEncounterMessage(scene, "mysteryEncounter:shady_vitamin_dealer_damage_only"); + } + + chosenPokemon.updateInfo(); + }) + .build()) + .withOption(new MysteryEncounterOptionBuilder() + .withSceneRequirement(new MoneyRequirement(0, 5)) // Wave scaling multiplier of 2 for cost + .withOptionPhase(async (scene: BattleScene) => { + // Choose Expensive Option + const modifiers = []; + let i = 0; + while (i < 3) { + // 2/1 weight on base stat booster vs PP Up + const roll = randSeedInt(3); + if (roll === 0) { + modifiers.push(modifierTypes.PP_UP); + } else { + + } + i++; + } + + setCustomEncounterRewards(scene, { guaranteedModifierTypeFuncs: modifiers, fillRemaining: false}); + leaveEncounterWithoutBattle(scene); + }) + .build()) + .withOption(new MysteryEncounterOptionBuilder() + .withOptionPhase(async (scene: BattleScene) => { + // Leave encounter with no rewards or exp + leaveEncounterWithoutBattle(scene, true); + return true; + }) + .build()) + .build(); diff --git a/src/data/mystery-encounters/sleeping-snorlax.ts b/src/data/mystery-encounters/sleeping-snorlax.ts index 56baa4ea1e7..bf53ad72593 100644 --- a/src/data/mystery-encounters/sleeping-snorlax.ts +++ b/src/data/mystery-encounters/sleeping-snorlax.ts @@ -1,7 +1,7 @@ import BattleScene from "../../battle-scene"; import { EnemyPartyConfig, - EnemyPokemonConfig, + EnemyPokemonConfig, generateModifierType, initBattleWithEnemyConfig, leaveEncounterWithoutBattle, queueEncounterMessage, setCustomEncounterRewards @@ -12,7 +12,6 @@ import {MysteryEncounterType} from "#enums/mystery-encounter-type"; import {MoveRequirement, WaveCountRequirement} from "../mystery-encounter-requirements"; import {MysteryEncounterOptionBuilder} from "../mystery-encounter-option"; import { - ModifierTypeGenerator, ModifierTypeOption, modifierTypes } from "#app/modifier/modifier-type"; @@ -76,7 +75,8 @@ export const SleepingSnorlaxEncounter: MysteryEncounter = new MysteryEncounterBu const p = instance.primaryPokemon; p.status = new Status(StatusEffect.SLEEP, 0, 3); p.updateInfo(true); - const sitrus = (modifierTypes.BERRY?.() as ModifierTypeGenerator).generateType(scene.getParty(), [BerryType.SITRUS]); + // const sitrus = (modifierTypes.BERRY?.() as ModifierTypeGenerator).generateType(scene.getParty(), [BerryType.SITRUS]); + const sitrus = generateModifierType(scene, modifierTypes.BERRY, [BerryType.SITRUS]); setCustomEncounterRewards(scene, { guaranteedModifierTypeOptions: [new ModifierTypeOption(sitrus, 0)], fillRemaining: false}); queueEncounterMessage(scene, "mysteryEncounter:sleeping_snorlax_option_2_bad_result"); diff --git a/src/data/mystery-encounters/training-session.ts b/src/data/mystery-encounters/training-session.ts index 9866a2785a1..c833c37c06f 100644 --- a/src/data/mystery-encounters/training-session.ts +++ b/src/data/mystery-encounters/training-session.ts @@ -1,7 +1,7 @@ import BattleScene from "../../battle-scene"; import { EnemyPartyConfig, - getTextWithEncounterDialogueTokens, + getTextWithEncounterDialogueTokensAndColor, initBattleWithEnemyConfig, selectPokemonForOption, setCustomEncounterRewards @@ -128,7 +128,7 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu scene.addModifier(mod, true, false, false, true); } scene.updateModifiers(true); - scene.queueMessage(getTextWithEncounterDialogueTokens(scene, "mysteryEncounter:training_session_battle_finished_1"), null, true); + scene.queueMessage(getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:training_session_battle_finished_1"), null, true); }; setCustomEncounterRewards(scene, { fillRemaining: true }, null, onBeforeRewardsPhase); @@ -174,7 +174,7 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu scene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { - scene.queueMessage(getTextWithEncounterDialogueTokens(scene, "mysteryEncounter:training_session_battle_finished_2"), null, true); + scene.queueMessage(getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:training_session_battle_finished_2"), null, true); // Add the pokemon back to party with Nature change playerPokemon.setNature(encounter.misc.chosenNature); scene.gameData.setPokemonCaught(playerPokemon, false); @@ -237,7 +237,7 @@ export const TrainingSessionEncounter: MysteryEncounter = new MysteryEncounterBu scene.removePokemonFromPlayerParty(playerPokemon, false); const onBeforeRewardsPhase = () => { - scene.queueMessage(getTextWithEncounterDialogueTokens(scene, "mysteryEncounter:training_session_battle_finished_3"), null, true); + scene.queueMessage(getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:training_session_battle_finished_3"), null, true); // Add the pokemon back to party with ability change const abilityIndex = encounter.misc.abilityIndex; if (!!playerPokemon.getFusionSpeciesForm()) { diff --git a/src/enums/mystery-encounter-type.ts b/src/enums/mystery-encounter-type.ts index a29a409103d..6e2815babca 100644 --- a/src/enums/mystery-encounter-type.ts +++ b/src/enums/mystery-encounter-type.ts @@ -4,5 +4,7 @@ export enum MysteryEncounterType { DARK_DEAL, FIGHT_OR_FLIGHT, SLEEPING_SNORLAX, - TRAINING_SESSION + TRAINING_SESSION, + DEPARTMENT_STORE_SALE, + SHADY_VITAMIN_DEALER } diff --git a/src/field/mystery-encounter-intro.ts b/src/field/mystery-encounter-intro.ts index 82e9bb49f89..8fdbd27ac9b 100644 --- a/src/field/mystery-encounter-intro.ts +++ b/src/field/mystery-encounter-intro.ts @@ -79,12 +79,12 @@ export default class MysteryEncounterIntroVisuals extends Phaser.GameObjects.Con // Sprite offset from origin if (config.x || config.y) { if (config.x) { - sprite.x = origin + config.x; - tintSprite.x = origin + config.x; + sprite.setPosition(origin + config.x, sprite.y); + tintSprite.setPosition(origin + config.x, tintSprite.y); } if (config.y) { - sprite.y = origin + config.y; - tintSprite.y = origin + config.y; + sprite.setPosition(sprite.x, config.y); + tintSprite.setPosition(tintSprite.x, config.y); } } else { // Single sprite diff --git a/src/locales/en/mystery-encounter.ts b/src/locales/en/mystery-encounter.ts index f6b3a80e0ec..9fe63b33f58 100644 --- a/src/locales/en/mystery-encounter.ts +++ b/src/locales/en/mystery-encounter.ts @@ -1,17 +1,28 @@ import {SimpleTranslationEntries} from "#app/interfaces/locales"; +/** + * Patterns that can be used: + * '$' will be treated as a new line for Message and Dialogue strings + * '@d{}' will add a time delay to text animation for Message and Dialogue strings + * + * '@ec{}' will auto-inject the matching token value for the specified Encounter + * + * '@ecCol[]{}' will auto-color the given text to a specified TextStyle (e.g. TextStyle.SUMMARY_GREEN) + * + * Any '(+)' or '(-)' type of tooltip will auto-color to green/blue respectively. THIS ONLY OCCURS FOR OPTION TOOLTIPS, NOWHERE ELSE + * Other types of '(...)' tooltips will have to specify the text color manually by using '@ecCol[SUMMARY_GREEN]{}' pattern + */ export const mysteryEncounter: SimpleTranslationEntries = { // DO NOT REMOVE "unit_test_dialogue": "@ec{test}@ec{test} @ec{test@ec{test}} @ec{test1} @ec{test\} @ec{test\\} @ec{test\\\} {test}", - // Mysterious Encounters -- Common Tier - + // Mystery Encounters -- Common Tier "mysterious_chest_intro_message": "You found...@d{32} a chest?", "mysterious_chest_title": "The Mysterious Chest", "mysterious_chest_description": "A beautifully ornamented chest stands on the ground. There must be something good inside... right?", "mysterious_chest_query": "Will you open it?", "mysterious_chest_option_1_label": "Open it", - "mysterious_chest_option_1_tooltip": "(35%) Something terrible\n(40%) Okay Rewards\n(20%) Good Rewards\n(4%) Great Rewards\n(1%) Amazing Rewards", + "mysterious_chest_option_1_tooltip": "@ecCol[SUMMARY_BLUE]{(35%) Something terrible}\n@ecCol[SUMMARY_GREEN]{(40%) Okay Rewards}\n@ecCol[SUMMARY_GREEN]{(20%) Good Rewards}\n@ecCol[SUMMARY_GREEN]{(4%) Great Rewards}\n@ecCol[SUMMARY_GREEN]{(1%) Amazing Rewards}", "mysterious_chest_option_2_label": "It's too risky, leave", "mysterious_chest_option_2_tooltip": "(-) No Rewards", "mysterious_chest_option_1_selected_message": "You open the chest to find...", @@ -27,36 +38,82 @@ export const mysteryEncounter: SimpleTranslationEntries = { "fight_or_flight_title": "Fight or Flight", "fight_or_flight_description": "It looks like there's a strong Pokémon guarding an item. Battling is the straightforward approach, but this Pokémon looks strong. You could also try to sneak around, though the Pokémon might catch you.", "fight_or_flight_query": "What will you do?", - "fight_or_flight_option_1_label": "Battle it", - "fight_or_flight_option_1_tooltip": "(+) Hard Battle\n(+) New Item", - "fight_or_flight_option_2_label": "Sneak around", - "fight_or_flight_option_2_tooltip": "(35%) Steal Item\n(65%) Harder Battle", + "fight_or_flight_option_1_label": "Battle the Pokémon", + "fight_or_flight_option_1_tooltip": "(-) Hard Battle\n(+) New Item", + "fight_or_flight_option_2_label": "Steal the item", + "fight_or_flight_option_2_tooltip": "@ecCol[SUMMARY_GREEN]{(35%) Steal Item}\n@ecCol[SUMMARY_BLUE]{(65%) Harder Battle}", + "fight_or_flight_option_2_steal_tooltip": "@ecCol[SUMMARY_GREEN]{(?) Use a Pokémon Move}", "fight_or_flight_option_3_label": "Leave", "fight_or_flight_option_3_tooltip": "(-) No Rewards", "fight_or_flight_option_1_selected_message": "You approach the\nPokémon without fear.", "fight_or_flight_option_2_good_result": `.@d{32}.@d{32}.@d{32} $You manage to sneak your way\npast and grab the item!`, + "fight_or_flight_option_2_steal_result": `.@d{32}.@d{32}.@d{32} + $Your @ec{thiefPokemon} helps you out and uses @ec{move}! + $ You nabbed the item!`, "fight_or_flight_option_2_bad_result": `.@d{32}.@d{32}.@d{32} $The Pokémon catches you\nas you try to sneak around!`, "fight_or_flight_boss_enraged": "The opposing @ec{enemyPokemon} has become enraged!", "fight_or_flight_option_3_selected": "You leave the strong Pokémon\nwith its prize and continue on.", - // Mysterious Encounters -- Uncommon Tier + "department_store_sale_intro_message": "It's a lady with a ton of shopping bags.", + "department_store_sale_speaker": "Shopper", + "department_store_sale_intro_dialogue": `Hello! Are you here for\nthe amazing sales too? + $There's a special coupon that you can\nredeem for a free item during the sale! + $I have an extra one. Here you go!`, + "department_store_sale_title": "Department Store Sale", + "department_store_sale_description": "There is merchandise in every direction! It looks like there are 4 counters where you can redeem the coupon for various items. The possibilities are endless!", + "department_store_sale_query": "Which counter will you go to?", + "department_store_sale_option_1_label": "TM Counter", + "department_store_sale_option_1_tooltip": "(+) TM Shop", + "department_store_sale_option_2_label": "Vitamin Counter", + "department_store_sale_option_2_tooltip": "(+) Vitamin Shop", + "department_store_sale_option_3_label": "Battle Item Counter", + "department_store_sale_option_3_tooltip": "(+) X Item Shop", + "department_store_sale_option_4_label": "Pokéball Counter", + "department_store_sale_option_4_tooltip": "(+) Pokéball Shop", + "department_store_sale_outro": "What a deal! You should shop there more often.", + + "shady_vitamin_dealer_intro_message": "A man in a dark coat approaches you.", + "shady_vitamin_dealer_speaker": "Shady Salesman", + "shady_vitamin_dealer_intro_dialogue": `.@d{16}.@d{16}.@d{16} + $I've got the goods if you've got the money. + $Make sure your Pokémon can handle it though.`, + "shady_vitamin_dealer_title": "The Vitamin Dealer", + "shady_vitamin_dealer_description": "The man opens his jacket to reveal some Pokémon vitamins. The numbers he quotes seem like a really good deal. Almost too good...\nHe offers two package deals to choose from.", + "shady_vitamin_dealer_query": "Which deal will choose?", + "shady_vitamin_dealer_option_1_label": "The Cheap Deal", + "shady_vitamin_dealer_option_1_tooltip": "(-) Pay @ec{option1Money}\n(-) Side Effects?\n(+) Chosen Pokémon Gains 2 Random Vitamins", + "shady_vitamin_dealer_option_2_label": "The Pricey Deal", + "shady_vitamin_dealer_option_2_tooltip": "(-) Pay @ec{option2Money}\n(-) Side Effects?\n(+) Chosen Pokémon Gains 2 Random Vitamins", + "shady_vitamin_dealer_option_selected": `The man hands you two bottles and quickly disappears. + $@ec{selectedPokemon} gained @ec{boost1} and @ec{boost2} boosts!`, + "shady_vitamin_dealer_damage_only": `But the medicine had some side effects! + $Your @ec{selectedPokemon} takes some damage...`, + "shady_vitamin_dealer_bad_poison": `But the medicine had some side effects! + $Your @ec{selectedPokemon} takes some damage\nand becomes badly poisoned...`, + "shady_vitamin_dealer_poison": `But the medicine had some side effects! + $Your @ec{selectedPokemon} becomes poisoned...`, + "shady_vitamin_dealer_option_3_label": "Leave", + "shady_vitamin_dealer_option_3_tooltip": "(-) No Rewards", + "shady_vitamin_dealer_outro_good": "Looks like there were no side-effects this time.", + + // Mystery Encounters -- Uncommon Tier "mysterious_challengers_intro_message": "Mysterious challengers have appeared!", "mysterious_challengers_title": "Mysterious Challengers", "mysterious_challengers_description": "If you defeat a challenger, you might impress them enough to receive a boon. But some look tough, are you up to the challenge?", "mysterious_challengers_query": "Who will you battle?", "mysterious_challengers_option_1_label": "A clever, mindful foe", - "mysterious_challengers_option_1_tooltip": "(+) Standard Battle\n(+) Move Item Rewards", + "mysterious_challengers_option_1_tooltip": "(-) Standard Battle\n(+) Move Item Rewards", "mysterious_challengers_option_2_label": "A strong foe", - "mysterious_challengers_option_2_tooltip": "(+) Hard Battle\n(+) Good Rewards", + "mysterious_challengers_option_2_tooltip": "(-) Hard Battle\n(+) Good Rewards", "mysterious_challengers_option_3_label": "The mightiest foe", - "mysterious_challengers_option_3_tooltip": "(+) Brutal Battle\n(+) Great Rewards", + "mysterious_challengers_option_3_tooltip": "(-) Brutal Battle\n(+) Great Rewards", "mysterious_challengers_option_selected_message": "The trainer steps forward...", "mysterious_challengers_outro_win": "The mysterious challenger was defeated!", - // Mysterious Encounters -- Rare Tier + // Mystery Encounters -- Rare Tier "training_session_intro_message": "You've come across some\ntraining tools and supplies.", "training_session_title": "Training Session", "training_session_description": "These supplies look like they could be used to train a member of your party! There are a few ways you could train your Pokémon, by battling against it with the rest of your team.", @@ -78,7 +135,7 @@ export const mysteryEncounter: SimpleTranslationEntries = { $Its ability was changed to @ec{ability}!`, "training_session_outro_win": "That was a successful training session!", - // Mysterious Encounters -- Super Rare Tier + // Mystery Encounters -- Super Rare Tier "dark_deal_intro_message": "A strange man in a tattered coat\nstands in your way...", "dark_deal_speaker": "Shady Guy", @@ -108,9 +165,9 @@ export const mysteryEncounter: SimpleTranslationEntries = { "sleeping_snorlax_description": "You could attack it to try and get it to move, or simply wait for it to wake up.", "sleeping_snorlax_query": "What will you do?", "sleeping_snorlax_option_1_label": "Fight it", - "sleeping_snorlax_option_1_tooltip": "(+) Fight Sleeping Snorlax", + "sleeping_snorlax_option_1_tooltip": "(-) Fight Sleeping Snorlax", "sleeping_snorlax_option_2_label": "Wait for it to move", - "sleeping_snorlax_option_2_tooltip": "(75%) Wait a short time\n(25%) Wait a long time", + "sleeping_snorlax_option_2_tooltip": "@ecCol[SUMMARY_BLUE]{(75%) Wait a short time}\n@ecCol[SUMMARY_BLUE]{(25%) Wait a long time}", "sleeping_snorlax_option_3_label": "Steal", "sleeping_snorlax_option_3_tooltip": "(+) Leftovers", "sleeping_snorlax_option_3_disabled_tooltip": "Your Pokémon need to know certain moves to choose this", diff --git a/src/overrides.ts b/src/overrides.ts index 6634ca32788..f3ac35e2075 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -118,9 +118,9 @@ export const EGG_GACHA_PULL_COUNT_OVERRIDE: number = 0; */ // 1 to 256, set to null to ignore -export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = null; +export const MYSTERY_ENCOUNTER_RATE_OVERRIDE: number = 256; export const MYSTERY_ENCOUNTER_TIER_OVERRIDE: MysteryEncounterTier = null; -export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = null; +export const MYSTERY_ENCOUNTER_OVERRIDE: MysteryEncounterType = MysteryEncounterType.SHADY_VITAMIN_DEALER; /** * MODIFIER / ITEM OVERRIDES diff --git a/src/phases/mystery-encounter-phase.ts b/src/phases/mystery-encounter-phase.ts index 1cd04339b84..b928d98834c 100644 --- a/src/phases/mystery-encounter-phase.ts +++ b/src/phases/mystery-encounter-phase.ts @@ -3,7 +3,7 @@ import BattleScene from "../battle-scene"; import { Phase } from "../phase"; import { Mode } from "../ui/ui"; import { - getTextWithEncounterDialogueTokens + getTextWithEncounterDialogueTokensAndColor } from "../data/mystery-encounters/mystery-encounter-utils"; import { CheckSwitchPhase, NewBattlePhase, PostSummonPhase, ReturnPhase, ScanIvsPhase, SummonPhase, ToggleDoublePositionPhase } from "../phases"; import MysteryEncounterOption from "../data/mystery-encounter-option"; @@ -89,9 +89,9 @@ export class MysteryEncounterPhase extends Phase { const nextAction = i === selectedDialogue.length - 1 ? endDialogueAndContinueEncounter : showNextDialogue; const dialogue = selectedDialogue[i]; let title: string = null; - const text: string = getTextWithEncounterDialogueTokens(this.scene, dialogue.text); + const text: string = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.text); if (dialogue.speaker) { - title = getTextWithEncounterDialogueTokens(this.scene, dialogue.speaker); + title = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.speaker); } if (title) { @@ -451,9 +451,9 @@ export class PostMysteryEncounterPhase extends Phase { const nextAction = i === outroDialogue.length - 1 ? endPhase : showNextDialogue; const dialogue = outroDialogue[i]; let title: string = null; - const text: string = getTextWithEncounterDialogueTokens(this.scene, dialogue.text); + const text: string = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.text); if (dialogue.speaker) { - title = getTextWithEncounterDialogueTokens(this.scene, dialogue.speaker); + title = getTextWithEncounterDialogueTokensAndColor(this.scene, dialogue.speaker); } this.scene.ui.setMode(Mode.MESSAGE); diff --git a/src/test/mystery-encounter/mystery-encounter-utils.test.ts b/src/test/mystery-encounter/mystery-encounter-utils.test.ts index 6f1bccd359c..63cbd83d975 100644 --- a/src/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/src/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -3,7 +3,7 @@ import GameManager from "#app/test/utils/gameManager"; import Phaser from "phaser"; import { getHighestLevelPlayerPokemon, getLowestLevelPlayerPokemon, - getRandomPlayerPokemon, getRandomSpeciesByStarterTier, getTextWithEncounterDialogueTokens, + getRandomPlayerPokemon, getRandomSpeciesByStarterTier, getTextWithEncounterDialogueTokensAndColor, koPlayerPokemon, queueEncounterMessage, showEncounterDialogue, showEncounterText, } from "#app/data/mystery-encounters/mystery-encounter-utils"; import {initSceneWithoutEncounterPhase} from "#test/utils/gameManagerUtils"; @@ -272,12 +272,12 @@ describe("Mystery Encounter Utils", () => { }); describe("getTextWithEncounterDialogueTokens", () => { - it("injects dialogue tokens", () => { + it("injects dialogue tokens and color styling", () => { scene.currentBattle.mysteryEncounter = new MysteryEncounter(null); scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); - const result = getTextWithEncounterDialogueTokens(scene, "mysteryEncounter:unit_test_dialogue"); - expect(result).toEqual("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}"); + const result = getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:unit_test_dialogue"); + expect(result).toEqual("[color=#f8f8f8][shadow=#6b5a73]valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]"); }); it("can perform nested dialogue token injection", () => { @@ -285,8 +285,8 @@ describe("Mystery Encounter Utils", () => { scene.currentBattle.mysteryEncounter.setDialogueToken("test", "value"); scene.currentBattle.mysteryEncounter.setDialogueToken("testvalue", "new"); - const result = getTextWithEncounterDialogueTokens(scene, "mysteryEncounter:unit_test_dialogue"); - expect(result).toEqual("valuevalue new @ec{test1} value @ec{test\\} @ec{test\\} {test}"); + const result = getTextWithEncounterDialogueTokensAndColor(scene, "mysteryEncounter:unit_test_dialogue"); + expect(result).toEqual("[color=#f8f8f8][shadow=#6b5a73]valuevalue new @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]"); }); }); @@ -298,7 +298,7 @@ describe("Mystery Encounter Utils", () => { const phaseSpy = vi.spyOn(game.scene, "unshiftPhase"); queueEncounterMessage(scene, "mysteryEncounter:unit_test_dialogue"); - expect(spy).toHaveBeenCalledWith("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", null, true); + expect(spy).toHaveBeenCalledWith("[color=#f8f8f8][shadow=#6b5a73]valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]", null, true); expect(phaseSpy).toHaveBeenCalledWith(expect.any(MessagePhase)); }); }); @@ -310,7 +310,7 @@ describe("Mystery Encounter Utils", () => { const spy = vi.spyOn(game.scene.ui, "showText"); showEncounterText(scene, "mysteryEncounter:unit_test_dialogue"); - expect(spy).toHaveBeenCalledWith("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", null, expect.any(Function), 0, true); + expect(spy).toHaveBeenCalledWith("[color=#f8f8f8][shadow=#6b5a73]valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]", null, expect.any(Function), 0, true); }); }); @@ -321,7 +321,7 @@ describe("Mystery Encounter Utils", () => { const spy = vi.spyOn(game.scene.ui, "showDialogue"); showEncounterDialogue(scene, "mysteryEncounter:unit_test_dialogue", "mysteryEncounter:unit_test_dialogue"); - expect(spy).toHaveBeenCalledWith("valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", "valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}", null, undefined, 0, 0); + expect(spy).toHaveBeenCalledWith("[color=#f8f8f8][shadow=#6b5a73]valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]", "[color=#f8f8f8][shadow=#6b5a73]valuevalue @ec{testvalue} @ec{test1} value @ec{test\\} @ec{test\\} {test}[/color][/shadow]", null, undefined, 0, 0); }); }); diff --git a/src/test/phases/mystery-encounter-phase.test.ts b/src/test/phases/mystery-encounter-phase.test.ts index 8d8bac79b5a..4374fc60bd6 100644 --- a/src/test/phases/mystery-encounter-phase.test.ts +++ b/src/test/phases/mystery-encounter-phase.test.ts @@ -84,7 +84,7 @@ describe("Mystery Encounter Phases", () => { expect(messageSpy).toHaveBeenCalledTimes(2); expect(dialogueSpy).toHaveBeenCalledWith("What's this?", "???", null, expect.any(Function)); expect(messageSpy).toHaveBeenCalledWith("Mysterious challengers have appeared!", null, expect.any(Function), 750, true); - expect(messageSpy).toHaveBeenCalledWith("The trainer steps forward...", null, expect.any(Function), 750, true); + expect(messageSpy).toHaveBeenCalledWith("[color=#f8f8f8][shadow=#6b5a73]The trainer steps forward...[/color][/shadow]", null, expect.any(Function), 750, true); }); }); diff --git a/src/ui/mystery-encounter-ui-handler.ts b/src/ui/mystery-encounter-ui-handler.ts index ce49a9f280d..862fa155bf4 100644 --- a/src/ui/mystery-encounter-ui-handler.ts +++ b/src/ui/mystery-encounter-ui-handler.ts @@ -1,16 +1,16 @@ import BattleScene from "../battle-scene"; -import { addTextObject, TextStyle } from "./text"; -import { Mode } from "./ui"; +import {addBBCodeTextObject, getBBCodeFrag, TextStyle} from "./text"; +import {Mode} from "./ui"; import UiHandler from "./ui-handler"; -import { Button } from "#enums/buttons"; -import { addWindow, WindowVariant } from "./ui-theme"; -import i18next from "i18next"; -import { MysteryEncounterPhase } from "../phases/mystery-encounter-phase"; -import { PartyUiMode } from "./party-ui-handler"; +import {Button} from "#enums/buttons"; +import {addWindow, WindowVariant} from "./ui-theme"; +import {MysteryEncounterPhase} from "../phases/mystery-encounter-phase"; +import {PartyUiMode} from "./party-ui-handler"; import MysteryEncounterOption from "../data/mystery-encounter-option"; import * as Utils from "../utils"; -import { getPokeballAtlasKey } from "../data/pokeball"; import {isNullOrUndefined} from "../utils"; +import {getPokeballAtlasKey} from "../data/pokeball"; +import {getTextWithEncounterDialogueTokensAndColor} from "#app/data/mystery-encounters/mystery-encounter-utils"; export default class MysteryEncounterUiHandler extends UiHandler { private cursorContainer: Phaser.GameObjects.Container; @@ -298,9 +298,9 @@ export default class MysteryEncounterUiHandler extends UiHandler { this.filteredEncounterOptions = mysteryEncounter.options; this.optionsMeetsReqs = []; - const titleText: string = i18next.t(mysteryEncounter.dialogue.encounterOptionsDialogue.title); - const descriptionText: string = i18next.t(mysteryEncounter.dialogue.encounterOptionsDialogue.description); - const queryText: string = i18next.t(mysteryEncounter.dialogue.encounterOptionsDialogue.query); + const titleText: string = getTextWithEncounterDialogueTokensAndColor(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.title, TextStyle.TOOLTIP_TITLE); + const descriptionText: string = getTextWithEncounterDialogueTokensAndColor(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.description, TextStyle.TOOLTIP_CONTENT); + const queryText: string = getTextWithEncounterDialogueTokensAndColor(this.scene, mysteryEncounter.dialogue.encounterOptionsDialogue.query, TextStyle.TOOLTIP_CONTENT); // Clear options container (except cursor) this.optionsContainer.removeAll(); @@ -310,16 +310,17 @@ export default class MysteryEncounterUiHandler extends UiHandler { let optionText; switch (this.filteredEncounterOptions.length) { case 2: - optionText = addTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, 8, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); break; case 3: - optionText = addTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); break; case 4: - optionText = addTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); + optionText = addBBCodeTextObject(this.scene, i % 2 === 0 ? 0 : 100, i < 2 ? 0 : 16, "-", TextStyle.WINDOW, { wordWrap: { width: 558 }, fontSize: "80px", lineSpacing: -8 }); break; } - const text = i18next.t(mysteryEncounter.dialogue.encounterOptionsDialogue.options[i].buttonLabel); + const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[i]; + const text = getTextWithEncounterDialogueTokensAndColor(this.scene, option.buttonLabel, option.style ? option.style : TextStyle.WINDOW); if (text) { optionText.setText(text); } @@ -336,11 +337,11 @@ export default class MysteryEncounterUiHandler extends UiHandler { } // View Party Button - const viewPartyText = addTextObject(this.scene, 256, -24, "View Party", TextStyle.PARTY); + const viewPartyText = addBBCodeTextObject(this.scene, 256, -24, getBBCodeFrag("View Party", TextStyle.PARTY), TextStyle.PARTY); this.optionsContainer.add(viewPartyText); // Description Window - const titleTextObject = addTextObject(this.scene, 0, 0, titleText, TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 }); + const titleTextObject = addBBCodeTextObject(this.scene, 0, 0, titleText, TextStyle.TOOLTIP_TITLE, { wordWrap: { width: 750 }, align: "center", lineSpacing: -8 }); this.descriptionContainer.add(titleTextObject); titleTextObject.setPosition(72 - titleTextObject.displayWidth / 2, 5.5); @@ -348,7 +349,7 @@ export default class MysteryEncounterUiHandler extends UiHandler { const ballType = getPokeballAtlasKey(mysteryEncounter.encounterTier as number); this.rarityBall.setTexture("pb", ballType); - const descriptionTextObject = addTextObject(this.scene, 6, 25, descriptionText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } }); + const descriptionTextObject = addBBCodeTextObject(this.scene, 6, 25, descriptionText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } }); // Sets up the mask that hides the description text to give an illusion of scrolling const descriptionTextMaskRect = this.scene.make.graphics({}); @@ -382,8 +383,9 @@ export default class MysteryEncounterUiHandler extends UiHandler { this.descriptionContainer.add(descriptionTextObject); - const queryTextObject = addTextObject(this.scene, 65 - (queryText.length), 90, queryText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } }); + const queryTextObject = addBBCodeTextObject(this.scene, 0, 0, queryText, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 830 } }); this.descriptionContainer.add(queryTextObject); + queryTextObject.setPosition(75 - queryTextObject.displayWidth / 2, 90); // Slide in description container if (slideInDescription) { @@ -412,15 +414,20 @@ export default class MysteryEncounterUiHandler extends UiHandler { const mysteryEncounter = this.scene.currentBattle.mysteryEncounter; let text; - if (!this.optionsMeetsReqs[cursor] && mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor].disabledTooltip) { - text = i18next.t(mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor].disabledTooltip); + const option = mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor]; + if (!this.optionsMeetsReqs[cursor] && option.disabledTooltip) { + text = getTextWithEncounterDialogueTokensAndColor(this.scene, option.disabledTooltip, TextStyle.TOOLTIP_CONTENT); } else { - text = i18next.t(mysteryEncounter.dialogue.encounterOptionsDialogue.options[cursor].buttonTooltip); + text = getTextWithEncounterDialogueTokensAndColor(this.scene, option.buttonTooltip, TextStyle.TOOLTIP_CONTENT); } + // Auto-color options green/blue for good/bad by looking for (+)/(-) + const primaryStyleString = [...text.match(new RegExp(/\[color=[^\[]*\]\[shadow=[^\[]*\]/i))][0]; + text = text.replace(/(\([^\(]*\+\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_GREEN) + "[/color][/shadow]" + primaryStyleString); + text = text.replace(/(\([^\(]*\-\)[^\(\[]*)/gi, substring => "[/color][/shadow]" + getBBCodeFrag(substring, TextStyle.SUMMARY_BLUE) + "[/color][/shadow]" + primaryStyleString); if (text) { - const tooltipTextObject = addTextObject(this.scene, 6, 7, text, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 600 }, fontSize: "72px" }); + const tooltipTextObject = addBBCodeTextObject(this.scene, 6, 7, text, TextStyle.TOOLTIP_CONTENT, { wordWrap: { width: 600 }, fontSize: "72px" }); this.tooltipContainer.add(tooltipTextObject); // Sets up the mask that hides the description text to give an illusion of scrolling