mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-25 00:39:27 +02:00
Merge branch 'beta' into qol/i18n-lazy-loading
This commit is contained in:
commit
bf2f4f87c2
150
package-lock.json
generated
150
package-lock.json
generated
@ -1512,224 +1512,208 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
|
||||
"integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
|
||||
"integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
|
||||
"integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
|
||||
"integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
|
||||
"integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
|
||||
"integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
|
||||
"integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
|
||||
"integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
|
||||
"integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
|
||||
"integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
|
||||
"integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
|
||||
"integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
|
||||
"integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
|
||||
"integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
|
||||
"integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
|
||||
"integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
|
||||
"integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
|
||||
"integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
|
||||
"integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
|
||||
"integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
|
||||
"integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
|
||||
"integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
|
||||
"integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
|
||||
"integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
|
||||
"integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
|
||||
"integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
@ -5448,9 +5432,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
|
||||
"integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
|
||||
"integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
@ -5463,22 +5447,22 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.18.1",
|
||||
"@rollup/rollup-android-arm64": "4.18.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.18.1",
|
||||
"@rollup/rollup-darwin-x64": "4.18.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.18.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.18.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.18.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.18.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.18.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.18.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.18.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.22.4",
|
||||
"@rollup/rollup-android-arm64": "4.22.4",
|
||||
"@rollup/rollup-darwin-arm64": "4.22.4",
|
||||
"@rollup/rollup-darwin-x64": "4.22.4",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.22.4",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.22.4",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.22.4",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.22.4",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.22.4",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.22.4",
|
||||
"@rollup/rollup-linux-x64-musl": "4.22.4",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.22.4",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.22.4",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.22.4",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
|
@ -71,6 +71,7 @@
|
||||
"disabledOnAdd": " {{moveName}} von {{pokemonNameWithAffix}} wurde blockiert!",
|
||||
"disabledLapse": "{{moveName}} von {{pokemonNameWithAffix}} ist nicht länger blockiert!",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}} ist nun schwach gegenüber Feuer-Attacken!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}} wirft seinen Schwanz ab, um eine Ablenkung zu schaffen!",
|
||||
"substituteOnAdd": "Ein Delegator von {{pokemonNameWithAffix}} ist erschienen!",
|
||||
"substituteOnHit": "Der Delegator steckt den Schlag für {{pokemonNameWithAffix}} ein!",
|
||||
"substituteOnRemove": "Der Delegator von {{pokemonNameWithAffix}} hört auf zu wirken!"
|
||||
|
@ -71,8 +71,12 @@
|
||||
"disabledOnAdd": "{{pokemonNameWithAffix}}'s {{moveName}}\nwas disabled!",
|
||||
"disabledLapse": "{{pokemonNameWithAffix}}'s {{moveName}}\nis no longer disabled.",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}} became weaker to fire!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}} shed its tail to create a decoy!",
|
||||
"substituteOnAdd": "{{pokemonNameWithAffix}} put in a substitute!",
|
||||
"substituteOnHit": "The substitute took damage for {{pokemonNameWithAffix}}!",
|
||||
"substituteOnRemove": "{{pokemonNameWithAffix}}'s substitute faded!",
|
||||
"tormentOnAdd": "{{pokemonNameWithAffix}} was subjected to torment!",
|
||||
"tauntOnAdd": "{{pokemonNameWithAffix}} fell for the taunt!",
|
||||
"imprisonOnAdd": "{{pokemonNameWithAffix}} sealed the opponents move(s)!",
|
||||
"autotomizeOnAdd": "{{pokemonNameWithAffix}} became nimble!"
|
||||
}
|
||||
|
@ -71,6 +71,7 @@
|
||||
"disabledOnAdd": "¡Se ha anulado el movimiento {{moveName}}\nde {{pokemonNameWithAffix}}!",
|
||||
"disabledLapse": "¡El movimiento {{moveName}} de {{pokemonNameWithAffix}} ya no está anulado!",
|
||||
"tarShotOnAdd": "¡{{pokemonNameWithAffix}} se ha vuelto débil ante el fuego!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}} se desprende\nde un segmento de su cuerpo y lo usa comoseñuelo!",
|
||||
"substituteOnAdd": "¡{{pokemonNameWithAffix}} creó un sustituto!",
|
||||
"substituteOnHit": "¡El sustituto recibe daño en lugar del {{pokemonNameWithAffix}}!",
|
||||
"substituteOnRemove": "¡El sustituto del {{pokemonNameWithAffix}} se debilitó!"
|
||||
|
@ -71,6 +71,7 @@
|
||||
"disabledOnAdd": "La capacité {{moveName}}\nde {{pokemonNameWithAffix}} est mise sous entrave !",
|
||||
"disabledLapse": "La capacité {{moveName}}\nde {{pokemonNameWithAffix}} n’est plus sous entrave !",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}} est maintenant\nvulnérable au feu !",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}} détache\nsa queue pour créer un leurre !",
|
||||
"substituteOnAdd": "{{pokemonNameWithAffix}}\ncrée un clone !",
|
||||
"substituteOnHit": "Le clone subit les dégâts à la place\nde {{pokemonNameWithAffix}} !",
|
||||
"substituteOnRemove": "Le clone de {{pokemonNameWithAffix}}\ndisparait…"
|
||||
|
@ -71,6 +71,7 @@
|
||||
"disabledOnAdd": "La mossa {{moveName}} di\n{{pokemonNameWithAffix}} è stata bloccata!",
|
||||
"disabledLapse": "La mossa {{moveName}} di\n{{pokemonNameWithAffix}} non è più bloccata!",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}} è diventato vulnerabile\nal tipo Fuoco!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}} si taglia\nla coda e ne fa un sostituto!",
|
||||
"substituteOnAdd": "Appare un sostituto di {{pokemonNameWithAffix}}!",
|
||||
"substituteOnHit": "Il sostituto viene colpito al posto di {{pokemonNameWithAffix}}!",
|
||||
"substituteOnRemove": "Il sostituto di {{pokemonNameWithAffix}} svanisce!"
|
||||
|
@ -70,5 +70,6 @@
|
||||
"stockpilingOnAdd": "{{pokemonNameWithAffix}}は {{stockpiledCount}}つ たくわえた!",
|
||||
"disabledOnAdd": "{{pokemonNameWithAffix}}の\n{{moveName}}\nを 封じこめた!",
|
||||
"disabledLapse": "{{pokemonNameWithAffix}}の\nかなしばりが 解けた!",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}}は ほのおに 弱くなった!"
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}}は ほのおに 弱くなった!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}}は\nしっぽを 切って みがわりにした!"
|
||||
}
|
||||
|
@ -71,6 +71,7 @@
|
||||
"disabledOnAdd": "{{pokemonNameWithAffix}}의 {{moveName}}[[는]]\n사용할 수 없다!",
|
||||
"disabledLapse": "{{pokemonNameWithAffix}}의 {{moveName}}[[는]]\n이제 사용할 수 있다.",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}}[[는]] 불꽃에 약해졌다!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}}[[는]] 꼬리를 잘라 대타로 삼았다!",
|
||||
"substituteOnAdd": "{{pokemonNameWithAffix}}의\n대타가 나타났다!",
|
||||
"substituteOnHit": "{{pokemonNameWithAffix}}[[를]] 대신하여\n대타가 공격을 받았다!",
|
||||
"substituteOnRemove": "{{pokemonNameWithAffix}}의\n대타는 사라져 버렸다..."
|
||||
|
@ -71,6 +71,7 @@
|
||||
"disabledOnAdd": "封住了{{pokemonNameWithAffix}}的\n{{moveName}}!",
|
||||
"disabledLapse": "{{pokemonNameWithAffix}}的\n定身法解除了!",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}}\n变得怕火了!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}}\n断掉尾巴并将其作为替身了!",
|
||||
"substituteOnAdd": "{{pokemonNameWithAffix}}的\n替身出现了!",
|
||||
"substituteOnHit": "替身代替{{pokemonNameWithAffix}}\n承受了攻击!",
|
||||
"substituteOnRemove": "{{pokemonNameWithAffix}}的\n替身消失了……"
|
||||
|
@ -71,6 +71,7 @@
|
||||
"disabledOnAdd": "封住了{{pokemonNameWithAffix}}的\n{moveName}}!",
|
||||
"disabledLapse": "{{pokemonNameWithAffix}}的\n定身法解除了!",
|
||||
"tarShotOnAdd": "{{pokemonNameWithAffix}}\n變得怕火了!",
|
||||
"shedTailOnAdd": "{{pokemonNameWithAffix}}\n截斷尾巴,把它做成了替身!",
|
||||
"substituteOnAdd": "{{pokemonNameWithAffix}}的\n替身出現了!",
|
||||
"substituteOnHit": "替身代替{{pokemonNameWithAffix}}承受了攻擊!",
|
||||
"substituteOnRemove": "{{pokemonNameWithAffix}}的\n替身消失了……"
|
||||
|
@ -2026,6 +2026,7 @@ export class PostSummonAbAttr extends AbAttr {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified arena tags when a Pokemon is summoned.
|
||||
*/
|
||||
@ -2852,17 +2853,17 @@ export class PreApplyBattlerTagAbAttr extends AbAttr {
|
||||
* Provides immunity to BattlerTags {@linkcode BattlerTag} to specified targets.
|
||||
*/
|
||||
export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
|
||||
private immuneTagType: BattlerTagType;
|
||||
private immuneTagTypes: BattlerTagType[];
|
||||
private battlerTag: BattlerTag;
|
||||
|
||||
constructor(immuneTagType: BattlerTagType) {
|
||||
constructor(immuneTagTypes: BattlerTagType | BattlerTagType[]) {
|
||||
super();
|
||||
|
||||
this.immuneTagType = immuneTagType;
|
||||
this.immuneTagTypes = Array.isArray(immuneTagTypes) ? immuneTagTypes : [immuneTagTypes];
|
||||
}
|
||||
|
||||
applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, simulated: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (tag.tagType === this.immuneTagType) {
|
||||
if (this.immuneTagTypes.includes(tag.tagType)) {
|
||||
cancelled.value = true;
|
||||
if (!simulated) {
|
||||
this.battlerTag = tag;
|
||||
@ -4916,7 +4917,7 @@ export function initAbilities() {
|
||||
.attr(TypeImmunityHealAbAttr, Type.WATER)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.OBLIVIOUS, 3)
|
||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.INFATUATED)
|
||||
.attr(BattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT])
|
||||
.attr(IntimidateImmunityAbAttr)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.CLOUD_NINE, 3)
|
||||
@ -5402,8 +5403,7 @@ export function initAbilities() {
|
||||
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonTeravolt", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
|
||||
.attr(MoveAbilityBypassAbAttr),
|
||||
new Ability(Abilities.AROMA_VEIL, 6)
|
||||
.ignorable()
|
||||
.unimplemented(),
|
||||
.attr(UserFieldBattlerTagImmunityAbAttr, [BattlerTagType.INFATUATED, BattlerTagType.TAUNT, BattlerTagType.DISABLED, BattlerTagType.TORMENT, BattlerTagType.HEAL_BLOCK]),
|
||||
new Ability(Abilities.FLOWER_VEIL, 6)
|
||||
.ignorable()
|
||||
.unimplemented(),
|
||||
@ -5885,7 +5885,6 @@ export function initAbilities() {
|
||||
.ignorable(),
|
||||
new Ability(Abilities.EARTH_EATER, 9)
|
||||
.attr(TypeImmunityHealAbAttr, Type.GROUND)
|
||||
.partial() // Healing not blocked by Heal Block
|
||||
.ignorable(),
|
||||
new Ability(Abilities.MYCELIUM_MIGHT, 9)
|
||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.2)
|
||||
@ -5900,8 +5899,7 @@ export function initAbilities() {
|
||||
.attr(PostSummonStatStageChangeAbAttr, [ Stat.EVA ], -1)
|
||||
.condition(getOncePerBattleCondition(Abilities.SUPERSWEET_SYRUP)),
|
||||
new Ability(Abilities.HOSPITALITY, 9)
|
||||
.attr(PostSummonAllyHealAbAttr, 4, true)
|
||||
.partial(), // Healing not blocked by Heal Block
|
||||
.attr(PostSummonAllyHealAbAttr, 4, true),
|
||||
new Ability(Abilities.TOXIC_CHAIN, 9)
|
||||
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
|
||||
new Ability(Abilities.EMBODY_ASPECT_TEAL, 9)
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { Arena } from "../field/arena";
|
||||
import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move";
|
||||
import { getPokemonNameWithAffix } from "../messages";
|
||||
import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
|
||||
import { StatusEffect } from "./status-effect";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
|
||||
import { Arena } from "#app/field/arena";
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { Type } from "#app/data/type";
|
||||
import * as Utils from "#app/utils";
|
||||
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "#app/data/move";
|
||||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import Pokemon, { HitResult, PlayerPokemon, PokemonMove, EnemyPokemon } from "#app/field/pokemon";
|
||||
import { StatusEffect } from "#app/data/status-effect";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
|
||||
import { Stat } from "#enums/stat";
|
||||
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
|
||||
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
|
||||
import i18next from "i18next";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
@ -919,6 +920,77 @@ class SafeguardTag extends ArenaTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This arena tag facilitates the application of the move Imprison
|
||||
* Imprison remains in effect as long as the source Pokemon is active and present on the field.
|
||||
* Imprison will apply to any opposing Pokemon that switch onto the field as well.
|
||||
*/
|
||||
class ImprisonTag extends ArenaTrapTag {
|
||||
private source: Pokemon;
|
||||
|
||||
constructor(sourceId: number, side: ArenaTagSide) {
|
||||
super(ArenaTagType.IMPRISON, Moves.IMPRISON, sourceId, side, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that retrieves the Pokemon effected
|
||||
* @param {BattleScene} scene medium to retrieve the involved Pokemon
|
||||
* @returns list of PlayerPokemon or EnemyPokemon on the field
|
||||
*/
|
||||
private retrieveField(scene: BattleScene): PlayerPokemon[] | EnemyPokemon[] {
|
||||
if (!this.source.isPlayer()) {
|
||||
return scene.getPlayerField() ?? [];
|
||||
}
|
||||
return scene.getEnemyField() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function applies the effects of Imprison to the opposing Pokemon already present on the field.
|
||||
* @param arena
|
||||
*/
|
||||
override onAdd({ scene }: Arena) {
|
||||
this.source = scene.getPokemonById(this.sourceId!)!;
|
||||
if (this.source) {
|
||||
const party = this.retrieveField(scene);
|
||||
party?.forEach((p: PlayerPokemon | EnemyPokemon ) => {
|
||||
p.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
|
||||
});
|
||||
scene.queueMessage(i18next.t("battlerTags:imprisonOnAdd", {pokemonNameWithAffix: getPokemonNameWithAffix(this.source)}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the source Pokemon is still active on the field
|
||||
* @param _arena
|
||||
* @returns `true` if the source of the tag is still active on the field | `false` if not
|
||||
*/
|
||||
override lapse(_arena: Arena): boolean {
|
||||
return this.source.isActive(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies the effects of Imprison to any opposing Pokemon that switch into the field while the source Pokemon is still active
|
||||
* @param {Pokemon} pokemon the Pokemon Imprison is applied to
|
||||
* @returns `true`
|
||||
*/
|
||||
override activateTrap(pokemon: Pokemon): boolean {
|
||||
if (this.source.isActive(true)) {
|
||||
pokemon.addTag(BattlerTagType.IMPRISON, 1, Moves.IMPRISON, this.sourceId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the arena tag is removed, it also attempts to remove any related Battler Tags if they haven't already been removed from the affected Pokemon
|
||||
* @param arena
|
||||
*/
|
||||
override onRemove({ scene }: Arena): void {
|
||||
const party = this.retrieveField(scene);
|
||||
party?.forEach((p: PlayerPokemon | EnemyPokemon) => {
|
||||
p.removeTag(BattlerTagType.IMPRISON);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
||||
switch (tagType) {
|
||||
@ -967,6 +1039,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
|
||||
return new HappyHourTag(turnCount, sourceId, side);
|
||||
case ArenaTagType.SAFEGUARD:
|
||||
return new SafeguardTag(turnCount, sourceId, side);
|
||||
case ArenaTagType.IMPRISON:
|
||||
return new ImprisonTag(sourceId, side);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { getPokemonNameWithAffix } from "../messages";
|
||||
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
|
||||
import { StatusEffect } from "./status-effect";
|
||||
import * as Utils from "../utils";
|
||||
import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr } from "./move";
|
||||
import { ChargeAttr, MoveFlags, allMoves, MoveCategory, applyMoveAttrs, StatusCategoryOnAllyAttr, HealOnAllyAttr, ConsecutiveUseDoublePowerAttr } from "./move";
|
||||
import { Type } from "./type";
|
||||
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs, ProtectStatAbAttr } from "./ability";
|
||||
import { TerrainType } from "./terrain";
|
||||
@ -2311,6 +2311,11 @@ export class AutotomizedTag extends BattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag implementing the {@link https://bulbapedia.bulbagarden.net/wiki/Substitute_(doll)#Effect | Substitute Doll} effect,
|
||||
* for use with the moves Substitute and Shed Tail. Pokemon with this tag deflect most forms of received attack damage
|
||||
* onto the tag. This tag also grants immunity to most Status moves and several move effects.
|
||||
*/
|
||||
export class SubstituteTag extends BattlerTag {
|
||||
/** The substitute's remaining HP. If HP is depleted, the Substitute fades. */
|
||||
public hp: number;
|
||||
@ -2330,7 +2335,11 @@ export class SubstituteTag extends BattlerTag {
|
||||
|
||||
// Queue battle animation and message
|
||||
pokemon.scene.triggerPokemonBattleAnim(pokemon, PokemonAnimType.SUBSTITUTE_ADD);
|
||||
if (this.sourceMove === Moves.SHED_TAIL) {
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:shedTailOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500);
|
||||
} else {
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:substituteOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500);
|
||||
}
|
||||
|
||||
// Remove any binding effects from the user
|
||||
pokemon.findAndRemoveTags(tag => tag instanceof DamagingTrapTag);
|
||||
@ -2437,6 +2446,150 @@ export class MysteryEncounterPostSummonTag extends BattlerTag {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Battle Tag that applies the move Torment to the target Pokemon
|
||||
* Torment restricts the use of moves twice in a row.
|
||||
* The tag is only removed if the target leaves the battle.
|
||||
* Torment does not interrupt the move if the move is performed consecutively in the same turn and right after Torment is applied
|
||||
*/
|
||||
export class TormentTag extends MoveRestrictionBattlerTag {
|
||||
private target: Pokemon;
|
||||
|
||||
constructor(sourceId: number) {
|
||||
super(BattlerTagType.TORMENT, BattlerTagLapseType.AFTER_MOVE, 1, Moves.TORMENT, sourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the battler tag to the target Pokemon and defines the private class variable 'target'
|
||||
* 'Target' is used to track the Pokemon's current status
|
||||
* @param {Pokemon} pokemon the Pokemon tormented
|
||||
*/
|
||||
override onAdd(pokemon: Pokemon) {
|
||||
super.onAdd(pokemon);
|
||||
this.target = pokemon;
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:tormentOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Torment only ends when the affected Pokemon leaves the battle field
|
||||
* @param {Pokemon} pokemon the Pokemon under the effects of Torment
|
||||
* @param _tagType
|
||||
* @returns `true` if still present | `false` if not
|
||||
*/
|
||||
override lapse(pokemon: Pokemon, _tagType: BattlerTagLapseType): boolean {
|
||||
return !pokemon.isActive(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks if the current move used is identical to the last used move with a {@linkcode MoveResult} of `SUCCESS`/`MISS`
|
||||
* @param {Moves} move the move under investigation
|
||||
* @returns `true` if there is valid consecutive usage | `false` if the moves are different from each other
|
||||
*/
|
||||
override isMoveRestricted(move: Moves): boolean {
|
||||
const lastMove = this.target.getLastXMoves(1)[0];
|
||||
if ( !lastMove ) {
|
||||
return false;
|
||||
}
|
||||
// This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY
|
||||
// Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts
|
||||
const moveObj = allMoves[lastMove.move];
|
||||
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || this.target.getTag(BattlerTagType.FRENZY) || moveObj.hasAttr(ChargeAttr);
|
||||
const validLastMoveResult = (lastMove.result === MoveResult.SUCCESS) || (lastMove.result === MoveResult.MISS);
|
||||
if (lastMove.move === move && validLastMoveResult && lastMove.move !== Moves.STRUGGLE && !isUnaffected) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override selectionDeniedText(_pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BattlerTag that applies the effects of Taunt to the target Pokemon
|
||||
* Taunt restricts the use of status moves.
|
||||
* The tag is removed after 4 turns.
|
||||
*/
|
||||
export class TauntTag extends MoveRestrictionBattlerTag {
|
||||
constructor() {
|
||||
super(BattlerTagType.TAUNT, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 4, Moves.TAUNT);
|
||||
}
|
||||
|
||||
override onAdd(pokemon: Pokemon) {
|
||||
super.onAdd(pokemon);
|
||||
pokemon.scene.queueMessage(i18next.t("battlerTags:tauntOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a move is a status move and determines its restriction status on that basis
|
||||
* @param {Moves} move the move under investigation
|
||||
* @returns `true` if the move is a status move
|
||||
*/
|
||||
override isMoveRestricted(move: Moves): boolean {
|
||||
return allMoves[move].category === MoveCategory.STATUS;
|
||||
}
|
||||
|
||||
override selectionDeniedText(_pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name });
|
||||
}
|
||||
|
||||
override interruptedText(pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BattlerTag that applies the effects of Imprison to the target Pokemon
|
||||
* Imprison restricts the opposing side's usage of moves shared by the source-user of Imprison.
|
||||
* The tag is only removed when the source-user is removed from the field.
|
||||
*/
|
||||
export class ImprisonTag extends MoveRestrictionBattlerTag {
|
||||
private source: Pokemon | null;
|
||||
|
||||
constructor(sourceId: number) {
|
||||
super(BattlerTagType.IMPRISON, [BattlerTagLapseType.PRE_MOVE, BattlerTagLapseType.AFTER_MOVE], 1, Moves.IMPRISON, sourceId);
|
||||
}
|
||||
|
||||
override onAdd(pokemon: Pokemon) {
|
||||
if (this.sourceId) {
|
||||
this.source = pokemon.scene.getPokemonById(this.sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the source of Imprison is still active
|
||||
* @param _pokemon
|
||||
* @param _lapseType
|
||||
* @returns `true` if the source is still active
|
||||
*/
|
||||
override lapse(_pokemon: Pokemon, _lapseType: BattlerTagLapseType): boolean {
|
||||
return this.source?.isActive(true) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the source of the tag has the parameter move in its moveset and that the source is still active
|
||||
* @param {Moves} move the move under investigation
|
||||
* @returns `false` if either condition is not met
|
||||
*/
|
||||
override isMoveRestricted(move: Moves): boolean {
|
||||
if (this.source) {
|
||||
const sourceMoveset = this.source.getMoveset().map(m => m!.moveId);
|
||||
return sourceMoveset?.includes(move) && this.source.isActive(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override selectionDeniedText(_pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:moveCannotBeSelected", { moveName: allMoves[move].name });
|
||||
}
|
||||
|
||||
override interruptedText(pokemon: Pokemon, move: Moves): string {
|
||||
return i18next.t("battle:disableInterruptedMove", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), moveName: allMoves[move].name });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a {@linkcode BattlerTag} based on the provided tag type, turn count, source move, and source ID.
|
||||
*
|
||||
@ -2604,6 +2757,12 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
|
||||
return new MysteryEncounterPostSummonTag();
|
||||
case BattlerTagType.HEAL_BLOCK:
|
||||
return new HealBlockTag(turnCount, sourceMove);
|
||||
case BattlerTagType.TORMENT:
|
||||
return new TormentTag(sourceId);
|
||||
case BattlerTagType.TAUNT:
|
||||
return new TauntTag();
|
||||
case BattlerTagType.IMPRISON:
|
||||
return new ImprisonTag(sourceId);
|
||||
case BattlerTagType.NONE:
|
||||
default:
|
||||
return new BattlerTag(tagType, BattlerTagLapseType.CUSTOM, turnCount, sourceMove, sourceId);
|
||||
|
@ -37,6 +37,7 @@ import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
||||
import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms";
|
||||
import { GameMode } from "#app/game-mode";
|
||||
import { applyChallenges, ChallengeType } from "./challenge";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
export enum MoveCategory {
|
||||
PHYSICAL,
|
||||
@ -1476,8 +1477,13 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
|
||||
* @see {@linkcode apply}
|
||||
*/
|
||||
export class AddSubstituteAttr extends MoveEffectAttr {
|
||||
constructor() {
|
||||
/** The ratio of the user's max HP that is required to apply this effect */
|
||||
private hpCost: number;
|
||||
|
||||
constructor(hpCost: number = 0.25) {
|
||||
super(true);
|
||||
|
||||
this.hpCost = hpCost;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1493,8 +1499,7 @@ export class AddSubstituteAttr extends MoveEffectAttr {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hpCost = Math.floor(user.getMaxHp() / 4);
|
||||
user.damageAndUpdate(hpCost, HitResult.OTHER, false, true, true);
|
||||
user.damageAndUpdate(Math.floor(user.getMaxHp() * this.hpCost), HitResult.OTHER, false, true, true);
|
||||
user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id);
|
||||
return true;
|
||||
}
|
||||
@ -1507,7 +1512,7 @@ export class AddSubstituteAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > Math.floor(user.getMaxHp() / 4) && user.getMaxHp() > 1;
|
||||
return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > Math.floor(user.getMaxHp() * this.hpCost) && user.getMaxHp() > 1;
|
||||
}
|
||||
|
||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
||||
@ -5151,9 +5156,9 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
if (user.scene.currentBattle.double && user.scene.getEnemyParty().length > 1) {
|
||||
const allyPokemon = user.getAlly();
|
||||
if (slotIndex<=1) {
|
||||
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, pokemon.getFieldIndex(), slotIndex, false, false, false));
|
||||
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false));
|
||||
} else if (allyPokemon.isFainted()) {
|
||||
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, allyPokemon.getFieldIndex(), slotIndex, false, false, false));
|
||||
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false));
|
||||
}
|
||||
}
|
||||
resolve(true);
|
||||
@ -5176,45 +5181,43 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
||||
export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
constructor(
|
||||
private selfSwitch: boolean = false,
|
||||
private batonPass: boolean = false
|
||||
private switchType: SwitchType = SwitchType.SWITCH
|
||||
) {
|
||||
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
||||
}
|
||||
|
||||
isBatonPass() {
|
||||
return this.batonPass;
|
||||
return this.switchType === SwitchType.BATON_PASS;
|
||||
}
|
||||
|
||||
// TODO: Why is this a Promise?
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
// Check if the move category is not STATUS or if the switch out condition is not met
|
||||
if (!this.getSwitchOutCondition()(user, target, move)) {
|
||||
return resolve(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move the switch out logic inside the conditional block
|
||||
// This ensures that the switch out only happens when the conditions are met
|
||||
/**
|
||||
* Move the switch out logic inside the conditional block
|
||||
* This ensures that the switch out only happens when the conditions are met
|
||||
*/
|
||||
const switchOutTarget = this.selfSwitch ? user : target;
|
||||
if (switchOutTarget instanceof PlayerPokemon) {
|
||||
switchOutTarget.leaveField(!this.batonPass);
|
||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||
|
||||
if (switchOutTarget.hp > 0) {
|
||||
user.scene.prependToPhase(new SwitchPhase(user.scene, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase);
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
user.scene.prependToPhase(new SwitchPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase);
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
||||
// Switch out logic for trainer battles
|
||||
switchOutTarget.leaveField(!this.batonPass);
|
||||
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||
|
||||
if (switchOutTarget.hp > 0) {
|
||||
// for opponent switching out
|
||||
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(),
|
||||
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(),
|
||||
(user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0),
|
||||
false, this.batonPass, false), MoveEndPhase);
|
||||
false, false), MoveEndPhase);
|
||||
}
|
||||
} else {
|
||||
// Switch out logic for everything else (eg: WILD battles)
|
||||
@ -5240,8 +5243,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
}
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
getCondition(): MoveConditionFunc {
|
||||
@ -5270,7 +5272,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
}
|
||||
|
||||
if (!player && user.scene.currentBattle.battleType === BattleType.WILD) {
|
||||
if (this.batonPass) {
|
||||
if (this.isBatonPass()) {
|
||||
return false;
|
||||
}
|
||||
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
|
||||
@ -5291,7 +5293,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
return -20;
|
||||
}
|
||||
let ret = this.selfSwitch ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
||||
if (this.selfSwitch && this.batonPass) {
|
||||
if (this.selfSwitch && this.isBatonPass()) {
|
||||
const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0);
|
||||
ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10));
|
||||
}
|
||||
@ -5301,10 +5303,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||
|
||||
|
||||
export class ChillyReceptionAttr extends ForceSwitchOutAttr {
|
||||
|
||||
// using inherited constructor
|
||||
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||
user.scene.arena.trySetWeather(WeatherType.SNOW, true);
|
||||
return super.apply(user, target, move, args);
|
||||
}
|
||||
@ -7394,7 +7393,7 @@ export function initMoves() {
|
||||
new AttackMove(Moves.DRAGON_BREATH, Type.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, 30, 0, 2)
|
||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||
new SelfStatusMove(Moves.BATON_PASS, Type.NORMAL, -1, 40, -1, 0, 2)
|
||||
.attr(ForceSwitchOutAttr, true, true)
|
||||
.attr(ForceSwitchOutAttr, true, SwitchType.BATON_PASS)
|
||||
.hidesUser(),
|
||||
new StatusMove(Moves.ENCORE, Type.NORMAL, 100, 5, -1, 0, 2)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, true)
|
||||
@ -7507,7 +7506,8 @@ export function initMoves() {
|
||||
.target(MoveTarget.BOTH_SIDES),
|
||||
new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3)
|
||||
.ignoresSubstitute()
|
||||
.unimplemented(),
|
||||
.partial() // Incomplete implementation because of Uproar's partial implementation
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TORMENT, false, true, 1),
|
||||
new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], 1)
|
||||
.attr(ConfuseAttr),
|
||||
@ -7538,7 +7538,7 @@ export function initMoves() {
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false),
|
||||
new StatusMove(Moves.TAUNT, Type.DARK, 100, 20, -1, 0, 3)
|
||||
.ignoresSubstitute()
|
||||
.unimplemented(),
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.TAUNT, false, true, 4),
|
||||
new StatusMove(Moves.HELPING_HAND, Type.NORMAL, -1, 20, -1, 5, 3)
|
||||
.attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND)
|
||||
.ignoresSubstitute()
|
||||
@ -7581,9 +7581,9 @@ export function initMoves() {
|
||||
new StatusMove(Moves.SKILL_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 3)
|
||||
.ignoresSubstitute()
|
||||
.attr(SwitchAbilitiesAttr),
|
||||
new SelfStatusMove(Moves.IMPRISON, Type.PSYCHIC, -1, 10, -1, 0, 3)
|
||||
new StatusMove(Moves.IMPRISON, Type.PSYCHIC, 100, 10, -1, 0, 3)
|
||||
.ignoresSubstitute()
|
||||
.unimplemented(),
|
||||
.attr(AddArenaTagAttr, ArenaTagType.IMPRISON, 1, true, false),
|
||||
new SelfStatusMove(Moves.REFRESH, Type.NORMAL, -1, 20, -1, 0, 3)
|
||||
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN)
|
||||
.condition((user, target, move) => !!user.status && (user.status.effect === StatusEffect.PARALYSIS || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.BURN)),
|
||||
@ -7819,7 +7819,7 @@ export function initMoves() {
|
||||
.makesContact(false)
|
||||
.target(MoveTarget.ATTACKER),
|
||||
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
|
||||
.attr(ForceSwitchOutAttr, true, false),
|
||||
.attr(ForceSwitchOutAttr, true),
|
||||
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
|
||||
.attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true),
|
||||
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
|
||||
@ -8252,7 +8252,7 @@ export function initMoves() {
|
||||
new AttackMove(Moves.GRASS_PLEDGE, Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
|
||||
.partial(),
|
||||
new AttackMove(Moves.VOLT_SWITCH, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5)
|
||||
.attr(ForceSwitchOutAttr, true, false),
|
||||
.attr(ForceSwitchOutAttr, true),
|
||||
new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5)
|
||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
@ -8420,7 +8420,7 @@ export function initMoves() {
|
||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
|
||||
.attr(ForceSwitchOutAttr, true, false)
|
||||
.attr(ForceSwitchOutAttr, true)
|
||||
.soundBased(),
|
||||
new StatusMove(Moves.TOPSY_TURVY, Type.DARK, -1, 20, -1, 0, 6)
|
||||
.attr(InvertStatsAttr),
|
||||
@ -9175,7 +9175,7 @@ export function initMoves() {
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1)
|
||||
.target(MoveTarget.NEAR_ALLY),
|
||||
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8)
|
||||
.attr(ForceSwitchOutAttr, true, false),
|
||||
.attr(ForceSwitchOutAttr, true),
|
||||
new AttackMove(Moves.TRIPLE_AXEL, Type.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8)
|
||||
.attr(MultiHitAttr, MultiHitType._3)
|
||||
.attr(MultiHitPowerIncrementAttr, 3)
|
||||
@ -9502,10 +9502,11 @@ export function initMoves() {
|
||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461/4096 : 1)
|
||||
.makesContact(),
|
||||
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||
.unimplemented(),
|
||||
.attr(AddSubstituteAttr, 0.5)
|
||||
.attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL),
|
||||
new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
||||
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", {pokemonName: getPokemonNameWithAffix(user)}))
|
||||
.attr(ChillyReceptionAttr, true, false),
|
||||
.attr(ChillyReceptionAttr, true),
|
||||
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
|
||||
.attr(RemoveArenaTrapAttr, true)
|
||||
|
@ -23,5 +23,6 @@ export enum ArenaTagType {
|
||||
TAILWIND = "TAILWIND",
|
||||
HAPPY_HOUR = "HAPPY_HOUR",
|
||||
SAFEGUARD = "SAFEGUARD",
|
||||
NO_CRIT = "NO_CRIT"
|
||||
NO_CRIT = "NO_CRIT",
|
||||
IMPRISON = "IMPRISON",
|
||||
}
|
||||
|
@ -82,4 +82,7 @@ export enum BattlerTagType {
|
||||
AUTOTOMIZED = "AUTOTOMIZED",
|
||||
MYSTERY_ENCOUNTER_POST_SUMMON = "MYSTERY_ENCOUNTER_POST_SUMMON",
|
||||
HEAL_BLOCK = "HEAL_BLOCK",
|
||||
TORMENT = "TORMENT",
|
||||
TAUNT = "TAUNT",
|
||||
IMPRISON = "IMPRISON",
|
||||
}
|
||||
|
12
src/enums/switch-type.ts
Normal file
12
src/enums/switch-type.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Indicates the type of switch functionality that a {@linkcode SwitchPhase}
|
||||
* or {@linkcode SwitchSummonPhase} will carry out.
|
||||
*/
|
||||
export enum SwitchType {
|
||||
/** Basic switchout where the Pokemon to switch in is selected */
|
||||
SWITCH,
|
||||
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */
|
||||
BATON_PASS,
|
||||
/** Transfers the returning Pokemon's Substitute to the switched in Pokemon */
|
||||
SHED_TAIL
|
||||
}
|
@ -61,6 +61,7 @@ import { Challenges } from "#enums/challenges";
|
||||
import { PokemonAnimType } from "#app/enums/pokemon-anim-type";
|
||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
export enum FieldPosition {
|
||||
CENTER,
|
||||
@ -4003,16 +4004,17 @@ export class PlayerPokemon extends Pokemon {
|
||||
/**
|
||||
* Causes this mon to leave the field (via {@linkcode leaveField}) and then
|
||||
* opens the party switcher UI to switch a new mon in
|
||||
* @param batonPass Indicates if this switch was caused by a baton pass (and
|
||||
* thus should maintain active mon effects)
|
||||
* @param switchType the {@linkcode SwitchType} for this switch-out. If this is
|
||||
* `BATON_PASS` or `SHED_TAIL`, this Pokemon's effects are not cleared upon leaving
|
||||
* the field.
|
||||
*/
|
||||
switchOut(batonPass: boolean): Promise<void> {
|
||||
switchOut(switchType: SwitchType = SwitchType.SWITCH): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
this.leaveField(!batonPass);
|
||||
this.leaveField(switchType === SwitchType.SWITCH);
|
||||
|
||||
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => {
|
||||
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||
this.scene.prependToPhase(new SwitchSummonPhase(this.scene, this.getFieldIndex(), slotIndex, false, batonPass), MoveEndPhase);
|
||||
this.scene.prependToPhase(new SwitchSummonPhase(this.scene, switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase);
|
||||
}
|
||||
this.scene.ui.setMode(Mode.MESSAGE).then(resolve);
|
||||
}, PartyUiHandler.FilterNonFainted);
|
||||
@ -4074,11 +4076,11 @@ export class PlayerPokemon extends Pokemon {
|
||||
const allyPokemon = this.getAlly();
|
||||
if (slotIndex<=1) {
|
||||
// Revived ally pokemon
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), slotIndex, false, false, true));
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true));
|
||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||
} else if (allyPokemon.isFainted()) {
|
||||
// Revived party pokemon, and ally pokemon is fainted
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, allyPokemon.getFieldIndex(), slotIndex, false, false, true));
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true));
|
||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import { BattlePhase } from "./battle-phase";
|
||||
import { PostSummonPhase } from "./post-summon-phase";
|
||||
import { SummonMissingPhase } from "./summon-missing-phase";
|
||||
import { SwitchPhase } from "./switch-phase";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
export class CheckSwitchPhase extends BattlePhase {
|
||||
protected fieldIndex: integer;
|
||||
@ -50,7 +51,7 @@ export class CheckSwitchPhase extends BattlePhase {
|
||||
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true));
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, false, true));
|
||||
this.end();
|
||||
}, () => {
|
||||
this.scene.ui.setMode(Mode.MESSAGE);
|
||||
|
@ -18,6 +18,7 @@ import { GameOverPhase } from "./game-over-phase";
|
||||
import { SwitchPhase } from "./switch-phase";
|
||||
import { VictoryPhase } from "./victory-phase";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
export class FaintPhase extends PokemonPhase {
|
||||
private preventEndure: boolean;
|
||||
@ -106,14 +107,14 @@ export class FaintPhase extends PokemonPhase {
|
||||
* If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field,
|
||||
* push a phase that prompts the player to summon a Pokemon from their party.
|
||||
*/
|
||||
this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false));
|
||||
this.scene.pushPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, true, false));
|
||||
}
|
||||
} else {
|
||||
this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex));
|
||||
if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.scene.currentBattle.battleType)) {
|
||||
const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length;
|
||||
if (hasReservePartyMember) {
|
||||
this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false));
|
||||
this.scene.pushPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, -1, false, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
||||
import { GameOverPhase } from "#app/phases/game-over-phase";
|
||||
import { SwitchPhase } from "#app/phases/switch-phase";
|
||||
import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
/**
|
||||
* Will handle (in order):
|
||||
@ -241,7 +242,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
||||
const playerField = this.scene.getPlayerField();
|
||||
playerField.forEach((pokemon, i) => {
|
||||
if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) {
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, i, true, false));
|
||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, i, true, false));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||
|
||||
export class ReturnPhase extends SwitchSummonPhase {
|
||||
constructor(scene: BattleScene, fieldIndex: integer) {
|
||||
super(scene, fieldIndex, -1, true, false);
|
||||
super(scene, SwitchType.SWITCH, fieldIndex, -1, true);
|
||||
}
|
||||
|
||||
switchAndSummon(): void {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import BattleScene from "#app/battle-scene";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
import { BattlePhase } from "./battle-phase";
|
||||
@ -9,22 +10,25 @@ import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||
* for the player (if a switch would be valid for the current battle state).
|
||||
*/
|
||||
export class SwitchPhase extends BattlePhase {
|
||||
protected fieldIndex: integer;
|
||||
private isModal: boolean;
|
||||
private doReturn: boolean;
|
||||
protected readonly fieldIndex: integer;
|
||||
private readonly switchType: SwitchType;
|
||||
private readonly isModal: boolean;
|
||||
private readonly doReturn: boolean;
|
||||
|
||||
/**
|
||||
* Creates a new SwitchPhase
|
||||
* @param scene {@linkcode BattleScene} Current battle scene
|
||||
* @param switchType {@linkcode SwitchType} The type of switch logic this phase implements
|
||||
* @param fieldIndex Field index to switch out
|
||||
* @param isModal Indicates if the switch should be forced (true) or is
|
||||
* optional (false).
|
||||
* @param doReturn Indicates if the party member on the field should be
|
||||
* recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}.
|
||||
*/
|
||||
constructor(scene: BattleScene, fieldIndex: integer, isModal: boolean, doReturn: boolean) {
|
||||
constructor(scene: BattleScene, switchType: SwitchType, fieldIndex: integer, isModal: boolean, doReturn: boolean) {
|
||||
super(scene);
|
||||
|
||||
this.switchType = switchType;
|
||||
this.fieldIndex = fieldIndex;
|
||||
this.isModal = isModal;
|
||||
this.doReturn = doReturn;
|
||||
@ -38,11 +42,13 @@ export class SwitchPhase extends BattlePhase {
|
||||
return super.end();
|
||||
}
|
||||
|
||||
// Skip if the fainted party member has been revived already. doReturn is
|
||||
// only passed as `false` from FaintPhase (as opposed to other usages such
|
||||
// as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this
|
||||
// if the mon should have already been returned but is still alive and well
|
||||
// on the field. see also; battle.test.ts
|
||||
/**
|
||||
* Skip if the fainted party member has been revived already. doReturn is
|
||||
* only passed as `false` from FaintPhase (as opposed to other usages such
|
||||
* as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this
|
||||
* if the mon should have already been returned but is still alive and well
|
||||
* on the field. see also; battle.test.ts
|
||||
*/
|
||||
if (this.isModal && !this.doReturn && !this.scene.getParty()[this.fieldIndex].isFainted()) {
|
||||
return super.end();
|
||||
}
|
||||
@ -57,7 +63,8 @@ export class SwitchPhase extends BattlePhase {
|
||||
|
||||
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
|
||||
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON));
|
||||
const switchType = (option === PartyOption.PASS_BATON) ? SwitchType.BATON_PASS : this.switchType;
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, fieldIndex, slotIndex, this.doReturn));
|
||||
}
|
||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
|
||||
}, PartyUiHandler.FilterNonFainted);
|
||||
|
@ -12,29 +12,30 @@ import i18next from "i18next";
|
||||
import { PostSummonPhase } from "./post-summon-phase";
|
||||
import { SummonPhase } from "./summon-phase";
|
||||
import { SubstituteTag } from "#app/data/battler-tags";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
export class SwitchSummonPhase extends SummonPhase {
|
||||
private slotIndex: integer;
|
||||
private doReturn: boolean;
|
||||
private batonPass: boolean;
|
||||
private readonly switchType: SwitchType;
|
||||
private readonly slotIndex: integer;
|
||||
private readonly doReturn: boolean;
|
||||
|
||||
private lastPokemon: Pokemon;
|
||||
|
||||
/**
|
||||
* Constructor for creating a new SwitchSummonPhase
|
||||
* @param scene {@linkcode BattleScene} the scene the phase is associated with
|
||||
* @param switchType the type of switch behavior
|
||||
* @param fieldIndex integer representing position on the battle field
|
||||
* @param slotIndex integer for the index of pokemon (in party of 6) to switch into
|
||||
* @param doReturn boolean whether to render "comeback" dialogue
|
||||
* @param batonPass boolean if the switch is from baton pass
|
||||
* @param player boolean if the switch is from the player
|
||||
*/
|
||||
constructor(scene: BattleScene, fieldIndex: integer, slotIndex: integer, doReturn: boolean, batonPass: boolean, player?: boolean) {
|
||||
constructor(scene: BattleScene, switchType: SwitchType, fieldIndex: integer, slotIndex: integer, doReturn: boolean, player?: boolean) {
|
||||
super(scene, fieldIndex, player !== undefined ? player : true);
|
||||
|
||||
this.switchType = switchType;
|
||||
this.slotIndex = slotIndex;
|
||||
this.doReturn = doReturn;
|
||||
this.batonPass = batonPass;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
@ -64,7 +65,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
|
||||
if (!this.batonPass) {
|
||||
if (this.switchType === SwitchType.SWITCH) {
|
||||
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
|
||||
const substitute = pokemon.getTag(SubstituteTag);
|
||||
if (substitute) {
|
||||
@ -94,7 +95,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.5,
|
||||
onComplete: () => {
|
||||
pokemon.leaveField(!this.batonPass, false);
|
||||
pokemon.leaveField(this.switchType === SwitchType.SWITCH, false);
|
||||
this.scene.time.delayedCall(750, () => this.switchAndSummon());
|
||||
}
|
||||
});
|
||||
@ -105,7 +106,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
const switchedInPokemon = party[this.slotIndex];
|
||||
this.lastPokemon = this.getPokemon();
|
||||
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
||||
if (this.batonPass && switchedInPokemon) {
|
||||
if (this.switchType === SwitchType.BATON_PASS && switchedInPokemon) {
|
||||
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id));
|
||||
if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) {
|
||||
const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier
|
||||
@ -130,7 +131,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
* If this switch is passing a Substitute, make the switched Pokemon match the returned Pokemon's state as it left.
|
||||
* Otherwise, clear any persisting tags on the returned Pokemon.
|
||||
*/
|
||||
if (this.batonPass) {
|
||||
if (this.switchType === SwitchType.BATON_PASS || this.switchType === SwitchType.SHED_TAIL) {
|
||||
const substitute = this.lastPokemon.getTag(SubstituteTag);
|
||||
if (substitute) {
|
||||
switchedInPokemon.x += this.lastPokemon.getSubstituteOffset()[0];
|
||||
@ -174,8 +175,13 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
pokemon.battleSummonData.turnCount--;
|
||||
}
|
||||
|
||||
if (this.batonPass && pokemon) {
|
||||
if (this.switchType === SwitchType.BATON_PASS && pokemon) {
|
||||
pokemon.transferSummon(this.lastPokemon);
|
||||
} else if (this.switchType === SwitchType.SHED_TAIL && pokemon) {
|
||||
const subTag = this.lastPokemon.getTag(SubstituteTag);
|
||||
if (subTag) {
|
||||
pokemon.summonData.tags.push(subTag);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastPokemon?.resetSummonData();
|
||||
|
@ -32,7 +32,7 @@ export class TurnInitPhase extends FieldPhase {
|
||||
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
||||
} else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) {
|
||||
// If there is at least one pokemon in the back that is legal to switch in, force a switch.
|
||||
p.switchOut(false);
|
||||
p.switchOut();
|
||||
} else {
|
||||
// If there are no pokemon in the back but we're not game overing, just hide the pokemon.
|
||||
// This should only happen in double battles.
|
||||
|
@ -19,6 +19,7 @@ import { TurnEndPhase } from "./turn-end-phase";
|
||||
import { WeatherEffectPhase } from "./weather-effect-phase";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { TrickRoomTag } from "#app/data/arena-tag";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
export class TurnStartPhase extends FieldPhase {
|
||||
constructor(scene: BattleScene) {
|
||||
@ -179,7 +180,8 @@ export class TurnStartPhase extends FieldPhase {
|
||||
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here?
|
||||
break;
|
||||
case Command.POKEMON:
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here?
|
||||
const switchType = turnCommand.args?.[0] ? SwitchType.BATON_PASS : SwitchType.SWITCH;
|
||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer()));
|
||||
break;
|
||||
case Command.RUN:
|
||||
let runningPokemon = pokemon;
|
||||
|
@ -22,6 +22,8 @@ export function applySessionDataPatches(data: SessionSaveData) {
|
||||
} else if (m.className === "PokemonResetNegativeStatStageModifier") {
|
||||
m.className = "ResetNegativeStatStageModifier";
|
||||
} else if (m.className === "TempBattleStatBoosterModifier") {
|
||||
// Dire Hit no longer a part of the TempBattleStatBoosterModifierTypeGenerator
|
||||
if (m.typeId !== "DIRE_HIT") {
|
||||
m.className = "TempStatStageBoosterModifier";
|
||||
m.typeId = "TEMP_STAT_STAGE_BOOSTER";
|
||||
|
||||
@ -31,6 +33,14 @@ export function applySessionDataPatches(data: SessionSaveData) {
|
||||
|
||||
// From [ stat, battlesLeft ] to [ stat, maxBattles, battleCount ]
|
||||
m.args = [ newStat, 5, m.args[1] ];
|
||||
} else {
|
||||
m.className = "TempCritBoosterModifier";
|
||||
m.typePregenArgs = [];
|
||||
|
||||
// From [ stat, battlesLeft ] to [ maxBattles, battleCount ]
|
||||
m.args = [ 5, m.args[1] ];
|
||||
}
|
||||
|
||||
} else if (m.className === "DoubleBattleChanceBoosterModifier" && m.args.length === 1) {
|
||||
let maxBattles: number;
|
||||
switch (m.typeId) {
|
||||
@ -73,7 +83,7 @@ export function applySystemDataPatches(data: SystemSaveData) {
|
||||
case "1.0.3":
|
||||
case "1.0.4":
|
||||
// --- LEGACY PATCHES ---
|
||||
if (data.starterData) {
|
||||
if (data.starterData && data.dexData) {
|
||||
// Migrate ability starter data if empty for caught species
|
||||
Object.keys(data.starterData).forEach(sd => {
|
||||
if (data.dexData[sd]?.caughtAttr && (data.starterData[sd] && !data.starterData[sd].abilityAttr)) {
|
||||
@ -104,6 +114,7 @@ export function applySystemDataPatches(data: SystemSaveData) {
|
||||
// --- PATCHES ---
|
||||
|
||||
// Fix Starter Data
|
||||
if (data.starterData && data.dexData) {
|
||||
for (const starterId of defaultStarterSpecies) {
|
||||
if (data.starterData[starterId]?.abilityAttr) {
|
||||
data.starterData[starterId].abilityAttr |= AbilityAttr.ABILITY_1;
|
||||
@ -113,6 +124,7 @@ export function applySystemDataPatches(data: SystemSaveData) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.gameVersion = LATEST_VERSION;
|
||||
}
|
||||
|
65
src/test/abilities/aroma_veil.test.ts
Normal file
65
src/test/abilities/aroma_veil.test.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { PlayerPokemon } from "#app/field/pokemon";
|
||||
|
||||
describe("Moves - Aroma Veil", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("double")
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset([Moves.HEAL_BLOCK, Moves.IMPRISON, Moves.SPLASH])
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.ability(Abilities.AROMA_VEIL)
|
||||
.moveset([Moves.GROWL]);
|
||||
});
|
||||
|
||||
it("Aroma Veil protects the Pokemon's side against most Move Restriction Battler Tags", async () => {
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]);
|
||||
|
||||
const party = game.scene.getParty()! as PlayerPokemon[];
|
||||
|
||||
game.move.select(Moves.GROWL);
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.forceEnemyMove(Moves.HEAL_BLOCK);
|
||||
await game.toNextTurn();
|
||||
party.forEach(p => {
|
||||
expect(p.getTag(BattlerTagType.HEAL_BLOCK)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("Aroma Veil does not protect against Imprison", async () => {
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]);
|
||||
|
||||
const party = game.scene.getParty()! as PlayerPokemon[];
|
||||
|
||||
game.move.select(Moves.GROWL);
|
||||
game.move.select(Moves.GROWL, 1);
|
||||
await game.forceEnemyMove(Moves.IMPRISON, BattlerIndex.PLAYER);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined();
|
||||
party.forEach(p => {
|
||||
expect(p.getTag(BattlerTagType.IMPRISON)).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
@ -18,6 +18,7 @@ import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
||||
import { Status, StatusEffect } from "#app/data/status-effect";
|
||||
import { SwitchType } from "#enums/switch-type";
|
||||
|
||||
|
||||
|
||||
@ -113,7 +114,7 @@ describe("Abilities - ZEN MODE", () => {
|
||||
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||
await game.phaseInterceptor.run(TurnStartPhase);
|
||||
game.onNextPrompt("SwitchPhase", Mode.PARTY, () => {
|
||||
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, 0, 1, false, false));
|
||||
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, SwitchType.SWITCH, 0, 1, false));
|
||||
game.scene.ui.setMode(Mode.MESSAGE);
|
||||
});
|
||||
game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => {
|
||||
|
98
src/test/moves/imprison.test.ts
Normal file
98
src/test/moves/imprison.test.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||
|
||||
describe("Moves - Imprison", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset([Moves.IMPRISON, Moves.SPLASH, Moves.GROWL])
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.moveset([Moves.TRANSFORM, Moves.SPLASH]);
|
||||
});
|
||||
|
||||
it("Pokemon under Imprison cannot use shared moves", async () => {
|
||||
await game.classicMode.startBattle([Species.REGIELEKI]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.TRANSFORM);
|
||||
await game.forceEnemyMove(Moves.IMPRISON);
|
||||
await game.toNextTurn();
|
||||
const playerMoveset = playerPokemon.getMoveset().map(x => x?.moveId);
|
||||
const enemyMoveset = game.scene.getEnemyPokemon()!.getMoveset().map(x => x?.moveId);
|
||||
expect(enemyMoveset.includes(playerMoveset[0])).toBeTruthy();
|
||||
const imprisonArenaTag = game.scene.arena.getTag(ArenaTagType.IMPRISON);
|
||||
const imprisonBattlerTag = playerPokemon.getTag(BattlerTagType.IMPRISON);
|
||||
expect(imprisonArenaTag).toBeDefined();
|
||||
expect(imprisonBattlerTag).toBeDefined();
|
||||
|
||||
// Second turn, Imprison forces Struggle to occur
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
const move1 = playerPokemon.getLastXMoves(1)[0]!;
|
||||
expect(move1.move).toBe(Moves.STRUGGLE);
|
||||
});
|
||||
|
||||
it("Imprison applies to Pokemon switched into Battle", async () => {
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]);
|
||||
|
||||
const playerPokemon1 = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.forceEnemyMove(Moves.IMPRISON);
|
||||
await game.toNextTurn();
|
||||
const imprisonArenaTag = game.scene.arena.getTag(ArenaTagType.IMPRISON);
|
||||
const imprisonBattlerTag1 = playerPokemon1.getTag(BattlerTagType.IMPRISON);
|
||||
expect(imprisonArenaTag).toBeDefined();
|
||||
expect(imprisonBattlerTag1).toBeDefined();
|
||||
|
||||
// Second turn, Imprison forces Struggle to occur
|
||||
game.doSwitchPokemon(1);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
const playerPokemon2 = game.scene.getPlayerPokemon()!;
|
||||
const imprisonBattlerTag2 = playerPokemon2.getTag(BattlerTagType.IMPRISON);
|
||||
expect(playerPokemon1).not.toEqual(playerPokemon2);
|
||||
expect(imprisonBattlerTag2).toBeDefined();
|
||||
});
|
||||
|
||||
it("The effects of Imprison only end when the source is no longer active", async () => {
|
||||
game.override.moveset([Moves.SPLASH, Moves.IMPRISON]);
|
||||
await game.classicMode.startBattle([Species.REGIELEKI, Species.BULBASAUR]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
game.move.select(Moves.IMPRISON);
|
||||
await game.forceEnemyMove(Moves.GROWL);
|
||||
await game.toNextTurn();
|
||||
expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeDefined();
|
||||
expect(enemyPokemon.getTag(BattlerTagType.IMPRISON)).toBeDefined();
|
||||
game.doSwitchPokemon(1);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
expect(playerPokemon.isActive(true)).toBeFalsy();
|
||||
expect(game.scene.arena.getTag(ArenaTagType.IMPRISON)).toBeUndefined();
|
||||
expect(enemyPokemon.getTag(BattlerTagType.IMPRISON)).toBeUndefined();
|
||||
});
|
||||
});
|
56
src/test/moves/shed_tail.test.ts
Normal file
56
src/test/moves/shed_tail.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { SubstituteTag } from "#app/data/battler-tags";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
||||
|
||||
describe("Moves - Shed Tail", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset([Moves.SHED_TAIL])
|
||||
.battleType("single")
|
||||
.enemySpecies(Species.SNORLAX)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
it("transfers a Substitute doll to the switched in Pokemon", async () => {
|
||||
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||
|
||||
const magikarp = game.scene.getPlayerPokemon()!;
|
||||
|
||||
game.move.select(Moves.SHED_TAIL);
|
||||
game.doSelectPartyPokemon(1);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase", false);
|
||||
|
||||
const feebas = game.scene.getPlayerPokemon()!;
|
||||
const substituteTag = feebas.getTag(SubstituteTag);
|
||||
|
||||
expect(feebas).not.toBe(magikarp);
|
||||
expect(feebas.hp).toBe(feebas.getMaxHp());
|
||||
// Note: Shed Tail's HP cost is currently not accurate to mainline, as it
|
||||
// should cost ceil(maxHP / 2) instead of max(floor(maxHp / 2), 1). The current
|
||||
// implementation is consistent with Substitute's HP cost logic, but that's not
|
||||
// the case in mainline for some reason :regiDespair:.
|
||||
expect(magikarp.hp).toBe(Math.ceil(magikarp.getMaxHp() / 2));
|
||||
expect(substituteTag).toBeDefined();
|
||||
expect(substituteTag?.hp).toBe(Math.floor(magikarp.getMaxHp() / 4));
|
||||
});
|
||||
});
|
54
src/test/moves/taunt.test.ts
Normal file
54
src/test/moves/taunt.test.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
|
||||
describe("Moves - Taunt", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset([Moves.TAUNT, Moves.SPLASH])
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.moveset([Moves.GROWL]);
|
||||
});
|
||||
|
||||
it("Pokemon should not be able to use Status Moves", async () => {
|
||||
await game.classicMode.startBattle([Species.REGIELEKI]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
// First turn, Player Pokemon succeeds using Growl without Taunt
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.forceEnemyMove(Moves.TAUNT);
|
||||
await game.toNextTurn();
|
||||
const move1 = playerPokemon.getLastXMoves(1)[0]!;
|
||||
expect(move1.move).toBe(Moves.GROWL);
|
||||
expect(move1.result).toBe(MoveResult.SUCCESS);
|
||||
expect(playerPokemon?.getTag(BattlerTagType.TAUNT)).toBeDefined();
|
||||
|
||||
// Second turn, Taunt forces Struggle to occur
|
||||
game.move.select(Moves.GROWL);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
const move2 = playerPokemon.getLastXMoves(1)[0]!;
|
||||
expect(move2.move).toBe(Moves.STRUGGLE);
|
||||
});
|
||||
});
|
64
src/test/moves/torment.test.ts
Normal file
64
src/test/moves/torment.test.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { MoveResult } from "#app/field/pokemon";
|
||||
import { BattlerTagType } from "#enums/battler-tag-type";
|
||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
||||
|
||||
describe("Moves - Torment", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset([Moves.TORMENT, Moves.SPLASH])
|
||||
.enemySpecies(Species.SHUCKLE)
|
||||
.enemyLevel(30)
|
||||
.moveset([Moves.TACKLE])
|
||||
.ability(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("Pokemon should not be able to use the same move consecutively", async () => {
|
||||
await game.classicMode.startBattle([Species.CHANSEY]);
|
||||
|
||||
const playerPokemon = game.scene.getPlayerPokemon()!;
|
||||
|
||||
// First turn, Player Pokemon uses Tackle successfully
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.forceEnemyMove(Moves.TORMENT);
|
||||
await game.toNextTurn();
|
||||
const move1 = playerPokemon.getLastXMoves(1)[0]!;
|
||||
expect(move1.move).toBe(Moves.TACKLE);
|
||||
expect(move1.result).toBe(MoveResult.SUCCESS);
|
||||
expect(playerPokemon?.getTag(BattlerTagType.TORMENT)).toBeDefined();
|
||||
|
||||
// Second turn, Torment forces Struggle to occur
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.toNextTurn();
|
||||
const move2 = playerPokemon.getLastXMoves(1)[0]!;
|
||||
expect(move2.move).toBe(Moves.STRUGGLE);
|
||||
|
||||
// Third turn, Tackle can be used.
|
||||
game.move.select(Moves.TACKLE);
|
||||
await game.forceEnemyMove(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
const move3 = playerPokemon.getLastXMoves(1)[0]!;
|
||||
expect(move3.move).toBe(Moves.TACKLE);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user