mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-01-16 14:01:52 +00:00
c2b2cf08db
* feat: Add Google and Discord login functionality feat: Add link to Discord in menu UI feat: Add Discord and Google login functionality Add container around discord and google icons refactor: Update environment variable names for Discord and Google client IDs feat: Add "Or use" translation for login options in multiple languages feat: Update menu UI translations for multiple languages Code review fixes refactor: Update Discord and Google client IDs in environment variables * refactor: Add missing properties to initLoggedInUser function * Update src/locales/de/menu-ui-handler.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * make i18n debugging an optional env setting this also reduces output noise in tests * set development default also to "0" * [Localization]Localized move-trigger text * [Localization]Localized zh-cn move-trigger text * [Localization]fix typo * [Localization]fix typo * Update src/locales/pt_BR/move-trigger.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * [Localization]add pokemonName to move-trigger.ts * [Localization]add pokemonName to move-trigger.ts * Update zh_TW move-trigger.ts * Update zh_CN move-trigger.ts * Update move.ts * Update src/locales/ko/move-trigger.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/ko/move-trigger.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update move-trigger.ts * Update src/locales/de/move-trigger.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * [Localization]add affix to target pokemon names * fix inaccurate docs for TypeImmunityAbAttr (#2957) * chore: Add beta branch to GitHub Actions tests workflow * chore: Add beta branch to GitHub Actions linter and gh-pages workflow * Update src/locales/ko/menu-ui-handler.ts Co-authored-by: lnuvy <lnuvy.code@gmail.com> * Update src/locales/ko/menu.ts Co-authored-by: lnuvy <lnuvy.code@gmail.com> * Localization(pt): translated bgm-name.ts (#2955) * Localization(pt): translated bgm-name.ts * fix * update VITE_I18N_DEBUG in .env files * chore: Update environment variables for beta and production environments * chore: Add beta branch to GitHub Actions deploy workflow * Hardcoded Pokemon should have proper names (#2941) * Refactor challenges and add fresh start (#2963) * [Balance] Update many TM learnsets (#2879) * Update TMs for more Indigo Disk changes * Fix typo, more Stored Power * Refactor challenges and add fresh start (#2964) * Refactor challenges and add fresh start * Add achievement for fresh start challenge * Update src/locales/fr/move-trigger.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * [Bug] Fix off-by-one error in damage calc (#2970) * Fix random damage roll to be 85-100% instead of 85-99% * Update battle.test.ts to reflect the fix * [Bug] Grounded on Terrain fixes (#2969) * [Help] [Move/Bug] Patches Psychic Terrain applicability edge cases Was cancelling moves even if targeted mons weren't on the terrain. * [Bug `]Pokemon.isGrounded` does not exist Replaced with `Pokemon.isGrounded()`, which does. * [Bug] Psychic Terrain priority move cancel ignoring ungrounded * [Bug] Semi-invulnerable should not be grounded * Update game-stats-ui-handler.ts (italian) (#2965) * [Bugfix] Fix a bug during bgm-bar initialization (#2822) * Prevent sizing error * Make reboot not necessary for show BGM * Makes the BGM Bar active by default + It had originally been decided that this would not be active by default because it was to be displayed outside the pause menu, but since its behavior has changed between this decision and its integration, the default deactivation is no longer necessary * [Mirror][Localization] Translate game victory sentences #2254 (#2906) * Translate forgotten sentences on phases * Translate to de forgotten sentences on phases * Translate to es forgotten sentences on phases * Translate to fr forgotten sentences on phases * Translate to it forgotten sentences on phases * Translate to ko forgotten sentences on phases * Translate to pt br forgotten sentences on phases * Translate to zh cn forgotten sentences on phases * Translate to zh tw forgotten sentences on phases * remove duplicate message * remove duplicate message * Update src/locales/pt_BR/battle.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/de/battle.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/de/battle.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update battle.ts [Localization(it)] * Update src/locales/zh_CN/battle.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> --------- Co-authored-by: Alexis <alexis.faizeau@animedigitalnetwork.fr> Co-authored-by: 송지원 <jiwsong@gmarket.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * [Mirror][Localization] Translate summary #2336 (#2907) * Translate pokemon summary * Translate pokemon summary to fr * Translate pokemon summary to de * Translate pokemon summary to es * Translate pokemon summary to it * Translate pokemon summary to ko * Translate pokemon summary to pt br * Translate pokemon summary to zh cn * Translate pokemon summary to zh tw * Fix import * Update partially for en and ko * Update interface name for en * Merge trainerLabel&Text and calculate typeLabel width * Update src/locales/es/pokemon-summary.ts Co-authored-by: GoldTra <162721984+GoldTra@users.noreply.github.com> * Apply translations key to all languages with interface TranslationEtries * Update ko/translationKey of status * Update ko/translationKey of pokemonInfo * Update de/translationKeys of memostring and metFragment * Update de/translationKeys of memostring and metFragment 2 * Update src/locales/ko/pokemon-summary.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/ko/pokemon-summary.ts Co-authored-by: returntoice <dieandbecome@gmail.com> * Update src/locales/pt_BR/pokemon-summary.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/ko/pokemon-summary.ts * Update src/locales/pt_BR/pokemon-summary.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update pokemon-summary.ts [Localization(it)] * remove unused code * Update src/locales/zh_CN/pokemon-summary.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/zh_CN/pokemon-summary.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/zh_CN/pokemon-summary.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/es/pokemon-summary.ts Co-authored-by: Arxxer <javiptn7@gmail.com> * Update src/locales/es/pokemon-summary.ts Co-authored-by: Arxxer <javiptn7@gmail.com> --------- Co-authored-by: Alexis <alexis.faizeau@animedigitalnetwork.fr> Co-authored-by: Mr.WaterT <water.t.works@gmail.com> Co-authored-by: Alexis Faizeau <faizeau.alexis@gmail.com> Co-authored-by: GoldTra <162721984+GoldTra@users.noreply.github.com> Co-authored-by: returntoice <dieandbecome@gmail.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: 송지원 <jiwsong@gmarket.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: Arxxer <javiptn7@gmail.com> * [Localization] Localization arena flyout (Active Battle Effects) (#2932) * localizing Active Battle Effects (working) * Localize Active Battle Effects * Change return value * Modify arena terrain desc * Update src/locales/zh_CN/arena-flyout.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/de/arena-flyout.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/fr/arena-flyout.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update arena-flyout.ts (IT) * Update src/locales/pt_BR/arena-flyout.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/es/arena-flyout.ts Co-authored-by: Arxxer <javiptn7@gmail.com> * Change util function name --------- Co-authored-by: 송지원 <jiwsong@gmarket.com> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Arxxer <javiptn7@gmail.com> * This should prevent gym leaders appear as doubles (marnie & piers) when they arent fixed battles (#2904) * The stat messages can now be plural (#2600) * The stat messages are now plural * "And" can now be localized * Revert Override * Update src/locales/fr/battle.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/fr/battle.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/fr/battle.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/zh_CN/battle.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/ko/battle.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/ko/battle.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/es/battle.ts Co-authored-by: GoldTra <162721984+GoldTra@users.noreply.github.com> * Update src/locales/es/battle.ts Co-authored-by: GoldTra <162721984+GoldTra@users.noreply.github.com> * Update src/locales/de/battle.ts * Update src/locales/pt_BR/battle.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/pt_BR/battle.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/data/battle-stat.ts * Update src/locales/zh_CN/battle.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Changed the way the multiple is handeled to use the i18n way * Missed one file * Apply suggestions from code review * Apply suggestions from code review * Changed the tests so they work now with the i18n hting * Fixed some other tests (chinese still makes problems... * Fix tests for chinese * Tests * Update src/test/battle-stat.spec.ts * Update src/locales/zh_CN/battle.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Apply suggestions from code review --------- Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: GoldTra <162721984+GoldTra@users.noreply.github.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * [Enhancement][QoL] Add option to adjust shop overlay opacity (#2622) * add option to adjust shop overlay opacity * add localization * fix bug * Update src/locales/fr/settings.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/zh_CN/settings.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/fr/settings.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * update default value * update setting values * re-add value 10 * Update src/locales/pt_BR/settings.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/ko/settings.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/es/settings.ts Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Update settings.ts --------- Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: InnocentGameDev <asdargmng@gmail.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> * [Bug] Fix battler tags lapsing at incorrect times (#2944) * Fix battler tags lapsing at incorrect times * Document FlinchedTag * Update French pokemon-summary.ts (#2976) * [Test] Prevent tests from running if overrides are different from the default values (#2110) * mock default overrides in test setup * change beforeEach to beforeALl * move some more enums into the enums directory * replace modules that import i18n into overrides with modules that don't * add pre tests and update vitest configs, scripts * replace tabs with spaces * fix vitest server port overlap warning * add missing overrides and clean up workspace config * change test name * include spec files in main test suite * [QoL] Highlight targets of multitarget moves instead of immediate execution (#2863) * show targets for move targeting multiple pokemon * dont allow selecting target if multiple * fix targeting * cleanup * more cleanup * only highlight targets is move is not status * fix tests failing * fix tests * change "immediately" to "auto" * nevermind just remove auto * remove status move condition * [Refactor] rewrite applyAbAttrsInternal to use an iterator. (#1832) * initial rewrite of applyAbAttrsInternal * clean up applyAbAttrsInternal * remove the await because it wraps non Promises in a promise * add TODO comment about promises * fix broken costar test, hopefully * Update typescript and typedoc (#2988) * update typescript and typedoc to latest versions * forgot to add the package-lock * add fixes for breaking type gen * update workflow (#2989) * [Qol/Balance] Dynamax cannon tweak (#2540) * Dynamax Cannon fix * Update src/locales/fr/move.ts Added fr translation. Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/fr/move.ts Adding an important missing precision in French description Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/de/move.ts German locale commit. Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/zh_CN/move.ts Chinese locale commit, checked by a native speaker Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/ko/move.ts Korean locale added Co-authored-by: returntoice <dieandbecome@gmail.com> * fixed trailing space in Chinese locale * added es locale * Different Dynamax Cannon fix. This one is the one * Dynamax Cannon fix localisations * Update src/locales/fr/move.ts Added fr locale Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/de/move.ts German translation ^^ Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/pt_BR/move.ts pt-BR translation Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/de/move.ts Eslint fix, good catch @Enoch Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/ko/move.ts Korean locale ^^ Co-authored-by: sodam <66295123+sodaMelon@users.noreply.github.com> * Update src/locales/zh_CN/move.ts zh_CN locale Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/zh_TW/move.ts zh_TW locale Co-authored-by: RimKnight <rimknight852@gmail.com> * Update move.ts linting mistake due to how my comment was written * Update move.ts linting mistake due to how my comment was written * Update move.ts [Localization(it)] * WIP test * WIP test part 2 * [Test] Add Unit Tests for Dynamax Cannon * removed some unnecessary cases in the test to reduce testing overhead * Update src/locales/ko/move.ts Updated kr locale Co-authored-by: Enoch <enoch.jwsong@gmail.com> * [Test] Adjust Unit Tests for Dynamax Cannon --------- Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: returntoice <dieandbecome@gmail.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: sodam <66295123+sodaMelon@users.noreply.github.com> Co-authored-by: RimKnight <rimknight852@gmail.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: xsn34kzx <xsn34kzx@gmail.com> * [Test] Fix/Extend Unit Test for Hard Press (#2992) * [Test] Update tests to enable no-crits override (#2971) * Update tests to enable no-crits override * Rename variable maxHP to initialHP * localized type * [Localization]Localized move-trigger text (#2913) * [Localization]Localized move-trigger text * [Localization]Localized zh-cn move-trigger text * [Localization]fix typo * [Localization]fix typo * Update src/locales/pt_BR/move-trigger.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * [Localization]add pokemonName to move-trigger.ts * [Localization]add pokemonName to move-trigger.ts * Update zh_TW move-trigger.ts * Update zh_CN move-trigger.ts * Update move.ts * Update src/locales/ko/move-trigger.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/ko/move-trigger.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update move-trigger.ts * Update src/locales/de/move-trigger.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * [Localization]add affix to target pokemon names * Update src/locales/fr/move-trigger.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * localized type --------- Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * [Bug] Fix description of rare candy to have proper amount of level (#2903) * [Bug] Fix description of rare candy to have proper amount of level * Update en locales * Add locales * Add locales * Update src/locales/de/modifier-type.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/de/modifier-type.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/fr/modifier-type.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/fr/modifier-type.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/ko/modifier-type.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/ko/modifier-type.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/zh_CN/modifier-type.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/zh_CN/modifier-type.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/pt_BR/modifier-type.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/pt_BR/modifier-type.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update modifier-type.ts (IT) * Update src/locales/es/modifier-type.ts Co-authored-by: Arxxer <javiptn7@gmail.com> * Update src/locales/es/modifier-type.ts Co-authored-by: Arxxer <javiptn7@gmail.com> --------- Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: Arxxer <javiptn7@gmail.com> * move status effect enum into separate file (#2998) * add .env.test (#2997) block i18n debug output by default * [QoL] Create default overrides class and export that with custom overrides (#2999) * Create default overrides class and export that with custom overrides * add comment to mock import and replace typecast with type narrowing * change modifier override type to pick keys from modifierTypes * [Feature][FUN] add breedersInSpace splash message (#2631) * add spaceBreeder splash message * fix typo (whops) * Add pt_BR translation Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * replace text & key with breedersInSpace * add french tanslation Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * remove obsolete array split in splash-messages.ts * Update src/locales/pt_BR/splash-messages.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/es/splash-messages.ts Co-authored-by: Asdar <asdargmng@gmail.com> * Update splash-messages.ts (Italian) * Update src/locales/zh_CN/splash-messages.ts Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Update src/locales/ko/splash-messages.ts Co-authored-by: sodam <66295123+sodaMelon@users.noreply.github.com> --------- Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: Asdar <asdargmng@gmail.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: sodam <66295123+sodaMelon@users.noreply.github.com> * [Enhancement] Pokemon generation number tooltip (#2642) * Create tooltip to show Pokemon generation * Add option to toggle generation tooltip * Use roman numeral for generation tooltip * Revert "Add option to toggle generation tooltip" This reverts commit 414b2366fc55c5642f8858d6f4854aa62c4caf60. * Update src/locales/de/battle-info.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/fr/battle-info.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/pt_BR/battle-info.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/zh_TW/battle-info.ts Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/zh_CN/battle-info.ts Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/ko/battle-info.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update battle-info.ts (IT) --------- Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: RimKnight <rimknight852@gmail.com> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> * [Beta] Updating the manage data->unlock all option to work with all abilities, natures, forms and passives (#2967) * Updated code to allow user to unlock all pokemon from the manage data menu option * Added code to Utils to allow it to check for a beta env, and hid the unlock all code behind that. This should stop it from being accessed in prod envs * Updated another section to be locked behind beta check, and also updated the everything.prsv to have everything unlocked going forward * Fixed some code reviews * [Bug] vite port (for development) (#3003) * make vite-port configurable and make it default 8000 * add retries for `does not trigger by non damage moves` test * feat: Update isBeta check in utils.ts to use import.meta.env.MODE The current implementation of the isBeta check in utils.ts is using import.meta.env.DEV, which gives the same value for both beta and dev environments. This commit updates the check to use import.meta.env.MODE === "beta" to accurately determine if the environment is beta. This ensures that the unlock all code is only accessible in the beta environment and not in production environments. * refactor: Update trainerId and secretId in game data This commit updates the `trainerId` and `secretId` properties in the `GameData` class. The values are replaced with the corresponding values from the `this.trainerId` and `this.secretId` variables. This change ensures that the `trainerId` and `secretId` are correctly updated in the game data. * Adds bg glow behind starters with unlocked passives (#2497) * [Localization] Localize missed things in battlers-tag.ts (#3004) * Localize missed things in battlers-tag * Change orders of configs * Add missed phase * Update src/locales/zh_CN/battle.ts Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/zh_CN/battle.ts Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/zh_TW/battle.ts Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/zh_TW/battle.ts Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/zh_TW/battler-tags.ts Co-authored-by: RimKnight <rimknight852@gmail.com> * Update src/locales/zh_CN/battler-tags.ts Co-authored-by: RimKnight <rimknight852@gmail.com> --------- Co-authored-by: RimKnight <rimknight852@gmail.com> * [Feature] Fully implement Octolock (#2985) * implement octolock * Add tests * [Bug] Fix import of overrides in a test (#3009) * [BUG] Fixes Sketch copying the first move used by the opponent instead of the last (#2759) * Changes getMoveHistory to getLastXMoves to fix sketch copying first move used instead of last * Optimizes move search and early return * Reverts check for virtual moves * [Bug] Making FormChangeItems Untransferrable (#2695) * Made FormChangeItems untransferrable. Replaced getTransferrable() * Made isTransferrable readonly. Removed unnecessary 'm as PokemonHeldItemModifier'. * [Bug] Fix Clear terrains upon Trainer Battle (#2027) * Clear terrains upon Trainer Battle * Adjusted comment * Fix item reward overrides going out of bounds (#3012) * [Localization] Localize Berry Heal message (#2996) * Localize berry heal message (HP/PP) * Change location of translation * conflict resolve * Update src/locales/de/battle.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Update src/locales/de/battle.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Update src/locales/de/battle.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/zh_CN/battle.ts Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> * Update src/locales/zh_TW/battle.ts Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> * Update src/locales/fr/battle.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/pt_BR/battle.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/pt_BR/battle.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update battle.ts (Berries ITALIAN) --------- Co-authored-by: 송지원 <jiwsong@gmarket.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> * Adds a check to the bgm-bar display to prevent it from being displayed in case of an empty value (#3007) * [Sprite] Improves Corviknight Palette usage (#3020) * [Sprite] Compress Corviknight exp * [Sprite] Compress Corviknight exp * [Sprite] Compress Corviknight exp * [Sprite] Compress Corviknight exp * [Sprite] Compress Corviknight exp * [Beta][Localization] Update French battler-tags.ts and battle.ts (#3015) * Update French battler-tags.ts * Update battle.ts * [Bug] Fix Lock-On and Mind Reader not working on the first turn (#3001) * [Bug] Struggle Recoil should ignore Magic Guard (#3017) * Drafted testable conditions for Magic Guard * Weather Test * Update src/test/abilities/magic_guard.test.ts InnerThunder Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * Implemented checks for poison/toxic/burn * Added tests for recoil moves and volatile status * Updated Rock Head, Magic Guard, and Reckless interactions with Struggle * Removed stray file * Fixed Typedoc errors * Implemented innerthunder's feedback --------- Co-authored-by: Frutescens <info@laptop> Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> * [Enhancement] Decouple move power calculation from Pokemon.apply(), Fixes Power Spot & Battery not boosting ally's move (#2984) * refactor power calc, fix battery & power spot * fix hard press unit test * fix hard press * refactor tests * use sypOn hp instead * rename method * cleanup tests * improve tests * use slow vs fast pokemon * fix steely spirit test * fix steely spirit for real this time * remove unnecessary test * address pr feedback * add removed code * [QoL] Offset the status indicator to keep pokeball in view (#2966) * Offset the status indicator to keep pokeball in view * Only use offsetX when the Pokemon is the enemy * Adjust position to accomodate boss health bar * [Bug] Fix to epic shiny Yungoos icon (#2991) (#3016) Fixed a floating pixel in epic shiny Yungoos' icon. Caused by Mimikyu's epic shiny sprite being placed too high on the sprite sheet. Moved Mimikyu's sprite down and updated json to reflect the change. * [Localization(ko)] Change line-break position of ghost type curse add message (#3022) * Revert "[Localization]Localized move-trigger text (#2913)" (#3026) This reverts commit 39bdfea0c8b33a759db760809d6488d825b510e2. * [Localization(ko)] Fixed typo in Bertha's dialogue (#3025) * [Bug] Prevent fixed-damage and OHKO moves from being modified by damage-reducing abilities (#2703) * ReceivedMoveDamageMultiplierAbAttr patch: WIP refactored damage calculation, reordered ReceivedMoveDamageMultiplierAbAttr to avoid issues with fixed damage and OHKO moves, stubbed unit tests for dragon rage (fixed damage) and fissure (OHKO) * ReceivedMoveDamageMultiplierAbAttr patch: commented concerns regarding EnemyDamageBooster/ReducerModifier for others' reference in WIP branch * ReceivedMoveDamageMultiplierAbAttr patch: reordered ReceivedMoveDamageMultiplierAbAttr and EnemyDamageBooster/ReducerModifier to not trigger for fixed damage and OHKO moves, completed relevant tests for dragon rage and fissure * ReceivedMoveDamageMultiplierAbAttr patch: removed newline * ReceivedMoveDamageMultiplierAbAttr patch: in the unit test, extracted hard-coded Dragon Rage damage to a variable * ReceivedMoveDamageMultiplierAbAttr patch: naming consistency * ReceivedMoveDamageMultiplierAbAttr patch: replaced awaiting DamagePhase with TurnEndPhase as the former assumes damage will be done * ReceivedMoveDamageMultiplierAbAttr patch: removed redundant overrides in Fissure tests * ReceivedMoveDamageMultiplierAbAttr patch: tests: refactored crit removal, removed berries, fixed bug associated with Porygon sometimes getting Trace and copying the opponent's ability, which would override the manual ability override * Fixed unit tests * Added a comment and cleaned up an existing one * [Localization] Brought german localization up to date (#3010) * Fixed errors in the german localization * Fresh Start Challenge * Update src/locales/de/move-trigger.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Apply suggestions from code review --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * [Bug] Remove redundant damage number popups (#3024) * Remove magic number from Belly Drum's attr * Remove redundant damage number popup * Fix merge issue and remove another duplicate damage number instance * [Localization]fix placeholder error * [Move] Implements Conversion 2 (#2943) * Creates function to get type resistances, implements conversion 2 * Removes unimplemented tag, adds condition for move history to exist * Cleans up type selection, creates i18n entries for typeChanged * Uses typeChanged i18n in Conversion move * More detailed docs, early return with stellar/unknown type * Adds note that it wont track type-changing moves properly * Rephrases doc description, adds partial since it can't track type-changing moves * Updates localization, removes typeChanged entry to use move-trigger entry * Missed locale de entry in last commit * Adds comment for reason of .partial() * Fixes localization error due to revert, removes improper merge conflict from prior commit * [BUG] Fixes bug with Metronome freezing the game (#2819) * Tests MovePhase with new PokemonMove instead of moveset search * Accounts for metronome call on charging moves * Update comment in ChargeAttr to be clearer * Add missing passive background graphic for legacy UI Relating to #2497 , this adds an identical graphic to the legacy UI files, as it will show up as a missing texture otherwise. * [Localization]fix placeholder error * [Localization]fix placeholder error * [Localization]fix placeholder error * [Localization] Localize ability messages in ability.ts (trigger, apply...) (#2972) * localize ability messages * Update src/locales/de/ability-trigger.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Modify parameter name, fix eslint * Korean Translation, modify some wrong param * Add missed message * Update src/locales/de/ability-trigger.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update ability-trigger.ts (Partial) (Italian) * Localize type name * Localize type name > Libero, Protean * param bug fix * Update src/data/ability.ts * Update zh-cn * Update ability-trigger.ts (Partial part 2, still not completed) (Italian) * Update src/locales/fr/ability-trigger.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/pt_BR/ability-trigger.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update src/locales/fr/ability-trigger.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> --------- Co-authored-by: 송지원 <jiwsong@gmarket.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Update egg.ts (italian) (#3038) * [Localization]complete AddTypeAttr localization * [Sprite] Revert Larvesta, fixing stray pixels (#3042) * [Sprite] Remove normal Larvesta stray pixels Taken from original commit * [Sprite] Revert shiny Larvesta stray pixels Taken from original commit * [Sprite] Match variant Larvesta colours * [Bug] Fix level 100 moves being skipped if leveled past 100 (#3040) * [Bug][Fix][Surf][Muddy Water][Sludge Wave][Animation] Added image to move animations. (#3044) * Update muddy-water.json * Update sludge-wave.json * Update surf.json * [Sprite] Fix Cofagrigus sprite (#3045) * Fix Cofagrigus sprite * Base sprite fix * Created json variant file... manually * Update src/locales/ko/move-trigger.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update src/locales/de/move-trigger.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Update src/locales/pt_BR/move-trigger.ts Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * [Localization]fix typo * Fix FR typo (#3051) * Update src/locales/fr/move-trigger.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * [Beta][Localization] Update Portuguese battler-tags.ts and battle.ts (#3050) * Update move-trigger.ts (PARTIAL, italian) * Update move-trigger.ts (PARTIAL, italian) * [Music/BGM] Fix bgm file & loop for battle_rival (#3053) * stop loading pride-update banner (#3057) * Update src/locales/ko/move-trigger.ts Co-authored-by: Enoch <enoch.jwsong@gmail.com> * [Item] Add Scope Lens and Leek (#2666) * [Item] Add Scope Lens and Leek * Add Entry to pt_BR * Localize for pt_BR Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Fix & Clean Unit Tests --------- Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * [Music/BGM] New music added for the "Slum" biome, composed by Andr06 (#3000) * Replacement of the BGM in the slum biome (by Andr06) * Modification of BGM credits in README.md * Replacement of the BGM name by the title chosen by its composer in all languages * Update BGM loop point for biome 'SLUM' * [Bug] implemented multi target damage penalty (#2329) * fix: damage multiplier by the number of targets * fix: used actual number value rather than the number holder * test: added unit test for counting targets * multi-target: fixed names of the unit tests. * test: simple-test changes * test: changed multi-target test code * test: testing damage decreasement * test: multi-target test fix * resolved conflicts in test --------- Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> * [BUG] Uturn faint switch bug fix (#2980) * reordering adding switch phases for u-turn * reverting some temp changes * generalizing function * adding PR suggestions * nit indenting * [Sprite] Remove extra Oinkologne sprites (#3067) * Delete public/images/pokemon/female/916.png * Delete public/images/pokemon/exp/female/916.json * Delete public/images/pokemon/exp/female/916.png * Delete public/images/pokemon/female/916.json * [Sprite] Mega Mewtwo sprite fixes (#3065) * [Sprite] Add static epic Mega Mewtwo Y Colours taken from exp, as consistent/exp rare backs share palettes. * [Sprite] Fix transparent pixels on Mega Mewtwo X * [Sprite] Fix transparent pixels on Mega Mewtwo X * [Sprite] Add static epic Mega Mewtwo Y Now exists, and is a paletteswap * [Music/BGM] New music added for the "Sea" biome, composed by Andr06 (#3063) * Replacement of the BGM in the sea biome (by Andr06) * Modification of BGM credits in README.md * Replacement of the BGM name by the title chosen by its composer in all languages * Update BGM loop point for biome 'SEA' * [Balance] Add 12 new TMs and remove 3 (#2733) * Added Counter TM * Counter, Aqua Tail, Gastro Acid, Pluck, Secret Power, Aurora Veil, Incinerate * Placed TMs in proper order, added to pool * Add Secret Power to the pool * Add TM for Synthesis * Covet, Heal Bell, PUPunch, Recycle, Volt Tackle, Worry Seed * Remove species specific TMs * Remove Secret Power TM until implementation * [Refactor/Test] Update Dynamax Cannon Unit Tests (#3074) * [Refactor/Test] Update Dynamax Cannon Unit Tests * Adjust Test Names * fix: update SameSite attribute in setCookie function to None * fix: update SameSite attribute in setCookie function to Strict * feat: Add prompt=none to Discord OAuth authorization URL * fix: Remove unnecessary cookie setting in LoginPhase and MenuUiHandler * feat: Improve cookie handling in getCookie function * feat: Delete duplicate cookies with the same name in getCookie function * [Localization] Add minor Korean translations to ability-trigger file (trace) (#3093) * modify trace trigger description (refered from official translation) modify trace trigger description (refered from official translation) * modify trace trigger description (refered from official translation) modify trace trigger description (refered from official translation) * Fix broken `multi_target` tests and remove RNG-based failures (#3095) * Prevent RNG from breaking the Quick Draw tests (#3096) * Make EXP boosting items stack additively and not multiplicatively (#3094) Fixes #2040 * [Bug] Fixes Encore bug with multi-target moves missing (#3060) * Pushes move history even when multi target move misses * Move pushMoveHistory out of Move Pending condition * Add remaining relearn moves (#2894) * [Enhancement] Decouple move accuracy and accuracy multiplier calculation from phases.ts (#2899) * refactor accuracy calc * update doc * move accuracy multiplier calculation outside phases * update wonder skin unit test * rename method * add docs * add unit tests * address feedback * rename method * fix imports * improve tests * add test for ohko move accuracy * [Sprite] Fix a number of sprite issues including the Zubat line, Goldeen, Golett, Dudunsparce, H-Sneasel, Garchomp, Sylveon, Marshadow (#3069) * Batfix, Goldeen, Chomp, Golett, genies * Fix Dudunsparce using Reborn shiny backsprite for whatever reason * Fix female Hisuian Sneasel missing variants * Marshadow epic front and Zenith, Sylveon back * Fix Zubat line back sprite inconsistencies * Fix Noivern shiny back * [Move] Finish implementation of Glaive Rush (#2720) * Finish implementation of Glaive Rush * Fix test RNG * Add code/test for Multi-Lens interaction * Fix off-by-one error in test caused by rounding issues * Update for code changes * Fix BattlerTag name * [Bug] fix not changing moveset after add to starter #1932 (#2396) * [Bug] Enemies can properly use stuff cheeks (#3090) * [Feature] Added pokemon nicknames/renaming (#2570) * Added basic temp renaming * Made nickname persistant after reloading * Localization and cancel button * Fixed instant rename on active pokemon * Small bugfix to prevent console errors * Changed logic to use the new nickname field. Replaced most .name with getNameToRender() for display. * Changed evolution message. Removed log messagesc * Added localization keys for other languages * Removed empty lines * French translation Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Chinese translation Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Portuguese (Brazil) translation Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Korean translation Co-authored-by: Enoch <enoch.jwsong@gmail.com> * Update menu.ts * Update menu.ts [Localization(it)] * Changed most .getNameToRender() instance to getPokemonNameWithAffix() * Changed wild encounter messages back to just use the name without affix. * Added localization for the party ui rename selection * Escaping nickname characters to support all characters * Better Error handling * Update src/field/pokemon.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --------- Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * [Sprite] Show Partner Eevee variants (#3097) * [Sprite] Show Partner Eevee variants Taken from non-partner Eevee * [Sprite] Show Partner Eevee variants Taken from non-partner Eevee * [Sprite] Show Partner Eevee variants * Allow Necrozma forms to play their special music (#3054) * [BUG] fixing multi-hit and move messages on faint (#2981) * fixing order of messages, scences, to render messages before fainting * updated fix for effectiveness text rendering order for multi hit moves * fixing messages not appearing for multi-hit moves on faint * updated multi-hit condition) * fixing PR conflicts * adding comments and FaintPhase setPhaseQueueSplice bug, fixing overrides merge conflict * writing better comments * removing space diff in overrides * adding fainting check for self damage moves * emergency fixing broken last commit * additional comments for multi-hit problem * updating comments, jsdoc style * fixing linter, destiny bond errors * splitting up varaible comments to be in JSDoc format * fixing tests and merge mistakes * adding rendering of multihit moves that only hit once * fixing comment formatting_tabs and spaces --------- Co-authored-by: Benjamin Odom <bennybroseph@gmail.com> * [Bug] Sheer Force/Serene Grace Flyout Bugfix (#2496) * Disable Show Ability for Serene Grace and Sheer Force when opponent calculates targetBenefitScore * Add comment and definition to argument * [Visual] Achievement icons (#2617) * Text Changes to MonoGen Challenges * Item Texture Files * MonoGen Ribbons added * Resolving merge conflict * Updated texture files? I think * Changed image names and added new localizations for syntax * Removed unused ribbons * [Feature] replace bug-report template with form (#2772) * replace bug-report template with form * update bug_report.yml * add palceholders for data & session file * Update item atlas * Update item atlas again This time, after merging main into the PR. * [Sprite] Show Partner Eevee variants (#3097) * [Sprite] Show Partner Eevee variants Taken from non-partner Eevee * [Sprite] Show Partner Eevee variants Taken from non-partner Eevee * [Sprite] Show Partner Eevee variants * Merged with beta's new items * Fixed typedoc issues * Removing localizations * Text Changes to MonoGen Challenges * Item Texture Files * MonoGen Ribbons added * Resolving merge conflict * Updated texture files? I think * Changed image names and added new localizations for syntax * Removed unused ribbons * Update item atlas * [Feature] replace bug-report template with form (#2772) * replace bug-report template with form * update bug_report.yml * add palceholders for data & session file * Update item atlas again This time, after merging main into the PR. * Merged with beta's new items * Fixed typedoc issues * Removing localizations * Allow Necrozma forms to play their special music (#3054) * Allow Necrozma forms to play their special music (#3054) * MonoGen Ribbons added * Text Changes to MonoGen Challenges * Item Texture Files * MonoGen Ribbons added * Updated texture files? I think * Changed image names and added new localizations for syntax * Fixed typedoc issues * Revert "Text Changes to MonoGen Challenges" This reverts commit 3bf79acc6a797b0e28e68e113e644b30096ea9ce. * Text Changes to MonoGen Challenges * Item Texture Files * MonoGen Ribbons added * Resolving merge conflict * Updated texture files? I think * Changed image names and added new localizations for syntax * Removed unused ribbons * Update item atlas * [Feature] replace bug-report template with form (#2772) * replace bug-report template with form * update bug_report.yml * add palceholders for data & session file * Update item atlas again This time, after merging main into the PR. * Merged with beta's new items * Fixed typedoc issues * Removing localizations * Text Changes to MonoGen Challenges * Item Texture Files * MonoGen Ribbons added * Resolving merge conflict * Updated texture files? I think * Changed image names and added new localizations for syntax * Removed unused ribbons * Update item atlas * Update item atlas again This time, after merging main into the PR. * Merged with beta's new items * Fixed typedoc issues * Removing localizations * MonoGen Ribbons added * Text Changes to MonoGen Challenges * Item Texture Files * MonoGen Ribbons added * Updated texture files? I think * Changed image names and added new localizations for syntax * Fixed typedoc issues * Revert "Text Changes to MonoGen Challenges" This reverts commit 3bf79acc6a797b0e28e68e113e644b30096ea9ce. * Please fix this. * Revert "[Feature] replace bug-report template with form (#2772)" This reverts commit aa69b107778123b2173bee6130e5d172d201b637. --------- Co-authored-by: Frutescens <info@laptop> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Madmadness65 <blaze.the.fireman@gmail.com> Co-authored-by: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> * Re-add changes accidentally deleted by #2617 * Fix some broken images in item atlas & achievement Fixed the broken sprites in the item atlas, and fixed the Baton Pass achievement to use the Baton item now (it was broken by the Leek being renamed). Also resized the Baton item image to 32*32, like all the other items. * fix: Remove unnecessary cookie setting in removeCookie function * fix: Update removeCookie function to use Max-Age=-1 instead of Expires header The removeCookie function was updated to use the Max-Age=-1 attribute instead of setting the Expires header to a past date. This change ensures that the cookie is immediately expired when removed, preventing any potential login loops. Additionally, a legacy cookie without a domain was added to handle older cookies. This commit resolves the unnecessary cookie setting in the removeCookie function. * [QoL] Starter UI selection update to allow removing specific pokemon from party (#1983) * Initial commits with logic to remove starters if they're in your party. Still need to make it work so that the starter selection cursor disappears when a starter is unselected * Updated code to be able to remove pokemon, including the side icons and cursor locations * Fixed popstarter to work with any index * Updating code to allow navigation of starter icons * Updating code to allow navigation of party starter icons * Updaing navigation of party icons * Updated logic to fix incorrect icon in top left of pokemon options when navigating the starter icons * Updated logic to include the ability to navigate and interact with the starter icons * Forgot to push the actual starter-select-ui-handler. Might be a bit hard to test things out without that :) * Removed some unnecessary comments * Fixed small bug with not being able to move from the far right to the gen selection when the starter icons were empty * Updated code to not be using a method to generate the party menu and made it more like it used to be. This should help with merge conflicts in the future * I committed the merged version but forgot to make the starter-select-ui-handler staged after making the changes * Accidentally broke challenges that had a specific typing requirement with last commit. This should fix it * Changed how navigation worked based on popular demand * Fixed code review comments * Accidentally left in a whole block of commented code. Intentionally removing it now * Started adding logic for mono type challenge runs to not break the game if the user tries to start a run with an invalid party * Updated the text to say the party is invalid * Updated logic to make invalid pokemon greyed out when no valid pokemon are in your party * Added comments on some code * Updated locales to include the key for trying to start with invalid parties during a challenge * Fixed some code from a bad merge where a challenge related param that was previously a number now needed to be a boolean and wasn't * Removed comment as per review * [Bug] Ability changing on evolution fix (#2995) * Prevent Pokemon with their second ability from evolving into their HA * Add check for fusions too * Localization(pt): Updated move.ts (#3078) * Localization(pt): Updated move.ts and fixed King's Shield * more fixes * [Bug] Enemy pokemon's Harvest creates berry icons on the player's side when triggered (#3077) * [Bug] Fix hustle not applying attack boost (#3101) * [Sprite] Mega Latis using opposite's eye colour (#3102) * [Sprite] Mega Latis using opposite eyes Taken from counterpart Lati * [Sprite] Update Mega Latios eyes * [Sprite] Update Mega Latias eyes * [Sprite] Mega Latias duplicated palette value Colour taken from non-exp Mega Latias * [Bug] Fix Thousand Arrows not hitting targets under the effects of Magnet Rise (#3100) * Fix Thousand Arrows not hitting through Magnet Rise * Add integration test for Thousand Arrows vs. Magnet Rise * ESLint * Remove unnecessary checks in integration tests * [Move] Aeroblast is a wind move (#3109) * Hardcoded Pokemon should have proper names * Aeroblast is a wind move * [Refactor] Replaces the single and double battles overrides with a single override (#3104) `SINGLE_BATTLE_OVERRIDE` and `DOUBLE_BATTLE_OVERRIDE` are now `BATTLE_TYPE` Also updates all uses of the old overrides with the new one * fix `Overrides` capitalization (#3111) * [Balance] More TM fixes (#3081) * More TM fixes * Leaf Storm * Removed sexy matcha * [Refactor] Code readability update (#3085) * Clean up/clarify `src/field/pokemon.ts` a bit Code provided by DerTapp on Discord * Update `PokemonSpeciesForm.getAbilityCount()` * Update `PokemonSpeciesForm.getAbility()` * Add explicit `Abilities.NONE` checks * Add tests * Add jsdoc and implement test suggestions * [Move] Implement Stockpile, Spit Up, Swallow (#2960) * feat: Implement Stockpile, Spit Up, Swallow * chore: Minor, likely unnecessary null checks * feat: Localization * Undo non-English localizations (unsure if they went through proper channels) * ko localization from @EnochG1 Co-authored-by: Enoch <enoch.jwsong@gmail.com> * linting fix * add tests, tiny (non-functional) tweaks * Remove unnecessary cast * Update src/data/move.ts (oops) * remove some unnecessary comments, rename something for clarity --------- Co-authored-by: Enoch <enoch.jwsong@gmail.com> * [Bug] Remove ability swap code in `PlayerPokemon.evolve()` (#3113) * [Test] Add test for rollout move (#3114) * add test for rollout move Co-authored-by: Zach Day <zachdayz@gmail.com> * fix tests still using `SINGLE_BATTLE_OVERRIDE` --------- Co-authored-by: Zach Day <zachdayz@gmail.com> * [Bug][Beta] Fix Hyper Beam, etc. permanently recharging (#3115) * Fix Hyper Beam, etc. permanently recharging * Fix override in hyper beam test * [QoL] New Starter Select UI with Filter (#2916) * update images for new UI * add updated starter UI with filter code * update starter-select test code * update win filter condition to pass test * remove unnecessary console log * update test code to match current filter UI * merge update * apply bugfix & chrry-pick small issues fix which are handled beta branch * resolve conflicts * fix lint errors * Fixed a bug where the target location for escaping using the left and right buttons on the starter button did not account for scrolling * update filter bar label color change when activated * fix lint error * fix lint * fix octolock.text.ts. it looks override import error. idk why it is happend in this PR. but it looks ok now * add passive dropdown in unlocks filter * fix lint * fix double button sound bug. refactoring genSpecies -> allSpecies, starterContainers -> starterContainer which are remove unnecessary generation axis * optimize updateStarterValueLabel function which is bottleneck of UI update latency * apply translation of gen filter label. fix lint * add # candies sort option * merge beta * resolve confilcts * fix offset of starter and start cursor * make compatible with starter UI * add missing feature * add images for legacy UI. adjust the position and size of the starterContainerWindow * [Localization] Implement Form localization (#3030) * Implement Pokemon forms localization * Update French pokemon-form.ts * Update French pokemon-form.ts * Update pokemon-form.ts * Add battle forms korean translation from returntoice Co-authored-by: returntoice <dieandbecome@gmail.com> * Add cosplay forms korean translation from returntoice Co-authored-by: returntoice <dieandbecome@gmail.com> * Add pichu form korean translation from returntoice Co-authored-by: returntoice <dieandbecome@gmail.com> * Add castform forms korean translation by returntoice Co-authored-by: returntoice <dieandbecome@gmail.com> * Add remaining forms korean translation by returntoice Co-authored-by: returntoice <dieandbecome@gmail.com> * French typo corrections pokemon-form.ts * Update Korean pokemon-form.ts * Modify froakiBattleBond like rockruff of OwnTempo case, it is froakie, not greninja. * Modify zygardePc Power construct is more important information Switch its position to 50% or 10% to avoid overlapping with the sprite * Modify mispelling * Added german forms * Changed Gigadynamax and Unendynamax so it fits at all * Add partner pikachu and eevee form localization * Add mimikyu forms localization * Partner Pikachu, Partner Evoli und Mimikyu Formen hinzugefügt * Update pokemon-form.ts * Update partners * Fix conflicts * Fix useless ? by flx-sta Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Fix switch use by flx-sta * Fix conflicts * Please work !!!!!!! * Update src/locales/fr/pokemon-form.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Update src/locales/fr/pokemon-form.ts Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Add pt_br primal localization Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Add pt_br pikachu localization Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Add pt_br castform localization Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Add pt_br 3g localization Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Add zh_cn localization Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Add pt_br other localization Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> * Add es castform localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es burmy localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es shellos localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es rotom localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es basculin localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es deerling localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es froakie localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es scatterbug localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es furfrou localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es xerneas localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es zygarde localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es pumpkaboo localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es flabebe localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es oricorio localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es minior localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es magearna localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es marshadow localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es sinistea ocalization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es eiscue localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es indeedee localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es rockruff localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es mimikyu localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es zarude localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es squawkabilly localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es tatsugiri localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es gimmighoul localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es poltchageit localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es paldeaTauros localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es primal localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es pikachu localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Add es partner localization Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Fix typedocs error * Fix typedocs error * cn form prefix added Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> * Fix german translate error * Fix typo Zh_CN Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> * Update pokemon-form.ts [Localization(it)] * Update src/locales/es/pokemon-form.ts Co-authored-by: InnocentGameDev <asdargmng@gmail.com> * Fix megas forms bug and add forgotten forms * Fix wrong ko config * Add fr localization for new forms Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> * Add de localization for new forms Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Remove forgotten debug line Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Optimize battle forms * Update pokemon-form.ts (additional forms, italian localization) * The same typo is in the zh_TW placeholder text too. Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> * Fix forgotten megaY Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Add capitalizeString to utils * Fix typedoc error * Update src/data/pokemon-species.ts Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> * Add ko localization for new forms Co-authored-by: MrWaterT <87186129+MrWaterT@users.noreply.github.com> --------- Co-authored-by: Lugiad <adrien.grivel@hotmail.fr> Co-authored-by: returntoice <dieandbecome@gmail.com> Co-authored-by: Mr.WaterT <water.t.works@gmail.com> Co-authored-by: MrWaterT <87186129+MrWaterT@users.noreply.github.com> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: Benjamin Odom <bennybroseph@gmail.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: InnocentGameDev <asdargmng@gmail.com> Co-authored-by: EnochG1 <enoch.jwsong@gmail.com> Co-authored-by: mercurius-00 <80205689+mercurius-00@users.noreply.github.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> * [Move] Fully implement Jungle Healing/Lunar Blessing status heal (#2785) * [Bug] Fix tags not resetting on switch (#3119) Fixes #2982 * [Move] Water Shuriken guarantees 3 hits with battle bond (#2687) * [Bug] Make on-summon abilities trigger after the switch check (#3118) * Make on-summon abilities trigger after the switch check * Add test * [Ability] Cloud Nine now displays a message on activation Maintains parity with Air Lock. Both of these probably need to be localized. Should fix issue 491 though * [QoL] Summary Option for Caught Pokemon (#2921) * Option to view Summary before adding new Pokemon to party * Fixed issues described by HopsWas * Adjusted makeRoomForConfirmUi to improve window spacing * Fixed ESLint issue + addressed OrangeRed review * Fixed Github pages issue * Removed duplicate unshiftPhase * Fixed phase order * Don't start from beginning of catch function * Option to view Summary before adding new Pokemon to party * Fixed issues described by HopsWas * Adjusted makeRoomForConfirmUi to improve window spacing * Fixed Github pages issue * Fixed phase order * Quick fix * This should fix the summaryOption feature without bugging confirm-ui-handler in other cases * Revert "Merge remote-tracking branch 'origin/summaryOption1' into summaryOption1" This reverts commit ea7d0ce59e3f5631a8ef3d76646069a3945ed036, reversing changes made to 4c565958dafe6904925015ed7100e4940f041213. * Added a better conditional that reflects its source and purpose --------- Co-authored-by: Frutescens <info@laptop> Co-authored-by: AJ Fontaine <fontbane@gmail.com> * [Bug] Fix HP rounding issues (#2968) * [Bug] Fixes bug with frenzy moves keeping the confusion counter despite disruption (#3041) * Adds frenzyMissFunc trigger on NO_EFFECT hit result * Refactors FrenzyAttr to properly lapse tags each turn and remove tags on disrupt * Makes comment on CONFUSED tag clearer * Changes all integer usages to number in battler-tags * Update getBattlerTag function to use number instead of integer * [Tests] Updating Leftovers test (#3123) * Bug Fix * Update src/system/game-stats.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Fixing a test * Fixed import * Revert "Bug Fix" This reverts commit 834844978ef2583b00555969a99b3071c47a19d9. --------- Co-authored-by: Frutescens <info@laptop> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * [Localization] Correctly Localized German names of the BGM for Evil Teams (#3124) * [Bug] Game Stats Sub-Legendary NaN bug for new game files (#3122) * Bug Fix * Update src/system/game-stats.ts Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * Fixing a test * Revert "Fixing a test" This reverts commit 1bdbe2da5a7e29dce17911eb7c1084de44328573. * Test Re-Run --------- Co-authored-by: Frutescens <info@laptop> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> * [Bug] Adjust how counter attacks target to account for uturn/voltswitch and double battles (#2462) * Adjust how counter attacks target to account for uturn/voltswitch * Creates move flag for metal burst/comeuppance to redirect in some cases * Remove debug printing * Bit shifts the redirect counter flag * Removes extraneous class from prior testing * Remove vitest timestamp file that was accidentally added * [Misc] Change fresh start achievement icon to reviver seed Mystic ticket icons in the achievements just confuse players * [Balance] Fix N-Solarizer etc appearing without secondary mon registered in dex (#2713) * Fix reins et al being available without unlocking second mon * Small fix * Parentheses for safety * Add documentation to new function * Bug Fix (#3129) Co-authored-by: Frutescens <info@laptop> * [Bug] Fixed OHKO moves being affected by accuracy and evasion battle stats (#3117) * Fixed OHKO moves being affected by accuracy and evasion battle stats * Added related tests for Fissure, unskipped related test for Hustle * Tweaked fissure accuracy and evasion tests to use spyOn() for getAccuracyMultiplier() as per feedback * Fixed accuracy test for Fissure * [Fix][Sprite] Politoed Back Sprites (#3130) * [Fix][Sprite] Politoed Back Sprites Retrieved first version from history. Rearranged frames to make jump a little less floaty. Set same anim length for all variations. Json is identical for all variations. Spritesheet frame position is identical for all variations. Trimmed the frame of extra space Reduced file size of spritesheets and JSON due to less unique frames needed. * [Fix][Sprite] Politoed Back Sprites - Reformatted JSON to texturepacker standard Reformatted json. * [Bug] Hotfix for Starter select UI with Filter (#3121) * fix remove pokemon menu * fix dropdown all is not changing bug * fix bug when go down with no starter on start button * fix starter corsor bug on deletion. out of screen cursor bug * fix challenge log * fix lint error * Update src/locales/it/move-trigger.ts * Sync with beta * resolve merge at move.ts * remove duplicate method --------- Co-authored-by: Frederico Santos <frederico.f.santos@tecnico.ulisboa.pt> Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com> Co-authored-by: Felix Staud <felix.staud@headwire.com> Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br> Co-authored-by: Enoch <enoch.jwsong@gmail.com> Co-authored-by: Adrian T <68144167+torranx@users.noreply.github.com> Co-authored-by: lnuvy <lnuvy.code@gmail.com> Co-authored-by: Tempoanon <163687446+Tempo-anon@users.noreply.github.com> Co-authored-by: Xavion3 <xavion333@gmail.com> Co-authored-by: AJ Fontaine <36677462+Fontbane@users.noreply.github.com> Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: chaosgrimmon <31082757+chaosgrimmon@users.noreply.github.com> Co-authored-by: Niccolò <123510358+NicusPulcis@users.noreply.github.com> Co-authored-by: Dakurei <maxime.palanchini@gmail.com> Co-authored-by: Alexis <alexis.faizeau@animedigitalnetwork.fr> Co-authored-by: 송지원 <jiwsong@gmarket.com> Co-authored-by: Yonmaru40 <47717431+40chyan@users.noreply.github.com> Co-authored-by: Mr.WaterT <water.t.works@gmail.com> Co-authored-by: Alexis Faizeau <faizeau.alexis@gmail.com> Co-authored-by: GoldTra <162721984+GoldTra@users.noreply.github.com> Co-authored-by: returntoice <dieandbecome@gmail.com> Co-authored-by: Arxxer <javiptn7@gmail.com> Co-authored-by: InnocentGameDev <asdargmng@gmail.com> Co-authored-by: innerthunder <168692175+innerthunder@users.noreply.github.com> Co-authored-by: Dmitriy K <kagno.dmitriy@gmail.com> Co-authored-by: sodam <66295123+sodaMelon@users.noreply.github.com> Co-authored-by: RimKnight <rimknight852@gmail.com> Co-authored-by: xsn34kzx <xsn34kzx@gmail.com> Co-authored-by: Amani H <109637146+xsn34kzx@users.noreply.github.com> Co-authored-by: hayuna <marek.kowalonek@gmail.com> Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> Co-authored-by: EmberCM <emberdevteam@gmail.com> Co-authored-by: Opaque02 <66582645+Opaque02@users.noreply.github.com> Co-authored-by: mcmontag <54485715+mcmontag@users.noreply.github.com> Co-authored-by: schmidtc1 <62030095+schmidtc1@users.noreply.github.com> Co-authored-by: Arxalc <63990624+Arxalc@users.noreply.github.com> Co-authored-by: Mumble <kimjoanne@protonmail.com> Co-authored-by: Frutescens <info@laptop> Co-authored-by: EmberCM <kooly213@hotmail.com> Co-authored-by: Zoruu <113668528+ArielStevens@users.noreply.github.com> Co-authored-by: Corrade <49605314+Corrade@users.noreply.github.com> Co-authored-by: Madmadness65 <blaze.the.fireman@gmail.com> Co-authored-by: cam <lrlrliwoo@gmail.com> Co-authored-by: 송영진 <36808515+bucket1582@users.noreply.github.com> Co-authored-by: DustinLin <39450497+DustinLin@users.noreply.github.com> Co-authored-by: 서명인 (Myungin, SEO) <65226760+smee6@users.noreply.github.com> Co-authored-by: gjeodnd12165 <61226524+gjeodnd12165@users.noreply.github.com> Co-authored-by: sirzento <sirzento@gmx.de> Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> Co-authored-by: Benjamin Odom <bennybroseph@gmail.com> Co-authored-by: Daniel Gaston <danielgaston6@gmail.com> Co-authored-by: Zach Day <zachdayz@gmail.com> Co-authored-by: Leo Kim <47556641+KimJeongSun@users.noreply.github.com> Co-authored-by: Kiriox <66013753+Kiriox94@users.noreply.github.com> Co-authored-by: MrWaterT <87186129+MrWaterT@users.noreply.github.com> Co-authored-by: AJ Fontaine <fontbane@gmail.com>
8754 lines
356 KiB
TypeScript
8754 lines
356 KiB
TypeScript
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
|
|
import { BattleEndPhase, MoveEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases";
|
|
import { BattleStat, getBattleStatName } from "./battle-stat";
|
|
import { EncoreTag, HelpingHandTag, SemiInvulnerableTag, StockpilingTag, TypeBoostTag } from "./battler-tags";
|
|
import { getPokemonMessage, getPokemonNameWithAffix } from "../messages";
|
|
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
|
|
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect";
|
|
import { getTypeResistances, Type } from "./type";
|
|
import { Constructor } from "#app/utils";
|
|
import * as Utils from "../utils";
|
|
import { WeatherType } from "./weather";
|
|
import { ArenaTagSide, ArenaTrapTag, WeakenMoveTypeTag } from "./arena-tag";
|
|
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr, IgnoreProtectOnContactAbAttr, IgnoreMoveEffectsAbAttr, applyPreDefendAbAttrs, MoveEffectChanceMultiplierAbAttr, WonderSkinAbAttr, applyPreAttackAbAttrs, MoveTypeChangeAttr, UserFieldMoveTypePowerBoostAbAttr, FieldMoveTypePowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr, VariableMovePowerAbAttr } from "./ability";
|
|
import { allAbilities } from "./ability";
|
|
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier, PokemonMoveAccuracyBoosterModifier, AttackTypeBoosterModifier, PokemonMultiHitModifier } from "../modifier/modifier";
|
|
import { BattlerIndex } from "../battle";
|
|
import { Stat } from "./pokemon-stat";
|
|
import { TerrainType } from "./terrain";
|
|
import { SpeciesFormChangeActiveTrigger } from "./pokemon-forms";
|
|
import { ModifierPoolType } from "#app/modifier/modifier-type";
|
|
import { Command } from "../ui/command-ui-handler";
|
|
import i18next from "i18next";
|
|
import { Localizable } from "#app/interfaces/locales";
|
|
import { getBerryEffectFunc } from "./berry";
|
|
import { Abilities } from "#enums/abilities";
|
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
|
import { BattlerTagType } from "#enums/battler-tag-type";
|
|
import { Biome } from "#enums/biome";
|
|
import { Moves } from "#enums/moves";
|
|
import { Species } from "#enums/species";
|
|
|
|
export enum MoveCategory {
|
|
PHYSICAL,
|
|
SPECIAL,
|
|
STATUS
|
|
}
|
|
|
|
export enum MoveTarget {
|
|
/** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_the_user Moves that target the User} */
|
|
USER,
|
|
OTHER,
|
|
ALL_OTHERS,
|
|
NEAR_OTHER,
|
|
/** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_all_adjacent_Pok%C3%A9mon Moves that target all adjacent Pokemon} */
|
|
ALL_NEAR_OTHERS,
|
|
NEAR_ENEMY,
|
|
/** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_all_adjacent_foes Moves that target all adjacent foes} */
|
|
ALL_NEAR_ENEMIES,
|
|
RANDOM_NEAR_ENEMY,
|
|
ALL_ENEMIES,
|
|
/** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Counterattacks Counterattacks} */
|
|
ATTACKER,
|
|
/** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_one_adjacent_ally Moves that target one adjacent ally} */
|
|
NEAR_ALLY,
|
|
ALLY,
|
|
USER_OR_NEAR_ALLY,
|
|
USER_AND_ALLIES,
|
|
/** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_target_all_Pok%C3%A9mon Moves that target all Pokemon} */
|
|
ALL,
|
|
USER_SIDE,
|
|
/** {@link https://bulbapedia.bulbagarden.net/wiki/Category:Entry_hazard-creating_moves Entry hazard-creating moves} */
|
|
ENEMY_SIDE,
|
|
BOTH_SIDES,
|
|
PARTY,
|
|
CURSE
|
|
}
|
|
|
|
export enum MoveFlags {
|
|
NONE = 0,
|
|
MAKES_CONTACT = 1 << 0,
|
|
IGNORE_PROTECT = 1 << 1,
|
|
IGNORE_VIRTUAL = 1 << 2,
|
|
SOUND_BASED = 1 << 3,
|
|
HIDE_USER = 1 << 4,
|
|
HIDE_TARGET = 1 << 5,
|
|
BITING_MOVE = 1 << 6,
|
|
PULSE_MOVE = 1 << 7,
|
|
PUNCHING_MOVE = 1 << 8,
|
|
SLICING_MOVE = 1 << 9,
|
|
/**
|
|
* Indicates a move should be affected by {@linkcode Abilities.RECKLESS}
|
|
* @see {@linkcode Move.recklessMove()}
|
|
*/
|
|
RECKLESS_MOVE = 1 << 10,
|
|
BALLBOMB_MOVE = 1 << 11,
|
|
POWDER_MOVE = 1 << 12,
|
|
DANCE_MOVE = 1 << 13,
|
|
WIND_MOVE = 1 << 14,
|
|
TRIAGE_MOVE = 1 << 15,
|
|
IGNORE_ABILITIES = 1 << 16,
|
|
/**
|
|
* Enables all hits of a multi-hit move to be accuracy checked individually
|
|
*/
|
|
CHECK_ALL_HITS = 1 << 17,
|
|
/**
|
|
* Indicates a move is able to be redirected to allies in a double battle if the attacker faints
|
|
*/
|
|
REDIRECT_COUNTER = 1 << 18,
|
|
}
|
|
|
|
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
|
|
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
|
|
|
|
export default class Move implements Localizable {
|
|
public id: Moves;
|
|
public name: string;
|
|
public type: Type;
|
|
public defaultType: Type;
|
|
public category: MoveCategory;
|
|
public moveTarget: MoveTarget;
|
|
public power: integer;
|
|
public accuracy: integer;
|
|
public pp: integer;
|
|
public effect: string;
|
|
public chance: integer;
|
|
public priority: integer;
|
|
public generation: integer;
|
|
public attrs: MoveAttr[];
|
|
private conditions: MoveCondition[];
|
|
private flags: integer;
|
|
private nameAppend: string;
|
|
|
|
constructor(id: Moves, type: Type, category: MoveCategory, defaultMoveTarget: MoveTarget, power: integer, accuracy: integer, pp: integer, chance: integer, priority: integer, generation: integer) {
|
|
this.id = id;
|
|
|
|
this.nameAppend = "";
|
|
this.type = type;
|
|
this.defaultType = type;
|
|
this.category = category;
|
|
this.moveTarget = defaultMoveTarget;
|
|
this.power = power;
|
|
this.accuracy = accuracy;
|
|
this.pp = pp;
|
|
this.chance = chance;
|
|
this.priority = priority;
|
|
this.generation = generation;
|
|
|
|
this.attrs = [];
|
|
this.conditions = [];
|
|
|
|
this.flags = 0;
|
|
if (defaultMoveTarget === MoveTarget.USER) {
|
|
this.setFlag(MoveFlags.IGNORE_PROTECT, true);
|
|
}
|
|
if (category === MoveCategory.PHYSICAL) {
|
|
this.setFlag(MoveFlags.MAKES_CONTACT, true);
|
|
}
|
|
|
|
this.localize();
|
|
}
|
|
|
|
localize(): void {
|
|
const i18nKey = Moves[this.id].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as unknown as string;
|
|
|
|
this.name = this.id ? `${i18next.t(`move:${i18nKey}.name`)}${this.nameAppend}` : "";
|
|
this.effect = this.id ? `${i18next.t(`move:${i18nKey}.effect`)}${this.nameAppend}` : "";
|
|
}
|
|
|
|
/**
|
|
* Get all move attributes that match `attrType`
|
|
* @param attrType any attribute that extends {@linkcode MoveAttr}
|
|
* @returns Array of attributes that match `attrType`, Empty Array if none match.
|
|
*/
|
|
getAttrs<T extends MoveAttr>(attrType: Constructor<T>): T[] {
|
|
return this.attrs.filter((a): a is T => a instanceof attrType);
|
|
}
|
|
|
|
/**
|
|
* Check if a move has an attribute that matches `attrType`
|
|
* @param attrType any attribute that extends {@linkcode MoveAttr}
|
|
* @returns true if the move has attribute `attrType`
|
|
*/
|
|
hasAttr<T extends MoveAttr>(attrType: Constructor<T>): boolean {
|
|
return this.attrs.some((attr) => attr instanceof attrType);
|
|
}
|
|
|
|
/**
|
|
* Takes as input a boolean function and returns the first MoveAttr in attrs that matches true
|
|
* @param attrPredicate
|
|
* @returns the first {@linkcode MoveAttr} element in attrs that makes the input function return true
|
|
*/
|
|
findAttr(attrPredicate: (attr: MoveAttr) => boolean): MoveAttr {
|
|
return this.attrs.find(attrPredicate);
|
|
}
|
|
|
|
/**
|
|
* Adds a new MoveAttr to the move (appends to the attr array)
|
|
* if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition}
|
|
* @param AttrType {@linkcode MoveAttr} the constructor of a MoveAttr class
|
|
* @param args the args needed to instantiate a the given class
|
|
* @returns the called object {@linkcode Move}
|
|
*/
|
|
attr<T extends Constructor<MoveAttr>>(AttrType: T, ...args: ConstructorParameters<T>): this {
|
|
const attr = new AttrType(...args);
|
|
this.attrs.push(attr);
|
|
let attrCondition = attr.getCondition();
|
|
if (attrCondition) {
|
|
if (typeof attrCondition === "function") {
|
|
attrCondition = new MoveCondition(attrCondition);
|
|
}
|
|
this.conditions.push(attrCondition);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds a new MoveAttr to the move (appends to the attr array)
|
|
* if the MoveAttr also comes with a condition, also adds that to the conditions array: {@linkcode MoveCondition}
|
|
* Almost identical to {@link attr}, except you are passing in a MoveAttr object, instead of a constructor and it's arguments
|
|
* @param attrAdd {@linkcode MoveAttr} the attribute to add
|
|
* @returns the called object {@linkcode Move}
|
|
*/
|
|
addAttr(attrAdd: MoveAttr): this {
|
|
this.attrs.push(attrAdd);
|
|
let attrCondition = attrAdd.getCondition();
|
|
if (attrCondition) {
|
|
if (typeof attrCondition === "function") {
|
|
attrCondition = new MoveCondition(attrCondition);
|
|
}
|
|
this.conditions.push(attrCondition);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the move target of this move
|
|
* @param moveTarget {@linkcode MoveTarget} the move target to set
|
|
* @returns the called object {@linkcode Move}
|
|
*/
|
|
target(moveTarget: MoveTarget): this {
|
|
this.moveTarget = moveTarget;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Getter function that returns if this Move has a MoveFlag
|
|
* @param flag {@linkcode MoveFlags} to check
|
|
* @returns boolean
|
|
*/
|
|
hasFlag(flag: MoveFlags): boolean {
|
|
// internally it is taking the bitwise AND (MoveFlags are represented as bit-shifts) and returning False if result is 0 and true otherwise
|
|
return !!(this.flags & flag);
|
|
}
|
|
|
|
/**
|
|
* Getter function that returns if the move hits multiple targets
|
|
* @returns boolean
|
|
*/
|
|
isMultiTarget(): boolean {
|
|
switch (this.moveTarget) {
|
|
case MoveTarget.ALL_OTHERS:
|
|
case MoveTarget.ALL_NEAR_OTHERS:
|
|
case MoveTarget.ALL_NEAR_ENEMIES:
|
|
case MoveTarget.ALL_ENEMIES:
|
|
case MoveTarget.USER_AND_ALLIES:
|
|
case MoveTarget.ALL:
|
|
case MoveTarget.USER_SIDE:
|
|
case MoveTarget.ENEMY_SIDE:
|
|
case MoveTarget.BOTH_SIDES:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Getter function that returns if the move targets itself or an ally
|
|
* @returns boolean
|
|
*/
|
|
|
|
isAllyTarget(): boolean {
|
|
switch (this.moveTarget) {
|
|
case MoveTarget.USER:
|
|
case MoveTarget.NEAR_ALLY:
|
|
case MoveTarget.ALLY:
|
|
case MoveTarget.USER_OR_NEAR_ALLY:
|
|
case MoveTarget.USER_AND_ALLIES:
|
|
case MoveTarget.USER_SIDE:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the move is immune to certain types.
|
|
* Currently looks at cases of Grass types with powder moves and Dark types with moves affected by Prankster.
|
|
* @param {Pokemon} user the source of this move
|
|
* @param {Pokemon} target the target of this move
|
|
* @param {Type} type the type of the move's target
|
|
* @returns boolean
|
|
*/
|
|
isTypeImmune(user: Pokemon, target: Pokemon, type: Type): boolean {
|
|
if (this.moveTarget === MoveTarget.USER) {
|
|
return false;
|
|
}
|
|
|
|
switch (type) {
|
|
case Type.GRASS:
|
|
if (this.hasFlag(MoveFlags.POWDER_MOVE)) {
|
|
return true;
|
|
}
|
|
break;
|
|
case Type.DARK:
|
|
if (user.hasAbility(Abilities.PRANKSTER) && this.category === MoveCategory.STATUS && (user.isPlayer() !== target.isPlayer())) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Adds a move condition to the move
|
|
* @param condition {@linkcode MoveCondition} or {@linkcode MoveConditionFunc}, appends to conditions array a new MoveCondition object
|
|
* @returns the called object {@linkcode Move}
|
|
*/
|
|
condition(condition: MoveCondition | MoveConditionFunc): this {
|
|
if (typeof condition === "function") {
|
|
condition = new MoveCondition(condition as MoveConditionFunc);
|
|
}
|
|
this.conditions.push(condition);
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Marks the move as "partial": appends texts to the move name
|
|
* @returns the called object {@linkcode Move}
|
|
*/
|
|
partial(): this {
|
|
this.nameAppend += " (P)";
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Marks the move as "unimplemented": appends texts to the move name
|
|
* @returns the called object {@linkcode Move}
|
|
*/
|
|
unimplemented(): this {
|
|
this.nameAppend += " (N)";
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the flags of the move
|
|
* @param flag {@linkcode MoveFlags}
|
|
* @param on a boolean, if True, then "ORs" the flag onto existing ones, if False then "XORs" the flag onto existing ones
|
|
*/
|
|
private setFlag(flag: MoveFlags, on: boolean): void {
|
|
// bitwise OR and bitwise XOR respectively
|
|
if (on) {
|
|
this.flags |= flag;
|
|
} else {
|
|
this.flags ^= flag;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.MAKES_CONTACT} flag for the calling Move
|
|
* @param makesContact The value (boolean) to set the flag to
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
makesContact(makesContact?: boolean): this {
|
|
this.setFlag(MoveFlags.MAKES_CONTACT, makesContact);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.IGNORE_PROTECT} flag for the calling Move
|
|
* @param ignoresProtect The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.CURSE}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
ignoresProtect(ignoresProtect?: boolean): this {
|
|
this.setFlag(MoveFlags.IGNORE_PROTECT, ignoresProtect);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.IGNORE_VIRTUAL} flag for the calling Move
|
|
* @param ignoresVirtual The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.NATURE_POWER}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
ignoresVirtual(ignoresVirtual?: boolean): this {
|
|
this.setFlag(MoveFlags.IGNORE_VIRTUAL, ignoresVirtual);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.SOUND_BASED} flag for the calling Move
|
|
* @param soundBased The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.UPROAR}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
soundBased(soundBased?: boolean): this {
|
|
this.setFlag(MoveFlags.SOUND_BASED, soundBased);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.HIDE_USER} flag for the calling Move
|
|
* @param hidesUser The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.TELEPORT}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
hidesUser(hidesUser?: boolean): this {
|
|
this.setFlag(MoveFlags.HIDE_USER, hidesUser);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.HIDE_TARGET} flag for the calling Move
|
|
* @param hidesTarget The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.WHIRLWIND}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
hidesTarget(hidesTarget?: boolean): this {
|
|
this.setFlag(MoveFlags.HIDE_TARGET, hidesTarget);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.BITING_MOVE} flag for the calling Move
|
|
* @param bitingMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.BITE}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
bitingMove(bitingMove?: boolean): this {
|
|
this.setFlag(MoveFlags.BITING_MOVE, bitingMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.PULSE_MOVE} flag for the calling Move
|
|
* @param pulseMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.WATER_PULSE}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
pulseMove(pulseMove?: boolean): this {
|
|
this.setFlag(MoveFlags.PULSE_MOVE, pulseMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.PUNCHING_MOVE} flag for the calling Move
|
|
* @param punchingMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.DRAIN_PUNCH}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
punchingMove(punchingMove?: boolean): this {
|
|
this.setFlag(MoveFlags.PUNCHING_MOVE, punchingMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.SLICING_MOVE} flag for the calling Move
|
|
* @param slicingMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.X_SCISSOR}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
slicingMove(slicingMove?: boolean): this {
|
|
this.setFlag(MoveFlags.SLICING_MOVE, slicingMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.RECKLESS_MOVE} flag for the calling Move
|
|
* @see {@linkcode Abilities.RECKLESS}
|
|
* @param recklessMove The value to set the flag to
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
recklessMove(recklessMove?: boolean): this {
|
|
this.setFlag(MoveFlags.RECKLESS_MOVE, recklessMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.BALLBOMB_MOVE} flag for the calling Move
|
|
* @param ballBombMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.ELECTRO_BALL}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
ballBombMove(ballBombMove?: boolean): this {
|
|
this.setFlag(MoveFlags.BALLBOMB_MOVE, ballBombMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.POWDER_MOVE} flag for the calling Move
|
|
* @param powderMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.STUN_SPORE}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
powderMove(powderMove?: boolean): this {
|
|
this.setFlag(MoveFlags.POWDER_MOVE, powderMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.DANCE_MOVE} flag for the calling Move
|
|
* @param danceMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.PETAL_DANCE}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
danceMove(danceMove?: boolean): this {
|
|
this.setFlag(MoveFlags.DANCE_MOVE, danceMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.WIND_MOVE} flag for the calling Move
|
|
* @param windMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.HURRICANE}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
windMove(windMove?: boolean): this {
|
|
this.setFlag(MoveFlags.WIND_MOVE, windMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.TRIAGE_MOVE} flag for the calling Move
|
|
* @param triageMove The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.ABSORB}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
triageMove(triageMove?: boolean): this {
|
|
this.setFlag(MoveFlags.TRIAGE_MOVE, triageMove);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.IGNORE_ABILITIES} flag for the calling Move
|
|
* @param ignoresAbilities sThe value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.SUNSTEEL_STRIKE}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
ignoresAbilities(ignoresAbilities?: boolean): this {
|
|
this.setFlag(MoveFlags.IGNORE_ABILITIES, ignoresAbilities);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.CHECK_ALL_HITS} flag for the calling Move
|
|
* @param checkAllHits The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.TRIPLE_AXEL}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
checkAllHits(checkAllHits?: boolean): this {
|
|
this.setFlag(MoveFlags.CHECK_ALL_HITS, checkAllHits);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the {@linkcode MoveFlags.REDIRECT_COUNTER} flag for the calling Move
|
|
* @param redirectCounter The value (boolean) to set the flag to
|
|
* example: @see {@linkcode Moves.METAL_BURST}
|
|
* @returns The {@linkcode Move} that called this function
|
|
*/
|
|
redirectCounter(redirectCounter?: boolean): this {
|
|
this.setFlag(MoveFlags.REDIRECT_COUNTER, redirectCounter);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Checks if the move flag applies to the pokemon(s) using/receiving the move
|
|
* @param flag {@linkcode MoveFlags} MoveFlag to check on user and/or target
|
|
* @param user {@linkcode Pokemon} the Pokemon using the move
|
|
* @param target {@linkcode Pokemon} the Pokemon receiving the move
|
|
* @returns boolean
|
|
*/
|
|
checkFlag(flag: MoveFlags, user: Pokemon, target: Pokemon): boolean {
|
|
// special cases below, eg: if the move flag is MAKES_CONTACT, and the user pokemon has an ability that ignores contact (like "Long Reach"), then overrides and move does not make contact
|
|
switch (flag) {
|
|
case MoveFlags.MAKES_CONTACT:
|
|
if (user.hasAbilityWithAttr(IgnoreContactAbAttr)) {
|
|
return false;
|
|
}
|
|
break;
|
|
case MoveFlags.IGNORE_ABILITIES:
|
|
if (user.hasAbilityWithAttr(MoveAbilityBypassAbAttr)) {
|
|
const abilityEffectsIgnored = new Utils.BooleanHolder(false);
|
|
applyAbAttrs(MoveAbilityBypassAbAttr, user, abilityEffectsIgnored, this);
|
|
if (abilityEffectsIgnored.value) {
|
|
return true;
|
|
}
|
|
}
|
|
case MoveFlags.IGNORE_PROTECT:
|
|
if (user.hasAbilityWithAttr(IgnoreProtectOnContactAbAttr) &&
|
|
this.checkFlag(MoveFlags.MAKES_CONTACT, user, target)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return !!(this.flags & flag);
|
|
}
|
|
|
|
/**
|
|
* Applies each {@linkcode MoveCondition} of this move to the params
|
|
* @param user {@linkcode Pokemon} to apply conditions to
|
|
* @param target {@linkcode Pokemon} to apply conditions to
|
|
* @param move {@linkcode Move} to apply conditions to
|
|
* @returns boolean: false if any of the apply()'s return false, else true
|
|
*/
|
|
applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean {
|
|
for (const condition of this.conditions) {
|
|
if (!condition.apply(user, target, move)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Sees if, given the target pokemon, a move fails on it (by looking at each {@linkcode MoveAttr} of this move
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} receiving the move
|
|
* @param move {@linkcode Move} using the move
|
|
* @param cancelled {@linkcode Utils.BooleanHolder} to hold boolean value
|
|
* @returns string of the failed text, or null
|
|
*/
|
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
for (const attr of this.attrs) {
|
|
const failedText = attr.getFailedText(user, target, move, cancelled);
|
|
if (failedText !== null) {
|
|
return failedText;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Calculates the userBenefitScore across all the attributes and conditions
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} receiving the move
|
|
* @param move {@linkcode Move} using the move
|
|
* @returns integer representing the total benefitScore
|
|
*/
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
let score = 0;
|
|
|
|
for (const attr of this.attrs) {
|
|
score += attr.getUserBenefitScore(user, target, move);
|
|
}
|
|
|
|
for (const condition of this.conditions) {
|
|
score += condition.getUserBenefitScore(user, target, move);
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
/**
|
|
* Calculates the targetBenefitScore across all the attributes
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} receiving the move
|
|
* @param move {@linkcode Move} using the move
|
|
* @returns integer representing the total benefitScore
|
|
*/
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
let score = 0;
|
|
|
|
for (const attr of this.attrs) {
|
|
// conditionals to check if the move is self targeting (if so then you are applying the move to yourself, not the target)
|
|
score += attr.getTargetBenefitScore(user, !attr.selfTarget ? target : user, move) * (target !== user && attr.selfTarget ? -1 : 1);
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
/**
|
|
* Calculates the accuracy of a move in battle based on various conditions and attributes.
|
|
*
|
|
* @param user {@linkcode Pokemon} The Pokémon using the move.
|
|
* @param target {@linkcode Pokemon} The Pokémon being targeted by the move.
|
|
* @returns The calculated accuracy of the move.
|
|
*/
|
|
calculateBattleAccuracy(user: Pokemon, target: Pokemon) {
|
|
const moveAccuracy = new Utils.NumberHolder(this.accuracy);
|
|
|
|
applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy);
|
|
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, moveAccuracy);
|
|
|
|
if (moveAccuracy.value === -1) {
|
|
return moveAccuracy.value;
|
|
}
|
|
|
|
const isOhko = this.hasAttr(OneHitKOAccuracyAttr);
|
|
|
|
if (!isOhko) {
|
|
user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
|
|
}
|
|
|
|
if (user.scene.arena.weather?.weatherType === WeatherType.FOG) {
|
|
/**
|
|
* The 0.9 multiplier is PokeRogue-only implementation, Bulbapedia uses 3/5
|
|
* See Fog {@link https://bulbapedia.bulbagarden.net/wiki/Fog}
|
|
*/
|
|
moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9);
|
|
}
|
|
|
|
if (!isOhko && user.scene.arena.getTag(ArenaTagType.GRAVITY)) {
|
|
moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67);
|
|
}
|
|
|
|
return moveAccuracy.value;
|
|
}
|
|
|
|
/**
|
|
* Calculates the power of a move in battle based on various conditions and attributes.
|
|
*
|
|
* @param source {@linkcode Pokemon} The Pokémon using the move.
|
|
* @param target {@linkcode Pokemon} The Pokémon being targeted by the move.
|
|
* @returns The calculated power of the move.
|
|
*/
|
|
calculateBattlePower(source: Pokemon, target: Pokemon): number {
|
|
const power = new Utils.NumberHolder(this.power);
|
|
|
|
const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1);
|
|
applyPreAttackAbAttrs(MoveTypeChangeAttr, source, target, this, typeChangeMovePowerMultiplier);
|
|
|
|
const sourceTeraType = source.getTeraType();
|
|
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !source.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
|
|
power.value = 60;
|
|
}
|
|
|
|
applyPreAttackAbAttrs(VariableMovePowerAbAttr, source, target, this, power);
|
|
|
|
if (source.getAlly()) {
|
|
applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, source.getAlly(), target, this, power);
|
|
}
|
|
|
|
const fieldAuras = new Set(
|
|
source.scene.getField(true)
|
|
.map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[])
|
|
.flat(),
|
|
);
|
|
for (const aura of fieldAuras) {
|
|
// The only relevant values are `move` and the `power` holder
|
|
aura.applyPreAttack(null, null, null, this, [power]);
|
|
}
|
|
|
|
const alliedField: Pokemon[] = source instanceof PlayerPokemon ? source.scene.getPlayerField() : source.scene.getEnemyField();
|
|
alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, target, this, power));
|
|
|
|
power.value *= typeChangeMovePowerMultiplier.value;
|
|
|
|
const typeBoost = source.findTag(t => t instanceof TypeBoostTag && t.boostedType === this.type) as TypeBoostTag;
|
|
if (typeBoost) {
|
|
power.value *= typeBoost.boostValue;
|
|
}
|
|
|
|
if (source.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && this.type === Type.GROUND && this.moveTarget === MoveTarget.ALL_NEAR_OTHERS) {
|
|
power.value /= 2;
|
|
}
|
|
|
|
applyMoveAttrs(VariablePowerAttr, source, target, this, power);
|
|
|
|
source.scene.applyModifiers(PokemonMultiHitModifier, source.isPlayer(), source, new Utils.IntegerHolder(0), power);
|
|
|
|
if (!this.hasAttr(TypelessAttr)) {
|
|
source.scene.arena.applyTags(WeakenMoveTypeTag, this.type, power);
|
|
source.scene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, this.type, power);
|
|
}
|
|
|
|
if (source.getTag(HelpingHandTag)) {
|
|
power.value *= 1.5;
|
|
}
|
|
|
|
return power.value;
|
|
}
|
|
}
|
|
|
|
export class AttackMove extends Move {
|
|
constructor(id: Moves, type: Type, category: MoveCategory, power: integer, accuracy: integer, pp: integer, chance: integer, priority: integer, generation: integer) {
|
|
super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation);
|
|
|
|
/**
|
|
* {@link https://bulbapedia.bulbagarden.net/wiki/Freeze_(status_condition)}
|
|
* > All damaging Fire-type moves can now thaw a frozen target, regardless of whether or not they have a chance to burn;
|
|
*/
|
|
if (this.type === Type.FIRE) {
|
|
this.addAttr(new HealStatusEffectAttr(false, StatusEffect.FREEZE));
|
|
}
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
let ret = super.getTargetBenefitScore(user, target, move);
|
|
|
|
let attackScore = 0;
|
|
|
|
const effectiveness = target.getAttackTypeEffectiveness(this.type, user);
|
|
attackScore = Math.pow(effectiveness - 1, 2) * effectiveness < 1 ? -2 : 2;
|
|
if (attackScore) {
|
|
if (this.category === MoveCategory.PHYSICAL) {
|
|
const atk = new Utils.IntegerHolder(user.getBattleStat(Stat.ATK, target));
|
|
applyMoveAttrs(VariableAtkAttr, user, target, move, atk);
|
|
if (atk.value > user.getBattleStat(Stat.SPATK, target)) {
|
|
const statRatio = user.getBattleStat(Stat.SPATK, target) / atk.value;
|
|
if (statRatio <= 0.75) {
|
|
attackScore *= 2;
|
|
} else if (statRatio <= 0.875) {
|
|
attackScore *= 1.5;
|
|
}
|
|
}
|
|
} else {
|
|
const spAtk = new Utils.IntegerHolder(user.getBattleStat(Stat.SPATK, target));
|
|
applyMoveAttrs(VariableAtkAttr, user, target, move, spAtk);
|
|
if (spAtk.value > user.getBattleStat(Stat.ATK, target)) {
|
|
const statRatio = user.getBattleStat(Stat.ATK, target) / spAtk.value;
|
|
if (statRatio <= 0.75) {
|
|
attackScore *= 2;
|
|
} else if (statRatio <= 0.875) {
|
|
attackScore *= 1.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
const power = new Utils.NumberHolder(this.power);
|
|
applyMoveAttrs(VariablePowerAttr, user, target, move, power);
|
|
|
|
attackScore += Math.floor(power.value / 5);
|
|
}
|
|
|
|
ret -= attackScore;
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
export class StatusMove extends Move {
|
|
constructor(id: Moves, type: Type, accuracy: integer, pp: integer, chance: integer, priority: integer, generation: integer) {
|
|
super(id, type, MoveCategory.STATUS, MoveTarget.NEAR_OTHER, -1, accuracy, pp, chance, priority, generation);
|
|
}
|
|
}
|
|
|
|
export class SelfStatusMove extends Move {
|
|
constructor(id: Moves, type: Type, accuracy: integer, pp: integer, chance: integer, priority: integer, generation: integer) {
|
|
super(id, type, MoveCategory.STATUS, MoveTarget.USER, -1, accuracy, pp, chance, priority, generation);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Base class defining all {@linkcode Move} Attributes
|
|
* @abstract
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export abstract class MoveAttr {
|
|
/** Should this {@linkcode Move} target the user? */
|
|
public selfTarget: boolean;
|
|
|
|
constructor(selfTarget: boolean = false) {
|
|
this.selfTarget = selfTarget;
|
|
}
|
|
|
|
/**
|
|
* Applies move attributes
|
|
* @see {@linkcode applyMoveAttrsInternal}
|
|
* @virtual
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args Set of unique arguments needed by this attribute
|
|
* @returns true if application of the ability succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @virtual
|
|
* @returns the {@linkcode MoveCondition} or {@linkcode MoveConditionFunc} for this {@linkcode Move}
|
|
*/
|
|
getCondition(): MoveCondition | MoveConditionFunc {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @virtual
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param cancelled {@linkcode Utils.BooleanHolder} which stores if the move should fail
|
|
* @returns the string representing failure of this {@linkcode Move}
|
|
*/
|
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Used by the Enemy AI to rank an attack based on a given user
|
|
* @see {@linkcode EnemyPokemon.getNextMove}
|
|
* @virtual
|
|
*/
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Used by the Enemy AI to rank an attack based on a given target
|
|
* @see {@linkcode EnemyPokemon.getNextMove}
|
|
* @virtual
|
|
*/
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export enum MoveEffectTrigger {
|
|
PRE_APPLY,
|
|
POST_APPLY,
|
|
HIT,
|
|
/** Triggers one time after all target effects have applied */
|
|
POST_TARGET,
|
|
}
|
|
|
|
/** Base class defining all Move Effect Attributes
|
|
* @extends MoveAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class MoveEffectAttr extends MoveAttr {
|
|
/** Defines when this effect should trigger in the move's effect order
|
|
* @see {@linkcode phases.MoveEffectPhase.start}
|
|
*/
|
|
public trigger: MoveEffectTrigger;
|
|
/** Should this effect only apply on the first hit? */
|
|
public firstHitOnly: boolean;
|
|
/** Should this effect only apply on the last hit? */
|
|
public lastHitOnly: boolean;
|
|
/** Should this effect only apply on the first target hit? */
|
|
public firstTargetOnly: boolean;
|
|
|
|
constructor(selfTarget?: boolean, trigger?: MoveEffectTrigger, firstHitOnly: boolean = false, lastHitOnly: boolean = false, firstTargetOnly: boolean = false) {
|
|
super(selfTarget);
|
|
this.trigger = trigger !== undefined ? trigger : MoveEffectTrigger.POST_APPLY;
|
|
this.firstHitOnly = firstHitOnly;
|
|
this.lastHitOnly = lastHitOnly;
|
|
this.firstTargetOnly = firstTargetOnly;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the {@linkcode Move}'s effects are valid to {@linkcode apply}
|
|
* @virtual
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args Set of unique arguments needed by this attribute
|
|
* @returns true if basic application of the ability attribute should be possible
|
|
*/
|
|
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
|
return !! (this.selfTarget ? user.hp && !user.getTag(BattlerTagType.FRENZY) : target.hp)
|
|
&& (this.selfTarget || !target.getTag(BattlerTagType.PROTECTED) ||
|
|
move.checkFlag(MoveFlags.IGNORE_PROTECT, user, target));
|
|
}
|
|
|
|
/** Applies move effects so long as they are able based on {@linkcode canApply} */
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
|
return this.canApply(user, target, move, args);
|
|
}
|
|
|
|
/**
|
|
* Gets the used move's additional effect chance.
|
|
* If user's ability has MoveEffectChanceMultiplierAbAttr or IgnoreMoveEffectsAbAttr modifies the base chance.
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target {@linkcode Pokemon} target of this move
|
|
* @param move {@linkcode Move} being used
|
|
* @param selfEffect {@linkcode Boolean} if move targets user.
|
|
* @returns Move chance value.
|
|
*/
|
|
getMoveChance(user: Pokemon, target: Pokemon, move: Move, selfEffect?: Boolean, showAbility?: Boolean): integer {
|
|
const moveChance = new Utils.NumberHolder(move.chance);
|
|
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, moveChance, move, target, selfEffect, showAbility);
|
|
if (!selfEffect) {
|
|
applyPreDefendAbAttrs(IgnoreMoveEffectsAbAttr, target, user, null, null, moveChance);
|
|
}
|
|
return moveChance.value;
|
|
}
|
|
}
|
|
|
|
export class PreMoveMessageAttr extends MoveAttr {
|
|
private message: string | ((user: Pokemon, target: Pokemon, move: Move) => string);
|
|
|
|
constructor(message: string | ((user: Pokemon, target: Pokemon, move: Move) => string)) {
|
|
super();
|
|
this.message = message;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const message = typeof this.message === "string"
|
|
? this.message as string
|
|
: this.message(user, target, move);
|
|
if (message) {
|
|
user.scene.queueMessage(message, 500);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class StatusMoveTypeImmunityAttr extends MoveAttr {
|
|
public immuneType: Type;
|
|
|
|
constructor(immuneType: Type) {
|
|
super(false);
|
|
|
|
this.immuneType = immuneType;
|
|
}
|
|
}
|
|
|
|
export class IgnoreOpponentStatChangesAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = 0;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class HighCritAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value++;
|
|
|
|
return true;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
export class CritOnlyAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.BooleanHolder).value = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
export class FixedDamageAttr extends MoveAttr {
|
|
private damage: integer;
|
|
|
|
constructor(damage: integer) {
|
|
super();
|
|
|
|
this.damage = damage;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = this.getDamage(user, target, move);
|
|
|
|
return true;
|
|
}
|
|
|
|
getDamage(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return this.damage;
|
|
}
|
|
}
|
|
|
|
export class UserHpDamageAttr extends FixedDamageAttr {
|
|
constructor() {
|
|
super(0);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = user.hp;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class TargetHalfHpDamageAttr extends FixedDamageAttr {
|
|
constructor() {
|
|
super(0);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = Math.max(Math.floor(target.hp / 2), 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
return target.getHpRatio() > 0.5 ? Math.floor(((target.getHpRatio() - 0.5) * -24) + 4) : -20;
|
|
}
|
|
}
|
|
|
|
export class MatchHpAttr extends FixedDamageAttr {
|
|
constructor() {
|
|
super(0);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = target.hp - user.hp;
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => user.hp <= target.hp;
|
|
}
|
|
|
|
// TODO
|
|
/*getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return 0;
|
|
}*/
|
|
}
|
|
|
|
type MoveFilter = (move: Move) => boolean;
|
|
|
|
export class CounterDamageAttr extends FixedDamageAttr {
|
|
private moveFilter: MoveFilter;
|
|
private multiplier: number;
|
|
|
|
constructor(moveFilter: MoveFilter, multiplier: integer) {
|
|
super(0);
|
|
|
|
this.moveFilter = moveFilter;
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const damage = user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).reduce((total: integer, ar: AttackMoveResult) => total + ar.damage, 0);
|
|
(args[0] as Utils.IntegerHolder).value = Math.floor(Math.max(damage * this.multiplier, 1));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !!user.turnData.attacksReceived.filter(ar => this.moveFilter(allMoves[ar.move])).length;
|
|
}
|
|
}
|
|
|
|
export class LevelDamageAttr extends FixedDamageAttr {
|
|
constructor() {
|
|
super(0);
|
|
}
|
|
|
|
getDamage(user: Pokemon, target: Pokemon, move: Move): number {
|
|
return user.level;
|
|
}
|
|
}
|
|
|
|
export class RandomLevelDamageAttr extends FixedDamageAttr {
|
|
constructor() {
|
|
super(0);
|
|
}
|
|
|
|
getDamage(user: Pokemon, target: Pokemon, move: Move): number {
|
|
return Math.max(Math.floor(user.level * (user.randSeedIntRange(50, 150) * 0.01)), 1);
|
|
}
|
|
}
|
|
|
|
export class ModifiedDamageAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const initialDamage = args[0] as Utils.IntegerHolder;
|
|
initialDamage.value = this.getModifiedDamage(user, target, move, initialDamage.value);
|
|
|
|
return true;
|
|
}
|
|
|
|
getModifiedDamage(user: Pokemon, target: Pokemon, move: Move, damage: integer): integer {
|
|
return damage;
|
|
}
|
|
}
|
|
|
|
export class SurviveDamageAttr extends ModifiedDamageAttr {
|
|
getModifiedDamage(user: Pokemon, target: Pokemon, move: Move, damage: number): number {
|
|
return Math.min(damage, target.hp - 1);
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => target.hp > 1;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return target.hp > 1 ? 0 : -20;
|
|
}
|
|
}
|
|
|
|
export class RecoilAttr extends MoveEffectAttr {
|
|
private useHp: boolean;
|
|
private damageRatio: number;
|
|
private unblockable: boolean;
|
|
|
|
constructor(useHp: boolean = false, damageRatio: number = 0.25, unblockable: boolean = false) {
|
|
super(true, MoveEffectTrigger.POST_APPLY, false, true);
|
|
|
|
this.useHp = useHp;
|
|
this.damageRatio = damageRatio;
|
|
this.unblockable = unblockable;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
if (!this.unblockable) {
|
|
applyAbAttrs(BlockRecoilDamageAttr, user, cancelled);
|
|
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
|
|
}
|
|
|
|
if (cancelled.value) {
|
|
return false;
|
|
}
|
|
|
|
const recoilDamage = Math.max(Math.floor((!this.useHp ? user.turnData.damageDealt : user.getMaxHp()) * this.damageRatio),
|
|
user.turnData.damageDealt ? 1 : 0);
|
|
if (!recoilDamage) {
|
|
return false;
|
|
}
|
|
|
|
if (cancelled.value) {
|
|
return false;
|
|
}
|
|
|
|
user.damageAndUpdate(recoilDamage, HitResult.OTHER, false, true, true);
|
|
user.scene.queueMessage(i18next.t("moveTriggers:hitWithRecoil", {pokemonName: getPokemonNameWithAffix(user)}));
|
|
user.turnData.damageTaken += recoilDamage;
|
|
|
|
return true;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return Math.floor((move.power / 5) / -4);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Attribute used for moves which self KO the user regardless if the move hits a target
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
**/
|
|
export class SacrificialAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true, MoveEffectTrigger.POST_TARGET);
|
|
}
|
|
|
|
/**
|
|
* Deals damage to the user equal to their current hp
|
|
* @param user {@linkcode Pokemon} that used the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
**/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
user.damageAndUpdate(user.hp, HitResult.OTHER, false, true, true);
|
|
user.turnData.damageTaken += user.hp;
|
|
|
|
return true;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
if (user.isBoss()) {
|
|
return -20;
|
|
}
|
|
return Math.ceil(((1 - user.getHpRatio()) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type, user) - 0.5));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves which self KO the user but only if the move hits a target
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
**/
|
|
export class SacrificialAttrOnHit extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true, MoveEffectTrigger.HIT);
|
|
}
|
|
|
|
/**
|
|
* Deals damage to the user equal to their current hp if the move lands
|
|
* @param user {@linkcode Pokemon} that used the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
**/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
// If the move fails to hit a target, then the user does not faint and the function returns false
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
user.damageAndUpdate(user.hp, HitResult.OTHER, false, true, true);
|
|
user.turnData.damageTaken += user.hp;
|
|
|
|
return true;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
if (user.isBoss()) {
|
|
return -20;
|
|
}
|
|
return Math.ceil(((1 - user.getHpRatio()) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type, user) - 0.5));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves which cut the user's Max HP in half.
|
|
* Triggers using {@linkcode MoveEffectTrigger.POST_TARGET}.
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class HalfSacrificialAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true, MoveEffectTrigger.POST_TARGET);
|
|
}
|
|
|
|
/**
|
|
* Cut's the user's Max HP in half and displays the appropriate recoil message
|
|
* @param user {@linkcode Pokemon} that used the move
|
|
* @param target N/A
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
// Check to see if the Pokemon has an ability that blocks non-direct damage
|
|
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
|
|
if (!cancelled.value) {
|
|
user.damageAndUpdate(Math.ceil(user.getMaxHp()/2), HitResult.OTHER, false, true, true);
|
|
user.scene.queueMessage(i18next.t("moveTriggers:cutHpPowerUpMove", {pokemonName: getPokemonNameWithAffix(user)})); // Queue recoil message
|
|
}
|
|
return true;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
if (user.isBoss()) {
|
|
return -10;
|
|
}
|
|
return Math.ceil(((1 - user.getHpRatio()/2) * 10 - 10) * (target.getAttackTypeEffectiveness(move.type, user) - 0.5));
|
|
}
|
|
}
|
|
|
|
export enum MultiHitType {
|
|
_2,
|
|
_2_TO_5,
|
|
_3,
|
|
_10,
|
|
BEAT_UP,
|
|
}
|
|
|
|
/**
|
|
* Heals the user or target by {@linkcode healRatio} depending on the value of {@linkcode selfTarget}
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class HealAttr extends MoveEffectAttr {
|
|
/** The percentage of {@linkcode Stat.HP} to heal */
|
|
private healRatio: number;
|
|
/** Should an animation be shown? */
|
|
private showAnim: boolean;
|
|
|
|
constructor(healRatio?: number, showAnim?: boolean, selfTarget?: boolean) {
|
|
super(selfTarget === undefined || selfTarget);
|
|
|
|
this.healRatio = healRatio || 1;
|
|
this.showAnim = !!showAnim;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
this.addHealPhase(this.selfTarget ? user : target, this.healRatio);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates a new {@linkcode PokemonHealPhase}.
|
|
* This heals the target and shows the appropriate message.
|
|
*/
|
|
addHealPhase(target: Pokemon, healRatio: number) {
|
|
target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(),
|
|
Math.max(Math.floor(target.getMaxHp() * healRatio), 1), getPokemonMessage(target, " \nhad its HP restored."), true, !this.showAnim));
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
const score = ((1 - (this.selfTarget ? user : target).getHpRatio()) * 20) - this.healRatio * 10;
|
|
return Math.round(score / (1 - this.healRatio / 2));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cures the user's party of non-volatile status conditions, ie. Heal Bell, Aromatherapy
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class PartyStatusCureAttr extends MoveEffectAttr {
|
|
/** Message to display after using move */
|
|
private message: string;
|
|
/** Skips mons with this ability, ie. Soundproof */
|
|
private abilityCondition: Abilities;
|
|
|
|
constructor(message: string, abilityCondition: Abilities) {
|
|
super();
|
|
|
|
this.message = message;
|
|
this.abilityCondition = abilityCondition;
|
|
}
|
|
|
|
//The same as MoveEffectAttr.canApply, except it doesn't check for the target's HP.
|
|
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
|
const isTargetValid =
|
|
(this.selfTarget && user.hp && !user.getTag(BattlerTagType.FRENZY)) ||
|
|
(!this.selfTarget && (!target.getTag(BattlerTagType.PROTECTED) || move.hasFlag(MoveFlags.IGNORE_PROTECT)));
|
|
return !!isTargetValid;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!this.canApply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
this.addPartyCurePhase(user);
|
|
}
|
|
|
|
addPartyCurePhase(user: Pokemon) {
|
|
user.scene.unshiftPhase(new PartyStatusCurePhase(user.scene, user, this.message, this.abilityCondition));
|
|
}
|
|
}
|
|
|
|
export class SacrificialFullRestoreAttr extends SacrificialAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
// We don't know which party member will be chosen, so pick the highest max HP in the party
|
|
const maxPartyMemberHp = user.scene.getParty().map(p => p.getMaxHp()).reduce((maxHp: integer, hp: integer) => Math.max(hp, maxHp), 0);
|
|
|
|
user.scene.pushPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(),
|
|
maxPartyMemberHp, getPokemonMessage(user, "'s Healing Wish\nwas granted!"), true, false, false, true), true);
|
|
|
|
return true;
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return -20;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => user.scene.getParty().filter(p => p.isActive()).length > user.scene.currentBattle.getBattlerCount();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves which ignore type-based debuffs from weather, namely Hydro Steam.
|
|
* Called during damage calculation after getting said debuff from getAttackTypeMultiplier in the Pokemon class.
|
|
* @extends MoveAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class IgnoreWeatherTypeDebuffAttr extends MoveAttr {
|
|
/** The {@linkcode WeatherType} this move ignores */
|
|
public weather: WeatherType;
|
|
|
|
constructor(weather: WeatherType) {
|
|
super();
|
|
this.weather = weather;
|
|
}
|
|
/**
|
|
* Changes the type-based weather modifier if this move's power would be reduced by it
|
|
* @param user {@linkcode Pokemon} that used the move
|
|
* @param target N/A
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args [0] {@linkcode Utils.NumberHolder} for arenaAttackTypeMultiplier
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const weatherModifier=args[0] as Utils.NumberHolder;
|
|
//If the type-based attack power modifier due to weather (e.g. Water moves in Sun) is below 1, set it to 1
|
|
if (user.scene.arena.weather?.weatherType === this.weather) {
|
|
weatherModifier.value = Math.max(weatherModifier.value, 1);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export abstract class WeatherHealAttr extends HealAttr {
|
|
constructor() {
|
|
super(0.5);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
let healRatio = 0.5;
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
|
|
const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE;
|
|
healRatio = this.getWeatherHealRatio(weatherType);
|
|
}
|
|
this.addHealPhase(user, healRatio);
|
|
return true;
|
|
}
|
|
|
|
abstract getWeatherHealRatio(weatherType: WeatherType): number;
|
|
}
|
|
|
|
export class PlantHealAttr extends WeatherHealAttr {
|
|
getWeatherHealRatio(weatherType: WeatherType): number {
|
|
switch (weatherType) {
|
|
case WeatherType.SUNNY:
|
|
case WeatherType.HARSH_SUN:
|
|
return 2 / 3;
|
|
case WeatherType.RAIN:
|
|
case WeatherType.SANDSTORM:
|
|
case WeatherType.HAIL:
|
|
case WeatherType.SNOW:
|
|
case WeatherType.HEAVY_RAIN:
|
|
return 0.25;
|
|
default:
|
|
return 0.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class SandHealAttr extends WeatherHealAttr {
|
|
getWeatherHealRatio(weatherType: WeatherType): number {
|
|
switch (weatherType) {
|
|
case WeatherType.SANDSTORM:
|
|
return 2 / 3;
|
|
default:
|
|
return 0.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Heals the target or the user by either {@linkcode normalHealRatio} or {@linkcode boostedHealRatio}
|
|
* depending on the evaluation of {@linkcode condition}
|
|
* @extends HealAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class BoostHealAttr extends HealAttr {
|
|
/** Healing received when {@linkcode condition} is false */
|
|
private normalHealRatio?: number;
|
|
/** Healing received when {@linkcode condition} is true */
|
|
private boostedHealRatio?: number;
|
|
/** The lambda expression to check against when boosting the healing value */
|
|
private condition?: MoveConditionFunc;
|
|
|
|
constructor(normalHealRatio?: number, boostedHealRatio?: number, showAnim?: boolean, selfTarget?: boolean, condition?: MoveConditionFunc) {
|
|
super(normalHealRatio, showAnim, selfTarget);
|
|
this.normalHealRatio = normalHealRatio;
|
|
this.boostedHealRatio = boostedHealRatio;
|
|
this.condition = condition;
|
|
}
|
|
|
|
/**
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args N/A
|
|
* @returns true if the move was successful
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const healRatio = this.condition(user, target, move) ? this.boostedHealRatio : this.normalHealRatio;
|
|
this.addHealPhase(target, healRatio);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Heals the target only if it is the ally
|
|
* @extends HealAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class HealOnAllyAttr extends HealAttr {
|
|
/**
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (user.getAlly() === target) {
|
|
super.apply(user, target, move, args);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Heals user as a side effect of a move that hits a target.
|
|
* Healing is based on {@linkcode healRatio} * the amount of damage dealt or a stat of the target.
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
* @see {@linkcode getUserBenefitScore}
|
|
*/
|
|
export class HitHealAttr extends MoveEffectAttr {
|
|
private healRatio: number;
|
|
private message: string;
|
|
private healStat: Stat;
|
|
|
|
constructor(healRatio?: number, healStat?: Stat) {
|
|
super(true, MoveEffectTrigger.HIT);
|
|
|
|
this.healRatio = healRatio || 0.5;
|
|
this.healStat = healStat || null;
|
|
}
|
|
/**
|
|
* Heals the user the determined amount and possibly displays a message about regaining health.
|
|
* If the target has the {@linkcode ReverseDrainAbAttr}, all healing is instead converted
|
|
* to damage to the user.
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target {@linkcode Pokemon} target of this move
|
|
* @param move {@linkcode Move} being used
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
let healAmount = 0;
|
|
let message = "";
|
|
const reverseDrain = target.hasAbilityWithAttr(ReverseDrainAbAttr, false);
|
|
if (this.healStat) {
|
|
// Strength Sap formula
|
|
healAmount = target.getBattleStat(this.healStat);
|
|
message = i18next.t("battle:drainMessage", {pokemonName: getPokemonNameWithAffix(target)});
|
|
} else {
|
|
// Default healing formula used by draining moves like Absorb, Draining Kiss, Bitter Blade, etc.
|
|
healAmount = Math.max(Math.floor(user.turnData.currDamageDealt * this.healRatio), 1);
|
|
message = i18next.t("battle:regainHealth", {pokemonName: getPokemonNameWithAffix(user)});
|
|
}
|
|
if (reverseDrain) {
|
|
if (user.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
|
|
healAmount = 0;
|
|
message = null;
|
|
} else {
|
|
user.turnData.damageTaken += healAmount;
|
|
healAmount = healAmount * -1;
|
|
message = null;
|
|
}
|
|
}
|
|
user.scene.unshiftPhase(new PokemonHealPhase(user.scene, user.getBattlerIndex(), healAmount, message, false, true));
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Used by the Enemy AI to rank an attack based on a given user
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target {@linkcode Pokemon} target of this move
|
|
* @param move {@linkcode Move} being used
|
|
* @returns an integer. Higher means enemy is more likely to use that move.
|
|
*/
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
if (this.healStat) {
|
|
const healAmount = target.getBattleStat(this.healStat);
|
|
return Math.floor(Math.max(0, (Math.min(1, (healAmount+user.hp)/user.getMaxHp() - 0.33))) / user.getHpRatio());
|
|
}
|
|
return Math.floor(Math.max((1 - user.getHpRatio()) - 0.33, 0) * (move.power / 4));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves that change priority in a turn given a condition,
|
|
* e.g. Grassy Glide
|
|
* Called when move order is calculated in {@linkcode TurnStartPhase}.
|
|
* @extends MoveAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class IncrementMovePriorityAttr extends MoveAttr {
|
|
/** The condition for a move's priority being incremented */
|
|
private moveIncrementFunc: (pokemon: Pokemon, target:Pokemon, move: Move) => boolean;
|
|
/** The amount to increment priority by, if condition passes. */
|
|
private increaseAmount: integer;
|
|
|
|
constructor(moveIncrementFunc: (pokemon: Pokemon, target:Pokemon, move: Move) => boolean, increaseAmount = 1) {
|
|
super();
|
|
|
|
this.moveIncrementFunc = moveIncrementFunc;
|
|
this.increaseAmount = increaseAmount;
|
|
}
|
|
|
|
/**
|
|
* Increments move priority by set amount if condition passes
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target {@linkcode Pokemon} target of this move
|
|
* @param move {@linkcode Move} being used
|
|
* @param args [0] {@linkcode Utils.IntegerHolder} for move priority.
|
|
* @returns true if function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!this.moveIncrementFunc(user, target, move)) {
|
|
return false;
|
|
}
|
|
|
|
(args[0] as Utils.IntegerHolder).value += this.increaseAmount;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for attack moves that hit multiple times per use, e.g. Bullet Seed.
|
|
*
|
|
* Applied at the beginning of {@linkcode MoveEffectPhase}.
|
|
*
|
|
* @extends MoveAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class MultiHitAttr extends MoveAttr {
|
|
private multiHitType: MultiHitType;
|
|
|
|
constructor(multiHitType?: MultiHitType) {
|
|
super();
|
|
|
|
this.multiHitType = multiHitType !== undefined ? multiHitType : MultiHitType._2_TO_5;
|
|
}
|
|
|
|
/**
|
|
* Set the hit count of an attack based on this attribute instance's {@linkcode MultiHitType}.
|
|
* If the target has an immunity to this attack's types, the hit count will always be 1.
|
|
*
|
|
* @param user {@linkcode Pokemon} that used the attack
|
|
* @param target {@linkcode Pokemon} targeted by the attack
|
|
* @param move {@linkcode Move} being used
|
|
* @param args [0] {@linkcode Utils.IntegerHolder} storing the hit count of the attack
|
|
* @returns True
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
let hitTimes: integer;
|
|
|
|
if (target.getAttackMoveEffectiveness(user, new PokemonMove(move.id)) === 0) {
|
|
// If there is a type immunity, the attack will stop no matter what
|
|
hitTimes = 1;
|
|
} else {
|
|
const hitType = new Utils.IntegerHolder(this.multiHitType);
|
|
applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType);
|
|
this.multiHitType = hitType.value;
|
|
hitTimes = this.getHitCount(user, target);
|
|
}
|
|
|
|
(args[0] as Utils.IntegerHolder).value = hitTimes;
|
|
return true;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
return -5;
|
|
}
|
|
|
|
/**
|
|
* Calculate the number of hits that an attack should have given this attribute's
|
|
* {@linkcode MultiHitType}.
|
|
*
|
|
* @param user {@linkcode Pokemon} using the attack
|
|
* @param target {@linkcode Pokemon} targeted by the attack
|
|
* @returns The number of hits this attack should deal
|
|
*/
|
|
getHitCount(user: Pokemon, target: Pokemon): integer {
|
|
switch (this.multiHitType) {
|
|
case MultiHitType._2_TO_5:
|
|
{
|
|
const rand = user.randSeedInt(16);
|
|
const hitValue = new Utils.IntegerHolder(rand);
|
|
applyAbAttrs(MaxMultiHitAbAttr, user, null, hitValue);
|
|
if (hitValue.value >= 10) {
|
|
return 2;
|
|
} else if (hitValue.value >= 4) {
|
|
return 3;
|
|
} else if (hitValue.value >= 2) {
|
|
return 4;
|
|
} else {
|
|
return 5;
|
|
}
|
|
}
|
|
case MultiHitType._2:
|
|
return 2;
|
|
break;
|
|
case MultiHitType._3:
|
|
return 3;
|
|
break;
|
|
case MultiHitType._10:
|
|
return 10;
|
|
break;
|
|
case MultiHitType.BEAT_UP:
|
|
const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty();
|
|
// No status means the ally pokemon can contribute to Beat Up
|
|
return party.reduce((total, pokemon) => {
|
|
return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1);
|
|
}, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ChangeMultiHitTypeAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
//const hitType = args[0] as Utils.NumberHolder;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class WaterShurikenMultiHitTypeAttr extends ChangeMultiHitTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (user.species.speciesId === Species.GRENINJA && user.hasAbility(Abilities.BATTLE_BOND) && user.formIndex === 2) {
|
|
(args[0] as Utils.IntegerHolder).value = MultiHitType._3;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class StatusEffectAttr extends MoveEffectAttr {
|
|
public effect: StatusEffect;
|
|
public cureTurn: integer;
|
|
public overrideStatus: boolean;
|
|
|
|
constructor(effect: StatusEffect, selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) {
|
|
super(selfTarget, MoveEffectTrigger.HIT);
|
|
|
|
this.effect = effect;
|
|
this.cureTurn = cureTurn;
|
|
this.overrideStatus = !!overrideStatus;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
|
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
|
|
if (statusCheck) {
|
|
const pokemon = this.selfTarget ? user : target;
|
|
if (pokemon.status) {
|
|
if (this.overrideStatus) {
|
|
pokemon.resetStatus();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
if ((!pokemon.status || (pokemon.status.effect === this.effect && moveChance < 0))
|
|
&& pokemon.trySetStatus(this.effect, true, user, this.cureTurn)) {
|
|
applyPostAttackAbAttrs(ConfusionOnStatusEffectAbAttr, user, target, move, null,this.effect);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
|
|
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
|
|
}
|
|
}
|
|
|
|
export class MultiStatusEffectAttr extends StatusEffectAttr {
|
|
public effects: StatusEffect[];
|
|
|
|
constructor(effects: StatusEffect[], selfTarget?: boolean, cureTurn?: integer, overrideStatus?: boolean) {
|
|
super(effects[0], selfTarget, cureTurn, overrideStatus);
|
|
this.effects = effects;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
this.effect = Utils.randSeedItem(this.effects);
|
|
const result = super.apply(user, target, move, args);
|
|
return result;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, false);
|
|
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(this.effect, true, false, user) ? Math.floor(moveChance * -0.1) : 0;
|
|
}
|
|
}
|
|
|
|
export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(false, MoveEffectTrigger.HIT);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const statusToApply: StatusEffect = user.status?.effect;
|
|
|
|
if (target.status) {
|
|
return false;
|
|
}
|
|
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) {
|
|
const statusAfflictResult = target.trySetStatus(statusToApply, true, user);
|
|
if (statusAfflictResult) {
|
|
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
|
|
user.resetStatus();
|
|
user.updateInfo();
|
|
}
|
|
return statusAfflictResult;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
return !(this.selfTarget ? user : target).status && (this.selfTarget ? user : target).canSetStatus(user.status?.effect, true, false, user) ? Math.floor(move.chance * -0.1) : 0;
|
|
}
|
|
}
|
|
/**
|
|
* The following needs to be implemented for Thief
|
|
* "If the user faints due to the target's Ability (Rough Skin or Iron Barbs) or held Rocky Helmet, it cannot remove the target's held item."
|
|
* "If Knock Off causes a Pokémon with the Sticky Hold Ability to faint, it can now remove that Pokémon's held item."
|
|
*/
|
|
export class StealHeldItemChanceAttr extends MoveEffectAttr {
|
|
private chance: number;
|
|
|
|
constructor(chance: number) {
|
|
super(false, MoveEffectTrigger.HIT);
|
|
this.chance = chance;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise<boolean>(resolve => {
|
|
const rand = Phaser.Math.RND.realInRange(0, 1);
|
|
if (rand >= this.chance) {
|
|
return resolve(false);
|
|
}
|
|
const heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable);
|
|
if (heldItems.length) {
|
|
const poolType = target.isPlayer() ? ModifierPoolType.PLAYER : target.hasTrainer() ? ModifierPoolType.TRAINER : ModifierPoolType.WILD;
|
|
const highestItemTier = heldItems.map(m => m.type.getOrInferTier(poolType)).reduce((highestTier, tier) => Math.max(tier, highestTier), 0);
|
|
const tierHeldItems = heldItems.filter(m => m.type.getOrInferTier(poolType) === highestItemTier);
|
|
const stolenItem = tierHeldItems[user.randSeedInt(tierHeldItems.length)];
|
|
user.scene.tryTransferHeldItemModifier(stolenItem, user, false).then(success => {
|
|
if (success) {
|
|
user.scene.queueMessage(i18next.t("moveTriggers:stoleItem", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: stolenItem.type.name}));
|
|
}
|
|
resolve(success);
|
|
});
|
|
return;
|
|
}
|
|
|
|
resolve(false);
|
|
});
|
|
}
|
|
|
|
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
|
return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
|
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
const heldItems = this.getTargetHeldItems(target);
|
|
return heldItems.length ? 5 : 0;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
const heldItems = this.getTargetHeldItems(target);
|
|
return heldItems.length ? -5 : 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a random held item (or berry) from target.
|
|
* Used for Incinerate and Knock Off.
|
|
* Not Implemented Cases: (Same applies for Thief)
|
|
* "If the user faints due to the target's Ability (Rough Skin or Iron Barbs) or held Rocky Helmet, it cannot remove the target's held item."
|
|
* "If Knock Off causes a Pokémon with the Sticky Hold Ability to faint, it can now remove that Pokémon's held item."
|
|
*/
|
|
export class RemoveHeldItemAttr extends MoveEffectAttr {
|
|
|
|
/** Optional restriction for item pool to berries only i.e. Differentiating Incinerate and Knock Off */
|
|
private berriesOnly: boolean;
|
|
|
|
constructor(berriesOnly: boolean) {
|
|
super(false, MoveEffectTrigger.HIT);
|
|
this.berriesOnly = berriesOnly;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param user {@linkcode Pokemon} that used the move
|
|
* @param target Target {@linkcode Pokemon} that the moves applies to
|
|
* @param move {@linkcode Move} that is used
|
|
* @param args N/A
|
|
* @returns {boolean} True if an item was removed
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
|
|
return false;
|
|
}
|
|
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft
|
|
|
|
if (cancelled.value === true) {
|
|
return false;
|
|
}
|
|
|
|
// Considers entire transferrable item pool by default (Knock Off). Otherwise berries only if specified (Incinerate).
|
|
let heldItems = this.getTargetHeldItems(target).filter(i => i.isTransferrable);
|
|
|
|
if (this.berriesOnly) {
|
|
heldItems = heldItems.filter(m => m instanceof BerryModifier && m.pokemonId === target.id, target.isPlayer());
|
|
}
|
|
|
|
if (heldItems.length) {
|
|
const removedItem = heldItems[user.randSeedInt(heldItems.length)];
|
|
|
|
// Decrease item amount and update icon
|
|
!--removedItem.stackCount;
|
|
target.scene.updateModifiers(target.isPlayer());
|
|
|
|
if (this.berriesOnly) {
|
|
user.scene.queueMessage(i18next.t("moveTriggers:incineratedItem", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name}));
|
|
} else {
|
|
user.scene.queueMessage(i18next.t("moveTriggers:knockedOffItem", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), itemName: removedItem.type.name}));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
getTargetHeldItems(target: Pokemon): PokemonHeldItemModifier[] {
|
|
return target.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
|
|
&& m.pokemonId === target.id, target.isPlayer()) as PokemonHeldItemModifier[];
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
const heldItems = this.getTargetHeldItems(target);
|
|
return heldItems.length ? 5 : 0;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
const heldItems = this.getTargetHeldItems(target);
|
|
return heldItems.length ? -5 : 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute that causes targets of the move to eat a berry. Used for Teatime, Stuff Cheeks
|
|
*/
|
|
export class EatBerryAttr extends MoveEffectAttr {
|
|
protected chosenBerry: BerryModifier;
|
|
constructor() {
|
|
super(true, MoveEffectTrigger.HIT);
|
|
this.chosenBerry = undefined;
|
|
}
|
|
/**
|
|
* Causes the target to eat a berry.
|
|
* @param user {@linkcode Pokemon} Pokemon that used the move
|
|
* @param target {@linkcode Pokemon} Pokemon that will eat a berry
|
|
* @param move {@linkcode Move} The move being used
|
|
* @param args Unused
|
|
* @returns {boolean} true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const heldBerries = this.getTargetHeldBerries(target);
|
|
if (heldBerries.length <= 0) {
|
|
return false;
|
|
}
|
|
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
|
|
const preserve = new Utils.BooleanHolder(false);
|
|
target.scene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation
|
|
if (!preserve.value) {
|
|
this.reduceBerryModifier(target);
|
|
}
|
|
this.eatBerry(target);
|
|
return true;
|
|
}
|
|
|
|
getTargetHeldBerries(target: Pokemon): BerryModifier[] {
|
|
return target.scene.findModifiers(m => m instanceof BerryModifier
|
|
&& (m as BerryModifier).pokemonId === target.id, target.isPlayer()) as BerryModifier[];
|
|
}
|
|
|
|
reduceBerryModifier(target: Pokemon) {
|
|
if (this.chosenBerry.stackCount === 1) {
|
|
target.scene.removeModifier(this.chosenBerry, !target.isPlayer());
|
|
} else {
|
|
this.chosenBerry.stackCount--;
|
|
}
|
|
target.scene.updateModifiers(target.isPlayer());
|
|
}
|
|
|
|
eatBerry(consumer: Pokemon) {
|
|
getBerryEffectFunc(this.chosenBerry.berryType)(consumer); // consumer eats the berry
|
|
applyAbAttrs(HealFromBerryUseAbAttr, consumer, new Utils.BooleanHolder(false));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves that steal a random berry from the target. The user then eats the stolen berry.
|
|
* Used for Pluck & Bug Bite.
|
|
*/
|
|
export class StealEatBerryAttr extends EatBerryAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
/**
|
|
* User steals a random berry from the target and then eats it.
|
|
* @param {Pokemon} user Pokemon that used the move and will eat the stolen berry
|
|
* @param {Pokemon} target Pokemon that will have its berry stolen
|
|
* @param {Move} move Move being used
|
|
* @param {any[]} args Unused
|
|
* @returns {boolean} true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft
|
|
if (cancelled.value === true) {
|
|
return false;
|
|
}
|
|
|
|
const heldBerries = this.getTargetHeldBerries(target);
|
|
if (heldBerries.length <= 0) {
|
|
return false;
|
|
}
|
|
// if the target has berries, pick a random berry and steal it
|
|
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
|
|
const message = i18next.t("battle:stealEatBerry", {pokemonName: user.name, targetName: target.name, berryName: this.chosenBerry.type.name});
|
|
user.scene.queueMessage(message);
|
|
this.reduceBerryModifier(target);
|
|
this.eatBerry(user);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Move attribute that signals that the move should cure a status effect
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply()}
|
|
*/
|
|
export class HealStatusEffectAttr extends MoveEffectAttr {
|
|
/** List of Status Effects to cure */
|
|
private effects: StatusEffect[];
|
|
|
|
/**
|
|
* @param selfTarget - Whether this move targets the user
|
|
* @param ...effects - List of status effects to cure
|
|
*/
|
|
constructor(selfTarget: boolean, ...effects: StatusEffect[]) {
|
|
super(selfTarget, MoveEffectTrigger.POST_APPLY, false, true);
|
|
|
|
this.effects = effects;
|
|
}
|
|
|
|
/**
|
|
* @param user {@linkcode Pokemon} source of the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move the {@linkcode Move} being used
|
|
* @returns true if the status is cured
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
// Special edge case for shield dust blocking Sparkling Aria curing burn
|
|
const moveTargets = getMoveTargets(user, move.id);
|
|
if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === Moves.SPARKLING_ARIA && moveTargets.targets.length === 1) {
|
|
return false;
|
|
}
|
|
|
|
const pokemon = this.selfTarget ? user : target;
|
|
if (pokemon.status && this.effects.includes(pokemon.status.effect)) {
|
|
pokemon.scene.queueMessage(getStatusEffectHealText(pokemon.status.effect, getPokemonNameWithAffix(pokemon)));
|
|
pokemon.resetStatus();
|
|
pokemon.updateInfo();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
isOfEffect(effect: StatusEffect): boolean {
|
|
return this.effects.includes(effect);
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return user.status ? 10 : 0;
|
|
}
|
|
}
|
|
|
|
export class BypassSleepAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (user.status?.effect === StatusEffect.SLEEP) {
|
|
user.addTag(BattlerTagType.BYPASS_SLEEP, 1, move.id, user.id);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves that bypass the burn damage reduction of physical moves, currently only facade
|
|
* Called during damage calculation
|
|
* @extends MoveAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class BypassBurnDamageReductionAttr extends MoveAttr {
|
|
/** Prevents the move's damage from being reduced by burn
|
|
* @param user N/A
|
|
* @param target N/A
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args [0] {@linkcode Utils.BooleanHolder} for burnDamageReductionCancelled
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.BooleanHolder).value = true;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class WeatherChangeAttr extends MoveEffectAttr {
|
|
private weatherType: WeatherType;
|
|
|
|
constructor(weatherType: WeatherType) {
|
|
super();
|
|
|
|
this.weatherType = weatherType;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
return user.scene.arena.trySetWeather(this.weatherType, true);
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !user.scene.arena.weather || (user.scene.arena.weather.weatherType !== this.weatherType && !user.scene.arena.weather.isImmutable());
|
|
}
|
|
}
|
|
|
|
export class ClearWeatherAttr extends MoveEffectAttr {
|
|
private weatherType: WeatherType;
|
|
|
|
constructor(weatherType: WeatherType) {
|
|
super();
|
|
|
|
this.weatherType = weatherType;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (user.scene.arena.weather?.weatherType === this.weatherType) {
|
|
return user.scene.arena.trySetWeather(WeatherType.NONE, true);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class TerrainChangeAttr extends MoveEffectAttr {
|
|
private terrainType: TerrainType;
|
|
|
|
constructor(terrainType: TerrainType) {
|
|
super();
|
|
|
|
this.terrainType = terrainType;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
return user.scene.arena.trySetTerrain(this.terrainType, true, true);
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !user.scene.arena.terrain || (user.scene.arena.terrain.terrainType !== this.terrainType);
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
// TODO: Expand on this
|
|
return user.scene.arena.terrain ? 0 : 6;
|
|
}
|
|
}
|
|
|
|
export class ClearTerrainAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
return user.scene.arena.trySetTerrain(TerrainType.NONE, true, true);
|
|
}
|
|
}
|
|
|
|
export class OneHitKOAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (target.isBossImmune()) {
|
|
return false;
|
|
}
|
|
|
|
(args[0] as Utils.BooleanHolder).value = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
applyAbAttrs(BlockOneHitKOAbAttr, target, cancelled);
|
|
return !cancelled.value && user.level >= target.level;
|
|
};
|
|
}
|
|
}
|
|
|
|
export class OverrideMoveEffectAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
|
//const overridden = args[0] as Utils.BooleanHolder;
|
|
//const virtual = arg[1] as boolean;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class ChargeAttr extends OverrideMoveEffectAttr {
|
|
public chargeAnim: ChargeAnim;
|
|
private chargeText: string;
|
|
private tagType: BattlerTagType;
|
|
private chargeEffect: boolean;
|
|
public sameTurn: boolean;
|
|
public followUpPriority: integer;
|
|
|
|
constructor(chargeAnim: ChargeAnim, chargeText: string, tagType?: BattlerTagType, chargeEffect: boolean = false, sameTurn: boolean = false, followUpPriority?: integer) {
|
|
super();
|
|
|
|
this.chargeAnim = chargeAnim;
|
|
this.chargeText = chargeText;
|
|
this.tagType = tagType;
|
|
this.chargeEffect = chargeEffect;
|
|
this.sameTurn = sameTurn;
|
|
this.followUpPriority = followUpPriority;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
const lastMove = user.getLastXMoves().find(() => true);
|
|
if (!lastMove || lastMove.move !== move.id || (lastMove.result !== MoveResult.OTHER && (this.sameTurn || lastMove.turn !== user.scene.currentBattle.turn))) {
|
|
(args[0] as Utils.BooleanHolder).value = true;
|
|
new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, () => {
|
|
user.scene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user)));
|
|
if (this.tagType) {
|
|
user.addTag(this.tagType, 1, move.id, user.id);
|
|
}
|
|
if (this.chargeEffect) {
|
|
applyMoveAttrs(MoveEffectAttr, user, target, move);
|
|
}
|
|
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
|
|
user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true });
|
|
if (this.sameTurn) {
|
|
let movesetMove = user.moveset.find(m => m.moveId === move.id);
|
|
if (!movesetMove) { // account for any move that calls a ChargeAttr move when the ChargeAttr move does not exist in moveset
|
|
movesetMove = new PokemonMove(move.id, 0, 0, true);
|
|
}
|
|
user.scene.pushMovePhase(new MovePhase(user.scene, user, [ target.getBattlerIndex() ], movesetMove, true), this.followUpPriority);
|
|
}
|
|
user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id);
|
|
resolve(true);
|
|
});
|
|
} else {
|
|
user.lapseTag(BattlerTagType.CHARGING);
|
|
resolve(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
usedChargeEffect(user: Pokemon, target: Pokemon, move: Move): boolean {
|
|
if (!this.chargeEffect) {
|
|
return false;
|
|
}
|
|
// Account for move history being populated when this function is called
|
|
const lastMoves = user.getLastXMoves(2);
|
|
return lastMoves.length === 2 && lastMoves[1].move === move.id && lastMoves[1].result === MoveResult.OTHER;
|
|
}
|
|
}
|
|
|
|
export class SunlightChargeAttr extends ChargeAttr {
|
|
constructor(chargeAnim: ChargeAnim, chargeText: string) {
|
|
super(chargeAnim, chargeText);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
const weatherType = user.scene.arena.weather?.weatherType;
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene) && (weatherType === WeatherType.SUNNY || weatherType === WeatherType.HARSH_SUN)) {
|
|
resolve(false);
|
|
} else {
|
|
super.apply(user, target, move, args).then(result => resolve(result));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export class ElectroShotChargeAttr extends ChargeAttr {
|
|
private statIncreaseApplied: boolean;
|
|
constructor() {
|
|
super(ChargeAnim.ELECTRO_SHOT_CHARGING, i18next.t("moveTriggers:absorbedElectricity", {pokemonName: "{USER}"}), null, true);
|
|
// Add a flag because ChargeAttr skills use themselves twice instead of once over one-to-two turns
|
|
this.statIncreaseApplied = false;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
const weatherType = user.scene.arena.weather?.weatherType;
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene) && (weatherType === WeatherType.RAIN || weatherType === WeatherType.HEAVY_RAIN)) {
|
|
// Apply the SPATK increase every call when used in the rain
|
|
const statChangeAttr = new StatChangeAttr(BattleStat.SPATK, 1, true);
|
|
statChangeAttr.apply(user, target, move, args);
|
|
// After the SPATK is raised, execute the move resolution e.g. deal damage
|
|
resolve(false);
|
|
} else {
|
|
if (!this.statIncreaseApplied) {
|
|
// Apply the SPATK increase only if it hasn't been applied before e.g. on the first turn charge up animation
|
|
const statChangeAttr = new StatChangeAttr(BattleStat.SPATK, 1, true);
|
|
statChangeAttr.apply(user, target, move, args);
|
|
// Set the flag to true so that on the following turn it doesn't raise SPATK a second time
|
|
this.statIncreaseApplied = true;
|
|
}
|
|
super.apply(user, target, move, args).then(result => {
|
|
if (!result) {
|
|
// On the second turn, reset the statIncreaseApplied flag without applying the SPATK increase
|
|
this.statIncreaseApplied = false;
|
|
}
|
|
resolve(result);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export class DelayedAttackAttr extends OverrideMoveEffectAttr {
|
|
public tagType: ArenaTagType;
|
|
public chargeAnim: ChargeAnim;
|
|
private chargeText: string;
|
|
|
|
constructor(tagType: ArenaTagType, chargeAnim: ChargeAnim, chargeText: string) {
|
|
super();
|
|
|
|
this.tagType = tagType;
|
|
this.chargeAnim = chargeAnim;
|
|
this.chargeText = chargeText;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
if (args.length < 2 || !args[1]) {
|
|
new MoveChargeAnim(this.chargeAnim, move.id, user).play(user.scene, () => {
|
|
(args[0] as Utils.BooleanHolder).value = true;
|
|
user.scene.queueMessage(this.chargeText.replace("{TARGET}", getPokemonNameWithAffix(target)).replace("{USER}", getPokemonNameWithAffix(user)));
|
|
user.pushMoveHistory({ move: move.id, targets: [ target.getBattlerIndex() ], result: MoveResult.OTHER });
|
|
user.scene.arena.addTag(this.tagType, 3, move.id, user.id, ArenaTagSide.BOTH, false, target.getBattlerIndex());
|
|
|
|
resolve(true);
|
|
});
|
|
} else {
|
|
user.scene.ui.showText(i18next.t("moveTriggers:tookMoveAttack", {pokemonName: getPokemonNameWithAffix(user.scene.getPokemonById(target.id)), moveName: move.name}), null, () => resolve(true));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export class StatChangeAttr extends MoveEffectAttr {
|
|
public stats: BattleStat[];
|
|
public levels: integer;
|
|
private condition: MoveConditionFunc;
|
|
private showMessage: boolean;
|
|
|
|
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false, moveEffectTrigger: MoveEffectTrigger = MoveEffectTrigger.HIT, firstTargetOnly: boolean = false) {
|
|
super(selfTarget, moveEffectTrigger, firstHitOnly, false, firstTargetOnly);
|
|
this.stats = typeof(stats) === "number"
|
|
? [ stats as BattleStat ]
|
|
: stats as BattleStat[];
|
|
this.levels = levels;
|
|
this.condition = condition || null;
|
|
this.showMessage = showMessage;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
|
if (!super.apply(user, target, move, args) || (this.condition && !this.condition(user, target, move))) {
|
|
return false;
|
|
}
|
|
|
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
|
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
|
const levels = this.getLevels(user);
|
|
user.scene.unshiftPhase(new StatChangePhase(user.scene, (this.selfTarget ? user : target).getBattlerIndex(), this.selfTarget, this.stats, levels, this.showMessage));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getLevels(_user: Pokemon): integer {
|
|
return this.levels;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
let ret = 0;
|
|
const moveLevels = this.getLevels(user);
|
|
for (const stat of this.stats) {
|
|
let levels = moveLevels;
|
|
if (levels > 0) {
|
|
levels = Math.min(target.summonData.battleStats[stat] + levels, 6) - target.summonData.battleStats[stat];
|
|
} else {
|
|
levels = Math.max(target.summonData.battleStats[stat] + levels, -6) - target.summonData.battleStats[stat];
|
|
}
|
|
let noEffect = false;
|
|
switch (stat) {
|
|
case BattleStat.ATK:
|
|
if (this.selfTarget) {
|
|
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL);
|
|
}
|
|
break;
|
|
case BattleStat.DEF:
|
|
if (!this.selfTarget) {
|
|
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL);
|
|
}
|
|
break;
|
|
case BattleStat.SPATK:
|
|
if (this.selfTarget) {
|
|
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL);
|
|
}
|
|
break;
|
|
case BattleStat.SPDEF:
|
|
if (!this.selfTarget) {
|
|
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL);
|
|
}
|
|
break;
|
|
}
|
|
if (noEffect) {
|
|
continue;
|
|
}
|
|
ret += (levels * 4) + (levels > 0 ? -2 : 2);
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
export class PostVictoryStatChangeAttr extends MoveAttr {
|
|
private stats: BattleStat[];
|
|
private levels: integer;
|
|
private condition: MoveConditionFunc;
|
|
private showMessage: boolean;
|
|
|
|
constructor(stats: BattleStat | BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) {
|
|
super();
|
|
this.stats = typeof(stats) === "number"
|
|
? [ stats as BattleStat ]
|
|
: stats as BattleStat[];
|
|
this.levels = levels;
|
|
this.condition = condition || null;
|
|
this.showMessage = showMessage;
|
|
}
|
|
applyPostVictory(user: Pokemon, target: Pokemon, move: Move): void {
|
|
if (this.condition && !this.condition(user, target, move)) {
|
|
return;
|
|
}
|
|
const statChangeAttr = new StatChangeAttr(this.stats, this.levels, this.showMessage);
|
|
statChangeAttr.apply(user, target, move, undefined);
|
|
}
|
|
}
|
|
|
|
export class AcupressureStatChangeAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean | Promise<boolean> {
|
|
let randStats = [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD, BattleStat.ACC, BattleStat.EVA ];
|
|
randStats = randStats.filter(s => target.summonData.battleStats[s] < 6);
|
|
if (randStats.length > 0) {
|
|
const boostStat = [randStats[Utils.randInt(randStats.length)]];
|
|
user.scene.unshiftPhase(new StatChangePhase(user.scene, target.getBattlerIndex(), this.selfTarget, boostStat, 2));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class GrowthStatChangeAttr extends StatChangeAttr {
|
|
constructor() {
|
|
super([ BattleStat.ATK, BattleStat.SPATK ], 1, true);
|
|
}
|
|
|
|
getLevels(user: Pokemon): number {
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
|
|
const weatherType = user.scene.arena.weather?.weatherType;
|
|
if (weatherType === WeatherType.SUNNY || weatherType === WeatherType.HARSH_SUN) {
|
|
return this.levels + 1;
|
|
}
|
|
}
|
|
return this.levels;
|
|
}
|
|
}
|
|
|
|
export class HalfHpStatMaxAttr extends StatChangeAttr {
|
|
constructor(stat: BattleStat) {
|
|
super(stat, 12, true, null, false);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise<boolean>(resolve => {
|
|
user.damageAndUpdate(Math.floor(user.getMaxHp() / 2), HitResult.OTHER, false, true);
|
|
user.updateInfo().then(() => {
|
|
const ret = super.apply(user, target, move, args);
|
|
user.scene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", {pokemonName: getPokemonNameWithAffix(user), statName: getBattleStatName(this.stats[BattleStat.ATK])}));
|
|
resolve(ret);
|
|
});
|
|
});
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => user.getHpRatio() > 0.5 && user.summonData.battleStats[this.stats[BattleStat.ATK]] < 6;
|
|
}
|
|
|
|
// TODO: Add benefit score that considers HP cut
|
|
}
|
|
|
|
export class CutHpStatBoostAttr extends StatChangeAttr {
|
|
private cutRatio: integer;
|
|
|
|
constructor(stat: BattleStat | BattleStat[], levels: integer, cutRatio: integer) {
|
|
super(stat, levels, true, null, true);
|
|
|
|
this.cutRatio = cutRatio;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise<boolean>(resolve => {
|
|
user.damageAndUpdate(Math.floor(user.getMaxHp() / this.cutRatio), HitResult.OTHER, false, true);
|
|
user.updateInfo().then(() => {
|
|
const ret = super.apply(user, target, move, args);
|
|
resolve(ret);
|
|
});
|
|
});
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => user.getHpRatio() > 1 / this.cutRatio;
|
|
}
|
|
}
|
|
|
|
export class CopyStatsAttr extends MoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
for (let s = 0; s < target.summonData.battleStats.length; s++) {
|
|
user.summonData.battleStats[s] = target.summonData.battleStats[s];
|
|
}
|
|
if (target.getTag(BattlerTagType.CRIT_BOOST)) {
|
|
user.addTag(BattlerTagType.CRIT_BOOST, 0, move.id);
|
|
} else {
|
|
user.removeTag(BattlerTagType.CRIT_BOOST);
|
|
}
|
|
target.updateInfo();
|
|
user.updateInfo();
|
|
target.scene.queueMessage(i18next.t("moveTriggers:copiedStatChanges", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target)}));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class InvertStatsAttr extends MoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
for (let s = 0; s < target.summonData.battleStats.length; s++) {
|
|
target.summonData.battleStats[s] *= -1;
|
|
}
|
|
target.updateInfo();
|
|
user.updateInfo();
|
|
|
|
target.scene.queueMessage(getPokemonMessage(target, "'s stat changes\nwere all reversed!"));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class ResetStatsAttr extends MoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
for (let s = 0; s < target.summonData.battleStats.length; s++) {
|
|
target.summonData.battleStats[s] = 0;
|
|
}
|
|
target.updateInfo();
|
|
user.updateInfo();
|
|
|
|
target.scene.queueMessage(getPokemonMessage(target, "'s stat changes\nwere eliminated!"));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves which swap the user and the target's stat changes.
|
|
*/
|
|
export class SwapStatsAttr extends MoveEffectAttr {
|
|
/**
|
|
* Swaps the user and the target's stat changes.
|
|
* @param user Pokemon that used the move
|
|
* @param target The target of the move
|
|
* @param move Move with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any []): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
} //Exits if the move can't apply
|
|
let priorBoost : integer; //For storing a stat boost
|
|
for (let s = 0; s < target.summonData.battleStats.length; s++) {
|
|
priorBoost = user.summonData.battleStats[s]; //Store user stat boost
|
|
user.summonData.battleStats[s] = target.summonData.battleStats[s]; //Applies target boost to self
|
|
target.summonData.battleStats[s] = priorBoost; //Applies stored boost to target
|
|
}
|
|
target.updateInfo();
|
|
user.updateInfo();
|
|
target.scene.queueMessage(i18next.t("moveTriggers:switchedStatChanges", {pokemonName: getPokemonNameWithAffix(user)}));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class HpSplitAttr extends MoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return resolve(false);
|
|
}
|
|
|
|
const infoUpdates = [];
|
|
|
|
const hpValue = Math.floor((target.hp + user.hp) / 2);
|
|
if (user.hp < hpValue) {
|
|
const healing = user.heal(hpValue - user.hp);
|
|
if (healing) {
|
|
user.scene.damageNumberHandler.add(user, healing, HitResult.HEAL);
|
|
}
|
|
} else if (user.hp > hpValue) {
|
|
const damage = user.damage(user.hp - hpValue, true);
|
|
if (damage) {
|
|
user.scene.damageNumberHandler.add(user, damage);
|
|
}
|
|
}
|
|
infoUpdates.push(user.updateInfo());
|
|
|
|
if (target.hp < hpValue) {
|
|
const healing = target.heal(hpValue - target.hp);
|
|
if (healing) {
|
|
user.scene.damageNumberHandler.add(user, healing, HitResult.HEAL);
|
|
}
|
|
} else if (target.hp > hpValue) {
|
|
const damage = target.damage(target.hp - hpValue, true);
|
|
if (damage) {
|
|
target.scene.damageNumberHandler.add(target, damage);
|
|
}
|
|
}
|
|
infoUpdates.push(target.updateInfo());
|
|
|
|
return Promise.all(infoUpdates).then(() => resolve(true));
|
|
});
|
|
}
|
|
}
|
|
|
|
export class VariablePowerAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
//const power = args[0] as Utils.NumberHolder;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class LessPPMorePowerAttr extends VariablePowerAttr {
|
|
/**
|
|
* Power up moves when less PP user has
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target {@linkcode Pokemon} target of this move
|
|
* @param move {@linkcode Move} being used
|
|
* @param args [0] {@linkcode Utils.NumberHolder} of power
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const ppMax = move.pp;
|
|
const ppUsed = user.moveset.find((m) => m.moveId === move.id).ppUsed;
|
|
|
|
let ppRemains = ppMax - ppUsed;
|
|
/** Reduce to 0 to avoid negative numbers if user has 1PP before attack and target has Ability.PRESSURE */
|
|
if (ppRemains < 0) {
|
|
ppRemains = 0;
|
|
}
|
|
|
|
const power = args[0] as Utils.NumberHolder;
|
|
|
|
switch (ppRemains) {
|
|
case 0:
|
|
power.value = 200;
|
|
break;
|
|
case 1:
|
|
power.value = 80;
|
|
break;
|
|
case 2:
|
|
power.value = 60;
|
|
break;
|
|
case 3:
|
|
power.value = 50;
|
|
break;
|
|
default:
|
|
power.value = 40;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class MovePowerMultiplierAttr extends VariablePowerAttr {
|
|
private powerMultiplierFunc: (user: Pokemon, target: Pokemon, move: Move) => number;
|
|
|
|
constructor(powerMultiplier: (user: Pokemon, target: Pokemon, move: Move) => number) {
|
|
super();
|
|
|
|
this.powerMultiplierFunc = powerMultiplier;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
power.value *= this.powerMultiplierFunc(user, target, move);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to calculate the the base power of an ally's hit when using Beat Up.
|
|
* @param user The Pokemon that used Beat Up.
|
|
* @param allyIndex The party position of the ally contributing to Beat Up.
|
|
* @returns The base power of the Beat Up hit.
|
|
*/
|
|
const beatUpFunc = (user: Pokemon, allyIndex: number): number => {
|
|
const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty();
|
|
|
|
for (let i = allyIndex; i < party.length; i++) {
|
|
const pokemon = party[i];
|
|
|
|
// The user contributes to Beat Up regardless of status condition.
|
|
// Allies can contribute only if they do not have a non-volatile status condition.
|
|
if (pokemon.id !== user.id && pokemon?.status && pokemon.status.effect !== StatusEffect.NONE) {
|
|
continue;
|
|
}
|
|
return (pokemon.species.getBaseStat(Stat.ATK) / 10) + 5;
|
|
}
|
|
};
|
|
|
|
export class BeatUpAttr extends VariablePowerAttr {
|
|
|
|
/**
|
|
* Gets the next party member to contribute to a Beat Up hit, and calculates the base power for it.
|
|
* @param user Pokemon that used the move
|
|
* @param _target N/A
|
|
* @param _move Move with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
|
|
const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty();
|
|
const allyCount = party.filter(pokemon => {
|
|
return pokemon.id === user.id || !pokemon.status?.effect;
|
|
}).length;
|
|
const allyIndex = (user.turnData.hitCount - user.turnData.hitsLeft) % allyCount;
|
|
power.value = beatUpFunc(user, allyIndex);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const doublePowerChanceMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => {
|
|
let message: string = null;
|
|
user.scene.executeWithSeedOffset(() => {
|
|
const rand = Utils.randSeedInt(100);
|
|
if (rand < move.chance) {
|
|
message = i18next.t("moveTriggers:goingAllOutForAttack", {pokemonName: getPokemonNameWithAffix(user)});
|
|
}
|
|
}, user.scene.currentBattle.turn << 6, user.scene.waveSeed);
|
|
return message;
|
|
};
|
|
|
|
export class DoublePowerChanceAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
let rand: integer;
|
|
user.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), user.scene.currentBattle.turn << 6, user.scene.waveSeed);
|
|
if (rand < move.chance) {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
power.value *= 2;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export abstract class ConsecutiveUsePowerMultiplierAttr extends MovePowerMultiplierAttr {
|
|
constructor(limit: integer, resetOnFail: boolean, resetOnLimit?: boolean, ...comboMoves: Moves[]) {
|
|
super((user: Pokemon, target: Pokemon, move: Move): number => {
|
|
const moveHistory = user.getLastXMoves(limit + 1).slice(1);
|
|
|
|
let count = 0;
|
|
let turnMove: TurnMove;
|
|
|
|
while (((turnMove = moveHistory.shift())?.move === move.id || (comboMoves.length && comboMoves.includes(turnMove?.move))) && (!resetOnFail || turnMove.result === MoveResult.SUCCESS)) {
|
|
if (count < (limit - 1)) {
|
|
count++;
|
|
} else if (resetOnLimit) {
|
|
count = 0;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return this.getMultiplier(count);
|
|
});
|
|
}
|
|
|
|
abstract getMultiplier(count: integer): number;
|
|
}
|
|
|
|
export class ConsecutiveUseDoublePowerAttr extends ConsecutiveUsePowerMultiplierAttr {
|
|
getMultiplier(count: number): number {
|
|
return Math.pow(2, count);
|
|
}
|
|
}
|
|
|
|
export class ConsecutiveUseMultiBasePowerAttr extends ConsecutiveUsePowerMultiplierAttr {
|
|
getMultiplier(count: number): number {
|
|
return (count + 1);
|
|
}
|
|
}
|
|
|
|
export class WeightPowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
|
|
const targetWeight = target.getWeight();
|
|
const weightThresholds = [ 10, 25, 50, 100, 200 ];
|
|
|
|
let w = 0;
|
|
while (targetWeight >= weightThresholds[w]) {
|
|
if (++w === weightThresholds.length) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
power.value = (w + 1) * 20;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for Electro Ball move.
|
|
* @extends VariablePowerAttr
|
|
* @see {@linkcode apply}
|
|
**/
|
|
export class ElectroBallPowerAttr extends VariablePowerAttr {
|
|
/**
|
|
* Move that deals more damage the faster {@linkcode BattleStat.SPD}
|
|
* the user is compared to the target.
|
|
* @param user Pokemon that used the move
|
|
* @param target The target of the move
|
|
* @param move Move with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
|
|
const statRatio = target.getBattleStat(Stat.SPD) / user.getBattleStat(Stat.SPD);
|
|
const statThresholds = [ 0.25, 1 / 3, 0.5, 1, -1 ];
|
|
const statThresholdPowers = [ 150, 120, 80, 60, 40 ];
|
|
|
|
let w = 0;
|
|
while (w < statThresholds.length - 1 && statRatio > statThresholds[w]) {
|
|
if (++w === statThresholds.length) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
power.value = statThresholdPowers[w];
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Attribute used for Gyro Ball move.
|
|
* @extends VariablePowerAttr
|
|
* @see {@linkcode apply}
|
|
**/
|
|
export class GyroBallPowerAttr extends VariablePowerAttr {
|
|
/**
|
|
* Move that deals more damage the slower {@linkcode BattleStat.SPD}
|
|
* the user is compared to the target.
|
|
* @param user Pokemon that used the move
|
|
* @param target The target of the move
|
|
* @param move Move with this attribute
|
|
* @param args N/A
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
const userSpeed = user.getBattleStat(Stat.SPD);
|
|
if (userSpeed < 1) {
|
|
// Gen 6+ always have 1 base power
|
|
power.value = 1;
|
|
return true;
|
|
}
|
|
|
|
power.value = Math.floor(Math.min(150, 25 * target.getBattleStat(Stat.SPD) / userSpeed + 1));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class LowHpPowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
const hpRatio = user.getHpRatio();
|
|
|
|
switch (true) {
|
|
case (hpRatio < 0.0417):
|
|
power.value = 200;
|
|
break;
|
|
case (hpRatio < 0.1042):
|
|
power.value = 150;
|
|
break;
|
|
case (hpRatio < 0.2083):
|
|
power.value = 100;
|
|
break;
|
|
case (hpRatio < 0.3542):
|
|
power.value = 80;
|
|
break;
|
|
case (hpRatio < 0.6875):
|
|
power.value = 40;
|
|
break;
|
|
default:
|
|
power.value = 20;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class CompareWeightPowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
const userWeight = user.getWeight();
|
|
const targetWeight = target.getWeight();
|
|
|
|
if (!userWeight || userWeight === 0) {
|
|
return false;
|
|
}
|
|
|
|
const relativeWeight = (targetWeight / userWeight) * 100;
|
|
|
|
switch (true) {
|
|
case (relativeWeight < 20.01):
|
|
power.value = 120;
|
|
break;
|
|
case (relativeWeight < 25.01):
|
|
power.value = 100;
|
|
break;
|
|
case (relativeWeight < 33.35):
|
|
power.value = 80;
|
|
break;
|
|
case (relativeWeight < 50.01):
|
|
power.value = 60;
|
|
break;
|
|
default:
|
|
power.value = 40;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class HpPowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.NumberHolder).value = Math.max(Math.floor(150 * user.getHpRatio()), 1);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves whose base power scales with the opponent's HP
|
|
* Used for Crush Grip, Wring Out, and Hard Press
|
|
* maxBasePower 100 for Hard Press, 120 for others
|
|
*/
|
|
export class OpponentHighHpPowerAttr extends VariablePowerAttr {
|
|
maxBasePower: number;
|
|
|
|
constructor(maxBasePower: number) {
|
|
super();
|
|
this.maxBasePower = maxBasePower;
|
|
}
|
|
|
|
/**
|
|
* Changes the base power of the move to be the target's HP ratio times the maxBasePower with a min value of 1
|
|
* @param user n/a
|
|
* @param target the Pokemon being attacked
|
|
* @param move n/a
|
|
* @param args holds the base power of the move at args[0]
|
|
* @returns true
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.NumberHolder).value = Math.max(Math.floor(this.maxBasePower * target.getHpRatio()), 1);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class FirstAttackDoublePowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
console.log(target.getLastXMoves(1), target.scene.currentBattle.turn);
|
|
if (!target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn)) {
|
|
(args[0] as Utils.NumberHolder).value *= 2;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
export class TurnDamagedDoublePowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (user.turnData.attacksReceived.find(r => r.damage && r.sourceId === target.id)) {
|
|
(args[0] as Utils.NumberHolder).value *= 2;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const magnitudeMessageFunc = (user: Pokemon, target: Pokemon, move: Move) => {
|
|
let message: string;
|
|
user.scene.executeWithSeedOffset(() => {
|
|
const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ];
|
|
|
|
const rand = Utils.randSeedInt(100);
|
|
|
|
let m = 0;
|
|
for (; m < magnitudeThresholds.length; m++) {
|
|
if (rand < magnitudeThresholds[m]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
message = i18next.t("moveTriggers:magnitudeMessage", {magnitude: m + 4});
|
|
}, user.scene.currentBattle.turn << 6, user.scene.waveSeed);
|
|
return message;
|
|
};
|
|
|
|
export class MagnitudePowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
|
|
const magnitudeThresholds = [ 5, 15, 35, 65, 75, 95 ];
|
|
const magnitudePowers = [ 10, 30, 50, 70, 90, 100, 110, 150 ];
|
|
|
|
let rand: integer;
|
|
|
|
user.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(100), user.scene.currentBattle.turn << 6, user.scene.waveSeed);
|
|
|
|
let m = 0;
|
|
for (; m < magnitudeThresholds.length; m++) {
|
|
if (rand < magnitudeThresholds[m]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
power.value = magnitudePowers[m];
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class AntiSunlightPowerDecreaseAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE;
|
|
switch (weatherType) {
|
|
case WeatherType.RAIN:
|
|
case WeatherType.SANDSTORM:
|
|
case WeatherType.HAIL:
|
|
case WeatherType.SNOW:
|
|
case WeatherType.HEAVY_RAIN:
|
|
power.value *= 0.5;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class FriendshipPowerAttr extends VariablePowerAttr {
|
|
private invert: boolean;
|
|
|
|
constructor(invert?: boolean) {
|
|
super();
|
|
|
|
this.invert = !!invert;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
|
|
const friendshipPower = Math.floor(Math.min(user instanceof PlayerPokemon ? user.friendship : user.species.baseFriendship, 255) / 2.5);
|
|
power.value = Math.max(!this.invert ? friendshipPower : 102 - friendshipPower, 1);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class HitCountPowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.NumberHolder).value += Math.min(user.battleData.hitCount, 6) * 50;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Turning a once was (StatChangeCountPowerAttr) statement and making it available to call for any attribute.
|
|
* @param {Pokemon} pokemon The pokemon that is being used to calculate the count of positive stats
|
|
* @returns {number} Returns the amount of positive stats
|
|
*/
|
|
const countPositiveStats = (pokemon: Pokemon): number => {
|
|
return pokemon.summonData.battleStats.reduce((total, stat) => (stat && stat > 0) ? total + stat : total, 0);
|
|
};
|
|
|
|
/**
|
|
* Attribute that increases power based on the amount of positive stat increases.
|
|
*/
|
|
export class StatChangeCountPowerAttr extends VariablePowerAttr {
|
|
|
|
/**
|
|
* @param {Pokemon} user The pokemon that is being used to calculate the amount of positive stats
|
|
* @param {Pokemon} target N/A
|
|
* @param {Move} move N/A
|
|
* @param {any[]} args The argument for VariablePowerAttr, accumulates and sets the amount of power multiplied by stats
|
|
* @returns {boolean} Returns true if attribute is applied
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const positiveStats: number = countPositiveStats(user);
|
|
|
|
(args[0] as Utils.NumberHolder).value += positiveStats * 20;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Punishment normally has a base power of 60,
|
|
* but gains 20 power for every increased stat stage the target has,
|
|
* up to a maximum of 200 base power in total.
|
|
*/
|
|
export class PunishmentPowerAttr extends VariablePowerAttr {
|
|
private PUNISHMENT_MIN_BASE_POWER = 60;
|
|
private PUNISHMENT_MAX_BASE_POWER = 200;
|
|
|
|
/**
|
|
* @param {Pokemon} user N/A
|
|
* @param {Pokemon} target The pokemon that the move is being used against, as well as calculating the stats for the min/max base power
|
|
* @param {Move} move N/A
|
|
* @param {any[]} args The value that is being changed due to VariablePowerAttr
|
|
* @returns Returns true if attribute is applied
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const positiveStats: number = countPositiveStats(target);
|
|
(args[0] as Utils.NumberHolder).value = Math.min(
|
|
this.PUNISHMENT_MAX_BASE_POWER,
|
|
this.PUNISHMENT_MIN_BASE_POWER + positiveStats * 20
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class PresentPowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
/**
|
|
* If this move is multi-hit, and this attribute is applied to any hit
|
|
* other than the first, this move cannot result in a heal.
|
|
*/
|
|
const firstHit = (user.turnData.hitCount === user.turnData.hitsLeft);
|
|
|
|
const powerSeed = Utils.randSeedInt(firstHit ? 100 : 80);
|
|
if (powerSeed <= 40) {
|
|
(args[0] as Utils.NumberHolder).value = 40;
|
|
} else if (40 < powerSeed && powerSeed <= 70) {
|
|
(args[0] as Utils.NumberHolder).value = 80;
|
|
} else if (70 < powerSeed && powerSeed <= 80) {
|
|
(args[0] as Utils.NumberHolder).value = 120;
|
|
} else if (80 < powerSeed && powerSeed <= 100) {
|
|
// If this move is multi-hit, disable all other hits
|
|
user.stopMultiHit();
|
|
target.scene.unshiftPhase(new PokemonHealPhase(target.scene, target.getBattlerIndex(),
|
|
Math.max(Math.floor(target.getMaxHp() / 4), 1), i18next.t("moveTriggers:regainedHealth", {pokemonName: getPokemonNameWithAffix(target)}), true));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class WaterShurikenPowerAttr extends VariablePowerAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (user.species.speciesId === Species.GRENINJA && user.hasAbility(Abilities.BATTLE_BOND) && user.formIndex === 2) {
|
|
(args[0] as Utils.IntegerHolder).value = 20;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used to calculate the power of attacks that scale with Stockpile stacks (i.e. Spit Up).
|
|
*/
|
|
export class SpitUpPowerAttr extends VariablePowerAttr {
|
|
private multiplier: number = 0;
|
|
|
|
constructor(multiplier: number) {
|
|
super();
|
|
this.multiplier = multiplier;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const stockpilingTag = user.getTag(StockpilingTag);
|
|
|
|
if (stockpilingTag?.stockpiledCount > 0) {
|
|
const power = args[0] as Utils.IntegerHolder;
|
|
power.value = this.multiplier * stockpilingTag.stockpiledCount;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used to apply Swallow's healing, which scales with Stockpile stacks.
|
|
* Does NOT remove stockpiled stacks.
|
|
*/
|
|
export class SwallowHealAttr extends HealAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const stockpilingTag = user.getTag(StockpilingTag);
|
|
|
|
if (stockpilingTag?.stockpiledCount > 0) {
|
|
const stockpiled = stockpilingTag.stockpiledCount;
|
|
let healRatio: number;
|
|
|
|
if (stockpiled === 1) {
|
|
healRatio = 0.25;
|
|
} else if (stockpiled === 2) {
|
|
healRatio = 0.50;
|
|
} else { // stockpiled >= 3
|
|
healRatio = 1.00;
|
|
}
|
|
|
|
if (healRatio) {
|
|
this.addHealPhase(user, healRatio);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const hasStockpileStacksCondition: MoveConditionFunc = (user) => user.getTag(StockpilingTag)?.stockpiledCount > 0;
|
|
|
|
/**
|
|
* Attribute used for multi-hit moves that increase power in increments of the
|
|
* move's base power for each hit, namely Triple Kick and Triple Axel.
|
|
* @extends VariablePowerAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class MultiHitPowerIncrementAttr extends VariablePowerAttr {
|
|
/** The max number of base power increments allowed for this move */
|
|
private maxHits: integer;
|
|
|
|
constructor(maxHits: integer) {
|
|
super();
|
|
|
|
this.maxHits = maxHits;
|
|
}
|
|
|
|
/**
|
|
* Increases power of move in increments of the base power for the amount of times
|
|
* the move hit. In the case that the move is extended, it will circle back to the
|
|
* original base power of the move after incrementing past the maximum amount of
|
|
* hits.
|
|
* @param user {@linkcode Pokemon} that used the move
|
|
* @param target {@linkcode Pokemon} that the move was used on
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args [0] {@linkcode Utils.NumberHolder} for final calculated power of move
|
|
* @returns true if attribute application succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const hitsTotal = user.turnData.hitCount - Math.max(user.turnData.hitsLeft, 0);
|
|
const power = args[0] as Utils.NumberHolder;
|
|
|
|
power.value = move.power * (1 + hitsTotal % this.maxHits);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves that double in power if the given move immediately
|
|
* preceded the move applying the attribute, namely Fusion Flare and
|
|
* Fusion Bolt.
|
|
* @extends VariablePowerAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class LastMoveDoublePowerAttr extends VariablePowerAttr {
|
|
/** The move that must precede the current move */
|
|
private move: Moves;
|
|
|
|
constructor(move: Moves) {
|
|
super();
|
|
|
|
this.move = move;
|
|
}
|
|
|
|
/**
|
|
* Doubles power of move if the given move is found to precede the current
|
|
* move with no other moves being executed in between, only ignoring failed
|
|
* moves if any.
|
|
* @param user {@linkcode Pokemon} that used the move
|
|
* @param target N/A
|
|
* @param move N/A
|
|
* @param args [0] {@linkcode Utils.NumberHolder} that holds the resulting power of the move
|
|
* @returns true if attribute application succeeds, false otherwise
|
|
*/
|
|
apply(user: Pokemon, _target: Pokemon, _move: Move, args: any[]): boolean {
|
|
const power = args[0] as Utils.NumberHolder;
|
|
const enemy = user.getOpponent(0);
|
|
const pokemonActed: Pokemon[] = [];
|
|
|
|
if (enemy.turnData.acted) {
|
|
pokemonActed.push(enemy);
|
|
}
|
|
|
|
if (user.scene.currentBattle.double) {
|
|
const userAlly = user.getAlly();
|
|
const enemyAlly = enemy.getAlly();
|
|
|
|
if (userAlly && userAlly.turnData.acted) {
|
|
pokemonActed.push(userAlly);
|
|
}
|
|
if (enemyAlly && enemyAlly.turnData.acted) {
|
|
pokemonActed.push(enemyAlly);
|
|
}
|
|
}
|
|
|
|
pokemonActed.sort((a, b) => b.turnData.order - a.turnData.order);
|
|
|
|
for (const p of pokemonActed) {
|
|
const [ lastMove ] = p.getLastXMoves(1);
|
|
if (lastMove.result !== MoveResult.FAIL) {
|
|
if ((lastMove.result === MoveResult.SUCCESS) && (lastMove.move === this.move)) {
|
|
power.value *= 2;
|
|
return true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class VariableAtkAttr extends MoveAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
//const atk = args[0] as Utils.IntegerHolder;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class TargetAtkUserAtkAttr extends VariableAtkAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = target.getBattleStat(Stat.ATK, target);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class DefAtkAttr extends VariableAtkAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = user.getBattleStat(Stat.DEF, target);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class VariableDefAttr extends MoveAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
//const def = args[0] as Utils.IntegerHolder;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class DefDefAttr extends VariableDefAttr {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
(args[0] as Utils.IntegerHolder).value = target.getBattleStat(Stat.DEF, user);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class VariableAccuracyAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
//const accuracy = args[0] as Utils.NumberHolder;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class ThunderAccuracyAttr extends VariableAccuracyAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
|
|
const accuracy = args[0] as Utils.NumberHolder;
|
|
const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE;
|
|
switch (weatherType) {
|
|
case WeatherType.SUNNY:
|
|
case WeatherType.SANDSTORM:
|
|
case WeatherType.HARSH_SUN:
|
|
accuracy.value = 50;
|
|
return true;
|
|
case WeatherType.RAIN:
|
|
case WeatherType.HEAVY_RAIN:
|
|
accuracy.value = -1;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves which never miss
|
|
* against Pokemon with the {@linkcode BattlerTagType.MINIMIZED}
|
|
* @extends VariableAccuracyAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class MinimizeAccuracyAttr extends VariableAccuracyAttr {
|
|
/**
|
|
* @see {@linkcode apply}
|
|
* @param user N/A
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move N/A
|
|
* @param args [0] Accuracy of the move to be modified
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (target.getTag(BattlerTagType.MINIMIZED)) {
|
|
const accuracy = args[0] as Utils.NumberHolder;
|
|
accuracy.value = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class ToxicAccuracyAttr extends VariableAccuracyAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (user.isOfType(Type.POISON)) {
|
|
const accuracy = args[0] as Utils.NumberHolder;
|
|
accuracy.value = -1;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class BlizzardAccuracyAttr extends VariableAccuracyAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
|
|
const accuracy = args[0] as Utils.NumberHolder;
|
|
const weatherType = user.scene.arena.weather?.weatherType || WeatherType.NONE;
|
|
if (weatherType === WeatherType.HAIL || weatherType === WeatherType.SNOW) {
|
|
accuracy.value = -1;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class VariableMoveCategoryAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class PhotonGeyserCategoryAttr extends VariableMoveCategoryAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const category = (args[0] as Utils.IntegerHolder);
|
|
|
|
if (user.getBattleStat(Stat.ATK, target, move) > user.getBattleStat(Stat.SPATK, target, move)) {
|
|
category.value = MoveCategory.PHYSICAL;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class TeraBlastCategoryAttr extends VariableMoveCategoryAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const category = (args[0] as Utils.IntegerHolder);
|
|
|
|
if (user.isTerastallized() && user.getBattleStat(Stat.ATK, target, move) > user.getBattleStat(Stat.SPATK, target, move)) {
|
|
category.value = MoveCategory.PHYSICAL;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change the move category to status when used on the ally
|
|
* @extends VariableMoveCategoryAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class StatusCategoryOnAllyAttr extends VariableMoveCategoryAttr {
|
|
/**
|
|
* @param user {@linkcode Pokemon} using the move
|
|
* @param target {@linkcode Pokemon} target of the move
|
|
* @param move {@linkcode Move} with this attribute
|
|
* @param args [0] {@linkcode Utils.IntegerHolder} The category of the move
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const category = (args[0] as Utils.IntegerHolder);
|
|
|
|
if (user.getAlly() === target) {
|
|
category.value = MoveCategory.STATUS;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const category = (args[0] as Utils.IntegerHolder);
|
|
const atkRatio = user.getBattleStat(Stat.ATK, target, move) / target.getBattleStat(Stat.DEF, user, move);
|
|
const specialRatio = user.getBattleStat(Stat.SPATK, target, move) / target.getBattleStat(Stat.SPDEF, user, move);
|
|
|
|
// Shell Side Arm is much more complicated than it looks, this is a partial implementation to try to achieve something similar to the games
|
|
if (atkRatio > specialRatio) {
|
|
category.value = MoveCategory.PHYSICAL;
|
|
return true;
|
|
} else if (atkRatio === specialRatio && user.randSeedInt(2) === 0) {
|
|
category.value = MoveCategory.PHYSICAL;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class VariableMoveTypeAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class FormChangeItemTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.ARCEUS) || [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.SILVALLY)) {
|
|
const form = user.species.speciesId === Species.ARCEUS || user.species.speciesId === Species.SILVALLY ? user.formIndex : user.fusionSpecies.formIndex;
|
|
|
|
move.type = Type[Type[form]];
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class TechnoBlastTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.GENESECT)) {
|
|
const form = user.species.speciesId === Species.GENESECT ? user.formIndex : user.fusionSpecies.formIndex;
|
|
|
|
switch (form) {
|
|
case 1: // Shock Drive
|
|
move.type = Type.ELECTRIC;
|
|
break;
|
|
case 2: // Burn Drive
|
|
move.type = Type.FIRE;
|
|
break;
|
|
case 3: // Chill Drive
|
|
move.type = Type.ICE;
|
|
break;
|
|
case 4: // Douse Drive
|
|
move.type = Type.WATER;
|
|
break;
|
|
default:
|
|
move.type = Type.NORMAL;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class AuraWheelTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)) {
|
|
const form = user.species.speciesId === Species.MORPEKO ? user.formIndex : user.fusionSpecies.formIndex;
|
|
|
|
switch (form) {
|
|
case 1: // Hangry Mode
|
|
move.type = Type.DARK;
|
|
break;
|
|
default: // Full Belly Mode
|
|
move.type = Type.ELECTRIC;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class RagingBullTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.PALDEA_TAUROS)) {
|
|
const form = user.species.speciesId === Species.PALDEA_TAUROS ? user.formIndex : user.fusionSpecies.formIndex;
|
|
|
|
switch (form) {
|
|
case 1: // Blaze breed
|
|
move.type = Type.FIRE;
|
|
break;
|
|
case 2: // Aqua breed
|
|
move.type = Type.WATER;
|
|
break;
|
|
default:
|
|
move.type = Type.FIGHTING;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if ([user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.OGERPON)) {
|
|
const form = user.species.speciesId === Species.OGERPON ? user.formIndex : user.fusionSpecies.formIndex;
|
|
|
|
switch (form) {
|
|
case 1: // Wellspring Mask
|
|
move.type = Type.WATER;
|
|
break;
|
|
case 2: // Hearthflame Mask
|
|
move.type = Type.FIRE;
|
|
break;
|
|
case 3: // Cornerstone Mask
|
|
move.type = Type.ROCK;
|
|
break;
|
|
case 4: // Teal Mask Tera
|
|
move.type = Type.GRASS;
|
|
break;
|
|
case 5: // Wellspring Mask Tera
|
|
move.type = Type.WATER;
|
|
break;
|
|
case 6: // Hearthflame Mask Tera
|
|
move.type = Type.FIRE;
|
|
break;
|
|
case 7: // Cornerstone Mask Tera
|
|
move.type = Type.ROCK;
|
|
break;
|
|
default:
|
|
move.type = Type.GRASS;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class WeatherBallTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!user.scene.arena.weather?.isEffectSuppressed(user.scene)) {
|
|
switch (user.scene.arena.weather?.weatherType) {
|
|
case WeatherType.SUNNY:
|
|
case WeatherType.HARSH_SUN:
|
|
move.type = Type.FIRE;
|
|
break;
|
|
case WeatherType.RAIN:
|
|
case WeatherType.HEAVY_RAIN:
|
|
move.type = Type.WATER;
|
|
break;
|
|
case WeatherType.SANDSTORM:
|
|
move.type = Type.ROCK;
|
|
break;
|
|
case WeatherType.HAIL:
|
|
case WeatherType.SNOW:
|
|
move.type = Type.ICE;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the move's type to match the current terrain.
|
|
* Has no effect if the user is not grounded.
|
|
* @extends VariableMoveTypeAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class TerrainPulseTypeAttr extends VariableMoveTypeAttr {
|
|
/**
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target N/A
|
|
* @param move N/A
|
|
* @param args [0] {@linkcode Utils.IntegerHolder} The move's type to be modified
|
|
* @returns true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!user.isGrounded()) {
|
|
return false;
|
|
}
|
|
|
|
const currentTerrain = user.scene.arena.getTerrainType();
|
|
switch (currentTerrain) {
|
|
case TerrainType.MISTY:
|
|
move.type = Type.FAIRY;
|
|
break;
|
|
case TerrainType.ELECTRIC:
|
|
move.type = Type.ELECTRIC;
|
|
break;
|
|
case TerrainType.GRASSY:
|
|
move.type = Type.GRASS;
|
|
break;
|
|
case TerrainType.PSYCHIC:
|
|
move.type = Type.PSYCHIC;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class HiddenPowerTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const iv_val = Math.floor(((user.ivs[Stat.HP] & 1)
|
|
+(user.ivs[Stat.ATK] & 1) * 2
|
|
+(user.ivs[Stat.DEF] & 1) * 4
|
|
+(user.ivs[Stat.SPD] & 1) * 8
|
|
+(user.ivs[Stat.SPATK] & 1) * 16
|
|
+(user.ivs[Stat.SPDEF] & 1) * 32) * 15/63);
|
|
|
|
move.type = [
|
|
Type.FIGHTING, Type.FLYING, Type.POISON, Type.GROUND,
|
|
Type.ROCK, Type.BUG, Type.GHOST, Type.STEEL,
|
|
Type.FIRE, Type.WATER, Type.GRASS, Type.ELECTRIC,
|
|
Type.PSYCHIC, Type.ICE, Type.DRAGON, Type.DARK][iv_val];
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class MatchUserTypeAttr extends VariableMoveTypeAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const userTypes = user.getTypes(true);
|
|
|
|
if (userTypes.includes(Type.STELLAR)) { // will not change to stellar type
|
|
const nonTeraTypes = user.getTypes();
|
|
move.type = nonTeraTypes[0];
|
|
return true;
|
|
} else if (userTypes.length > 0) {
|
|
move.type = userTypes[0];
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
export class VariableMoveTypeMultiplierAttr extends MoveAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class NeutralDamageAgainstFlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!target.getTag(BattlerTagType.IGNORE_FLYING)) {
|
|
const multiplier = args[0] as Utils.NumberHolder;
|
|
//When a flying type is hit, the first hit is always 1x multiplier. Levitating pokemon are instantly affected by typing
|
|
if (target.isOfType(Type.FLYING) || target.hasAbility(Abilities.LEVITATE)) {
|
|
multiplier.value = 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class WaterSuperEffectTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const multiplier = args[0] as Utils.NumberHolder;
|
|
if (target.isOfType(Type.WATER)) {
|
|
multiplier.value *= 4; // Increased twice because initial reduction against water
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class IceNoEffectTypeAttr extends VariableMoveTypeMultiplierAttr {
|
|
/**
|
|
* Checks to see if the Target is Ice-Type or not. If so, the move will have no effect.
|
|
* @param {Pokemon} user N/A
|
|
* @param {Pokemon} target Pokemon that is being checked whether Ice-Type or not.
|
|
* @param {Move} move N/A
|
|
* @param {any[]} args Sets to false if the target is Ice-Type, so it should do no damage/no effect.
|
|
* @returns {boolean} Returns true if move is successful, false if Ice-Type.
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (target.isOfType(Type.ICE)) {
|
|
(args[0] as Utils.BooleanHolder).value = false;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class FlyingTypeMultiplierAttr extends VariableMoveTypeMultiplierAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const multiplier = args[0] as Utils.NumberHolder;
|
|
multiplier.value *= target.getAttackTypeEffectiveness(Type.FLYING, user);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class OneHitKOAccuracyAttr extends VariableAccuracyAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const accuracy = args[0] as Utils.NumberHolder;
|
|
if (user.level < target.level) {
|
|
accuracy.value = 0;
|
|
} else {
|
|
accuracy.value = Math.min(Math.max(30 + 100 * (1 - target.level / user.level), 0), 100);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class SheerColdAccuracyAttr extends OneHitKOAccuracyAttr {
|
|
/**
|
|
* Changes the normal One Hit KO Accuracy Attr to implement the Gen VII changes,
|
|
* where if the user is Ice-Type, it has more accuracy.
|
|
* @param {Pokemon} user Pokemon that is using the move; checks the Pokemon's level.
|
|
* @param {Pokemon} target Pokemon that is receiving the move; checks the Pokemon's level.
|
|
* @param {Move} move N/A
|
|
* @param {any[]} args Uses the accuracy argument, allowing to change it from either 0 if it doesn't pass
|
|
* the first if/else, or 30/20 depending on the type of the user Pokemon.
|
|
* @returns Returns true if move is successful, false if misses.
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const accuracy = args[0] as Utils.NumberHolder;
|
|
if (user.level < target.level) {
|
|
accuracy.value = 0;
|
|
} else {
|
|
const baseAccuracy = user.isOfType(Type.ICE) ? 30 : 20;
|
|
accuracy.value = Math.min(Math.max(baseAccuracy + 100 * (1 - target.level / user.level), 0), 100);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class MissEffectAttr extends MoveAttr {
|
|
private missEffectFunc: UserMoveConditionFunc;
|
|
|
|
constructor(missEffectFunc: UserMoveConditionFunc) {
|
|
super();
|
|
|
|
this.missEffectFunc = missEffectFunc;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
this.missEffectFunc(user, move);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class NoEffectAttr extends MoveAttr {
|
|
private noEffectFunc: UserMoveConditionFunc;
|
|
|
|
constructor(noEffectFunc: UserMoveConditionFunc) {
|
|
super();
|
|
|
|
this.noEffectFunc = noEffectFunc;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
this.noEffectFunc(user, move);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const crashDamageFunc = (user: Pokemon, move: Move) => {
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
|
|
if (cancelled.value) {
|
|
return false;
|
|
}
|
|
|
|
user.damageAndUpdate(Math.floor(user.getMaxHp() / 2), HitResult.OTHER, false, true);
|
|
user.scene.queueMessage(i18next.t("moveTriggers:keptGoingAndCrashed", {pokemonName: getPokemonNameWithAffix(user)}));
|
|
user.turnData.damageTaken += Math.floor(user.getMaxHp() / 2);
|
|
|
|
return true;
|
|
};
|
|
|
|
export class TypelessAttr extends MoveAttr { }
|
|
/**
|
|
* Attribute used for moves which ignore redirection effects, and always target their original target, i.e. Snipe Shot
|
|
* Bypasses Storm Drain, Follow Me, Ally Switch, and the like.
|
|
*/
|
|
export class BypassRedirectAttr extends MoveAttr { }
|
|
|
|
export class DisableMoveAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const moveQueue = target.getLastXMoves();
|
|
let turnMove: TurnMove;
|
|
while (moveQueue.length) {
|
|
turnMove = moveQueue.shift();
|
|
if (turnMove.virtual) {
|
|
continue;
|
|
}
|
|
|
|
const moveIndex = target.getMoveset().findIndex(m => m.moveId === turnMove.move);
|
|
if (moveIndex === -1) {
|
|
return false;
|
|
}
|
|
|
|
const disabledMove = target.getMoveset()[moveIndex];
|
|
target.summonData.disabledMove = disabledMove.moveId;
|
|
target.summonData.disabledTurns = 4;
|
|
|
|
user.scene.queueMessage(getPokemonMessage(target, `'s ${disabledMove.getName()}\nwas disabled!`));
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
if (target.summonData.disabledMove || target.isMax()) {
|
|
return false;
|
|
}
|
|
|
|
const moveQueue = target.getLastXMoves();
|
|
let turnMove: TurnMove;
|
|
while (moveQueue.length) {
|
|
turnMove = moveQueue.shift();
|
|
if (turnMove.virtual) {
|
|
continue;
|
|
}
|
|
|
|
const move = target.getMoveset().find(m => m.moveId === turnMove.move);
|
|
if (!move) {
|
|
continue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return -5;
|
|
}
|
|
}
|
|
|
|
export class FrenzyAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true, MoveEffectTrigger.HIT, false, true);
|
|
}
|
|
|
|
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
|
|
return !(this.selfTarget ? user : target).isFainted();
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
if (!user.getTag(BattlerTagType.FRENZY) && !user.getMoveQueue().length) {
|
|
const turnCount = user.randSeedIntRange(1, 2);
|
|
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
|
|
user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id);
|
|
} else {
|
|
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args);
|
|
user.lapseTag(BattlerTagType.FRENZY); // if FRENZY is already in effect (moveQueue.length > 0), lapse the tag
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
|
|
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) {
|
|
user.getMoveQueue().shift();
|
|
}
|
|
user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect
|
|
|
|
return true;
|
|
};
|
|
|
|
export class AddBattlerTagAttr extends MoveEffectAttr {
|
|
public tagType: BattlerTagType;
|
|
public turnCountMin: integer;
|
|
public turnCountMax: integer;
|
|
private failOnOverlap: boolean;
|
|
|
|
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: integer = 0, turnCountMax?: integer, lastHitOnly: boolean = false) {
|
|
super(selfTarget, MoveEffectTrigger.POST_APPLY, false, lastHitOnly);
|
|
|
|
this.tagType = tagType;
|
|
this.turnCountMin = turnCountMin;
|
|
this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin;
|
|
this.failOnOverlap = !!failOnOverlap;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
|
|
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
|
|
return (this.selfTarget ? user : target).addTag(this.tagType, user.randSeedInt(this.turnCountMax - this.turnCountMin, this.turnCountMin), move.id, user.id);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return this.failOnOverlap
|
|
? (user, target, move) => !(this.selfTarget ? user : target).getTag(this.tagType)
|
|
: null;
|
|
}
|
|
|
|
getTagTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
switch (this.tagType) {
|
|
case BattlerTagType.RECHARGING:
|
|
case BattlerTagType.PERISH_SONG:
|
|
return -16;
|
|
case BattlerTagType.FLINCHED:
|
|
case BattlerTagType.CONFUSED:
|
|
case BattlerTagType.INFATUATED:
|
|
case BattlerTagType.NIGHTMARE:
|
|
case BattlerTagType.DROWSY:
|
|
case BattlerTagType.NO_CRIT:
|
|
return -5;
|
|
case BattlerTagType.SEEDED:
|
|
case BattlerTagType.SALT_CURED:
|
|
case BattlerTagType.CURSED:
|
|
case BattlerTagType.FRENZY:
|
|
case BattlerTagType.TRAPPED:
|
|
case BattlerTagType.BIND:
|
|
case BattlerTagType.WRAP:
|
|
case BattlerTagType.FIRE_SPIN:
|
|
case BattlerTagType.WHIRLPOOL:
|
|
case BattlerTagType.CLAMP:
|
|
case BattlerTagType.SAND_TOMB:
|
|
case BattlerTagType.MAGMA_STORM:
|
|
case BattlerTagType.SNAP_TRAP:
|
|
case BattlerTagType.THUNDER_CAGE:
|
|
case BattlerTagType.INFESTATION:
|
|
return -3;
|
|
case BattlerTagType.ENCORE:
|
|
return -2;
|
|
case BattlerTagType.MINIMIZED:
|
|
return 0;
|
|
case BattlerTagType.INGRAIN:
|
|
case BattlerTagType.IGNORE_ACCURACY:
|
|
case BattlerTagType.AQUA_RING:
|
|
return 3;
|
|
case BattlerTagType.PROTECTED:
|
|
case BattlerTagType.FLYING:
|
|
case BattlerTagType.CRIT_BOOST:
|
|
case BattlerTagType.ALWAYS_CRIT:
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
let moveChance = this.getMoveChance(user,target,move,this.selfTarget, false);
|
|
if (moveChance < 0) {
|
|
moveChance = 100;
|
|
}
|
|
return Math.floor(this.getTagTargetBenefitScore(user, target, move) * (moveChance / 100));
|
|
}
|
|
}
|
|
|
|
export class CurseAttr extends MoveEffectAttr {
|
|
|
|
apply(user: Pokemon, target: Pokemon, move:Move, args: any[]): boolean {
|
|
if (user.getTypes(true).includes(Type.GHOST)) {
|
|
if (target.getTag(BattlerTagType.CURSED)) {
|
|
user.scene.queueMessage(i18next.t("battle:attackFailed"));
|
|
return false;
|
|
}
|
|
const curseRecoilDamage = Math.max(1, Math.floor(user.getMaxHp() / 2));
|
|
user.damageAndUpdate(curseRecoilDamage, HitResult.OTHER, false, true, true);
|
|
user.scene.queueMessage(
|
|
i18next.t("battle:battlerTagsCursedOnAdd", {
|
|
pokemonNameWithAffix: getPokemonNameWithAffix(user),
|
|
pokemonName: getPokemonNameWithAffix(target)
|
|
})
|
|
);
|
|
|
|
target.addTag(BattlerTagType.CURSED, 0, move.id, user.id);
|
|
return true;
|
|
} else {
|
|
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.getBattlerIndex(), true, [BattleStat.ATK, BattleStat.DEF], 1));
|
|
user.scene.unshiftPhase(new StatChangePhase(user.scene, user.getBattlerIndex(), true, [BattleStat.SPD], -1));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
export class LapseBattlerTagAttr extends MoveEffectAttr {
|
|
public tagTypes: BattlerTagType[];
|
|
|
|
constructor(tagTypes: BattlerTagType[], selfTarget: boolean = false) {
|
|
super(selfTarget);
|
|
|
|
this.tagTypes = tagTypes;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
for (const tagType of this.tagTypes) {
|
|
(this.selfTarget ? user : target).lapseTag(tagType);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class RemoveBattlerTagAttr extends MoveEffectAttr {
|
|
public tagTypes: BattlerTagType[];
|
|
|
|
constructor(tagTypes: BattlerTagType[], selfTarget: boolean = false) {
|
|
super(selfTarget);
|
|
|
|
this.tagTypes = tagTypes;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
for (const tagType of this.tagTypes) {
|
|
(this.selfTarget ? user : target).removeTag(tagType);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class FlinchAttr extends AddBattlerTagAttr {
|
|
constructor() {
|
|
super(BattlerTagType.FLINCHED, false);
|
|
}
|
|
}
|
|
|
|
export class ConfuseAttr extends AddBattlerTagAttr {
|
|
constructor(selfTarget?: boolean) {
|
|
super(BattlerTagType.CONFUSED, selfTarget, false, 2, 5);
|
|
}
|
|
}
|
|
|
|
export class RechargeAttr extends AddBattlerTagAttr {
|
|
constructor() {
|
|
super(BattlerTagType.RECHARGING, true, false, 1, 1, true);
|
|
}
|
|
}
|
|
|
|
export class TrapAttr extends AddBattlerTagAttr {
|
|
constructor(tagType: BattlerTagType) {
|
|
super(tagType, false, false, 4, 5);
|
|
}
|
|
}
|
|
|
|
export class ProtectAttr extends AddBattlerTagAttr {
|
|
constructor(tagType: BattlerTagType = BattlerTagType.PROTECTED) {
|
|
super(tagType, true);
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return ((user, target, move): boolean => {
|
|
let timesUsed = 0;
|
|
const moveHistory = user.getLastXMoves();
|
|
let turnMove: TurnMove;
|
|
|
|
while (moveHistory.length) {
|
|
turnMove = moveHistory.shift();
|
|
if (!allMoves[turnMove.move].hasAttr(ProtectAttr) || turnMove.result !== MoveResult.SUCCESS) {
|
|
break;
|
|
}
|
|
timesUsed++;
|
|
}
|
|
if (timesUsed) {
|
|
return !user.randSeedInt(Math.pow(3, timesUsed));
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
export class EndureAttr extends ProtectAttr {
|
|
constructor() {
|
|
super(BattlerTagType.ENDURING);
|
|
}
|
|
}
|
|
|
|
export class IgnoreAccuracyAttr extends AddBattlerTagAttr {
|
|
constructor() {
|
|
super(BattlerTagType.IGNORE_ACCURACY, true, false, 2);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:tookAimAtTarget", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target)}));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class FaintCountdownAttr extends AddBattlerTagAttr {
|
|
constructor() {
|
|
super(BattlerTagType.PERISH_SONG, false, true, 4);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
user.scene.queueMessage(getPokemonMessage(target, `\nwill faint in ${this.turnCountMin - 1} turns.`));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used when a move hits a {@linkcode BattlerTagType} for double damage
|
|
* @extends MoveAttr
|
|
*/
|
|
export class HitsTagAttr extends MoveAttr {
|
|
/** The {@linkcode BattlerTagType} this move hits */
|
|
public tagType: BattlerTagType;
|
|
/** Should this move deal double damage against {@linkcode HitsTagAttr.tagType}? */
|
|
public doubleDamage: boolean;
|
|
|
|
constructor(tagType: BattlerTagType, doubleDamage?: boolean) {
|
|
super();
|
|
|
|
this.tagType = tagType;
|
|
this.doubleDamage = !!doubleDamage;
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return target.getTag(this.tagType) ? this.doubleDamage ? 10 : 5 : 0;
|
|
}
|
|
}
|
|
|
|
export class AddArenaTagAttr extends MoveEffectAttr {
|
|
public tagType: ArenaTagType;
|
|
public turnCount: integer;
|
|
private failOnOverlap: boolean;
|
|
public selfSideTarget: boolean;
|
|
|
|
constructor(tagType: ArenaTagType, turnCount?: integer, failOnOverlap: boolean = false, selfSideTarget: boolean = false) {
|
|
super(true, MoveEffectTrigger.POST_APPLY);
|
|
|
|
this.tagType = tagType;
|
|
this.turnCount = turnCount;
|
|
this.failOnOverlap = failOnOverlap;
|
|
this.selfSideTarget = selfSideTarget;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
if (move.chance < 0 || move.chance === 100 || user.randSeedInt(100) < move.chance) {
|
|
user.scene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return this.failOnOverlap
|
|
? (user, target, move) => !user.scene.arena.getTagOnSide(this.tagType, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY)
|
|
: null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generic class for removing arena tags
|
|
* @param tagTypes: The types of tags that can be removed
|
|
* @param selfSideTarget: Is the user removing tags from its own side?
|
|
*/
|
|
export class RemoveArenaTagsAttr extends MoveEffectAttr {
|
|
public tagTypes: ArenaTagType[];
|
|
public selfSideTarget: boolean;
|
|
|
|
constructor(tagTypes: ArenaTagType[], selfSideTarget: boolean) {
|
|
super(true, MoveEffectTrigger.POST_APPLY);
|
|
|
|
this.tagTypes = tagTypes;
|
|
this.selfSideTarget = selfSideTarget;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
|
|
|
for (const tagType of this.tagTypes) {
|
|
user.scene.arena.removeTagOnSide(tagType, side);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class AddArenaTrapTagAttr extends AddArenaTagAttr {
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
|
const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
|
if (!tag) {
|
|
return true;
|
|
}
|
|
return tag.layers < tag.maxLayers;
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for Stone Axe and Ceaseless Edge.
|
|
* Applies the given ArenaTrapTag when move is used.
|
|
* @extends AddArenaTagAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class AddArenaTrapTagHitAttr extends AddArenaTagAttr {
|
|
/**
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target {@linkcode Pokemon} target of this move
|
|
* @param move {@linkcode Move} being used
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const moveChance = this.getMoveChance(user,target,move,this.selfTarget, true);
|
|
const side = (this.selfSideTarget ? user : target).isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
|
|
const tag = user.scene.arena.getTagOnSide(this.tagType, side) as ArenaTrapTag;
|
|
if ((moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance)) {
|
|
user.scene.arena.addTag(this.tagType, 0, move.id, user.id, side);
|
|
if (!tag) {
|
|
return true;
|
|
}
|
|
return tag.layers < tag.maxLayers;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class RemoveArenaTrapAttr extends MoveEffectAttr {
|
|
|
|
private targetBothSides: boolean;
|
|
|
|
constructor(targetBothSides: boolean = false) {
|
|
super(true, MoveEffectTrigger.PRE_APPLY);
|
|
this.targetBothSides = targetBothSides;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
if (this.targetBothSides) {
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.PLAYER);
|
|
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, ArenaTagSide.ENEMY);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, ArenaTagSide.ENEMY);
|
|
} else {
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.TOXIC_SPIKES, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.STEALTH_ROCK, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.STICKY_WEB, target.isPlayer() ? ArenaTagSide.ENEMY : ArenaTagSide.PLAYER);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class RemoveScreensAttr extends MoveEffectAttr {
|
|
|
|
private targetBothSides: boolean;
|
|
|
|
constructor(targetBothSides: boolean = false) {
|
|
super(true, MoveEffectTrigger.PRE_APPLY);
|
|
this.targetBothSides = targetBothSides;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
if (this.targetBothSides) {
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.PLAYER);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.PLAYER);
|
|
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, ArenaTagSide.ENEMY);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, ArenaTagSide.ENEMY);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, ArenaTagSide.ENEMY);
|
|
} else {
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.REFLECT, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.LIGHT_SCREEN, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
|
|
user.scene.arena.removeTagOnSide(ArenaTagType.AURORA_VEIL, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY);
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
}
|
|
|
|
/*Swaps arena effects between the player and enemy side
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class SwapArenaTagsAttr extends MoveEffectAttr {
|
|
public SwapTags: ArenaTagType[];
|
|
|
|
|
|
constructor(SwapTags: ArenaTagType[]) {
|
|
super(true, MoveEffectTrigger.POST_APPLY);
|
|
this.SwapTags = SwapTags;
|
|
}
|
|
|
|
apply(user:Pokemon, target:Pokemon, move:Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const tagPlayerTemp = user.scene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.PLAYER);
|
|
const tagEnemyTemp = user.scene.arena.findTagsOnSide((t => this.SwapTags.includes(t.tagType)), ArenaTagSide.ENEMY);
|
|
|
|
|
|
if (tagPlayerTemp) {
|
|
for (const swapTagsType of tagPlayerTemp) {
|
|
user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.PLAYER, true);
|
|
user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId, ArenaTagSide.ENEMY, true);
|
|
}
|
|
}
|
|
if (tagEnemyTemp) {
|
|
for (const swapTagsType of tagEnemyTemp) {
|
|
user.scene.arena.removeTagOnSide(swapTagsType.tagType, ArenaTagSide.ENEMY, true);
|
|
user.scene.arena.addTag(swapTagsType.tagType, swapTagsType.turnCount, swapTagsType.sourceMove, swapTagsType.sourceId, ArenaTagSide.PLAYER, true);
|
|
}
|
|
}
|
|
|
|
|
|
user.scene.queueMessage( `${getPokemonNameWithAffix(user)} swapped the battle effects affecting each side of the field!`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for Revival Blessing.
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
*/
|
|
export class RevivalBlessingAttr extends MoveEffectAttr {
|
|
constructor(user?: boolean) {
|
|
super(true);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param user {@linkcode Pokemon} using this move
|
|
* @param target {@linkcode Pokemon} target of this move
|
|
* @param move {@linkcode Move} being used
|
|
* @param args N/A
|
|
* @returns Promise, true if function succeeds.
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
// If user is player, checks if the user has fainted pokemon
|
|
if (user instanceof PlayerPokemon
|
|
&& user.scene.getParty().findIndex(p => p.isFainted())>-1) {
|
|
(user as PlayerPokemon).revivalBlessing().then(() => {
|
|
resolve(true);
|
|
});
|
|
// If user is enemy, checks that it is a trainer, and it has fainted non-boss pokemon in party
|
|
} else if (user instanceof EnemyPokemon
|
|
&& user.hasTrainer()
|
|
&& user.scene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) {
|
|
// Selects a random fainted pokemon
|
|
const faintedPokemon = user.scene.getEnemyParty().filter(p => p.isFainted() && !p.isBoss());
|
|
const pokemon = faintedPokemon[user.randSeedInt(faintedPokemon.length)];
|
|
const slotIndex = user.scene.getEnemyParty().findIndex(p => pokemon.id === p.id);
|
|
pokemon.resetStatus();
|
|
pokemon.heal(Math.min(Math.max(Math.ceil(Math.floor(0.5 * pokemon.getMaxHp())), 1), pokemon.getMaxHp()));
|
|
user.scene.queueMessage(`${getPokemonNameWithAffix(pokemon)} was revived!`,0,true);
|
|
|
|
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));
|
|
} else if (allyPokemon.isFainted()) {
|
|
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, allyPokemon.getFieldIndex(), slotIndex, false, false,false));
|
|
}
|
|
}
|
|
resolve(true);
|
|
} else {
|
|
user.scene.queueMessage(i18next.t("battle:attackFailed"));
|
|
resolve(false);
|
|
}
|
|
});
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
if (user.hasTrainer() && user.scene.getEnemyParty().findIndex(p => p.isFainted() && !p.isBoss()) > -1) {
|
|
return 20;
|
|
}
|
|
|
|
return -20;
|
|
}
|
|
}
|
|
|
|
export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|
private user: boolean;
|
|
private batonPass: boolean;
|
|
|
|
constructor(user?: boolean, batonPass?: boolean) {
|
|
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
|
this.user = !!user;
|
|
this.batonPass = !!batonPass;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
|
|
// Check if the move category is not STATUS or if the switch out condition is not met
|
|
if (!this.getSwitchOutCondition()(user, target, move)) {
|
|
//Apply effects before switch out i.e. poison point, flame body, etc
|
|
applyPostDefendAbAttrs(PostDefendContactApplyStatusEffectAbAttr, target, user, move, null);
|
|
return resolve(false);
|
|
}
|
|
|
|
// 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.user ? user : target;
|
|
if (switchOutTarget instanceof PlayerPokemon) {
|
|
if (switchOutTarget.hp > 0) {
|
|
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, switchOutTarget);
|
|
// switchOut below sets the UI to select party(this is not a separate Phase), then adds a SwitchSummonPhase with selected 'mon
|
|
(switchOutTarget as PlayerPokemon).switchOut(this.batonPass, true).then(() => resolve(true));
|
|
} else {
|
|
resolve(false);
|
|
}
|
|
return;
|
|
} else if (user.scene.currentBattle.battleType) {
|
|
// Switch out logic for the battle type
|
|
switchOutTarget.resetTurnData();
|
|
switchOutTarget.resetSummonData();
|
|
switchOutTarget.hideInfo();
|
|
switchOutTarget.setVisible(false);
|
|
switchOutTarget.scene.field.remove(switchOutTarget);
|
|
user.scene.triggerPokemonFormChange(switchOutTarget, SpeciesFormChangeActiveTrigger, true);
|
|
|
|
if (switchOutTarget.hp > 0) {
|
|
// for opponent switching out
|
|
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(), user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot), false, this.batonPass, false), MoveEndPhase);
|
|
}
|
|
} else {
|
|
// Switch out logic for everything else
|
|
switchOutTarget.setVisible(false);
|
|
|
|
if (switchOutTarget.hp) {
|
|
switchOutTarget.hideInfo().then(() => switchOutTarget.destroy());
|
|
switchOutTarget.scene.field.remove(switchOutTarget);
|
|
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
|
|
}
|
|
|
|
if (!switchOutTarget.getAlly()?.isActive(true)) {
|
|
user.scene.clearEnemyHeldItemModifiers();
|
|
|
|
if (switchOutTarget.hp) {
|
|
user.scene.pushPhase(new BattleEndPhase(user.scene));
|
|
user.scene.pushPhase(new NewBattlePhase(user.scene));
|
|
}
|
|
}
|
|
}
|
|
|
|
resolve(true);
|
|
});
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => (move.category !== MoveCategory.STATUS || this.getSwitchOutCondition()(user, target, move));
|
|
}
|
|
|
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
|
const blockedByAbility = new Utils.BooleanHolder(false);
|
|
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility);
|
|
return blockedByAbility.value ? i18next.t("moveTriggers:cannotBeSwitchedOut", {pokemonName: getPokemonNameWithAffix(target)}) : null;
|
|
}
|
|
|
|
getSwitchOutCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
const switchOutTarget = (this.user ? user : target);
|
|
const player = switchOutTarget instanceof PlayerPokemon;
|
|
|
|
if (!this.user && move.category === MoveCategory.STATUS && (target.hasAbilityWithAttr(ForceSwitchOutImmunityAbAttr) || target.isMax())) {
|
|
return false;
|
|
}
|
|
|
|
if (!player && !user.scene.currentBattle.battleType) {
|
|
if (this.batonPass) {
|
|
return false;
|
|
}
|
|
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
|
|
if (!(user.scene.currentBattle.waveIndex % 10)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const party = player ? user.scene.getParty() : user.scene.getEnemyParty();
|
|
return (!player && !user.scene.currentBattle.battleType) || party.filter(p => p.isAllowedInBattle() && (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > user.scene.currentBattle.getBattlerCount();
|
|
};
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
if (!user.scene.getEnemyParty().find(p => p.isActive() && !p.isOnField())) {
|
|
return -20;
|
|
}
|
|
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
|
if (this.user && this.batonPass) {
|
|
const battleStatTotal = user.summonData.battleStats.reduce((bs: integer, total: integer) => total += bs, 0);
|
|
ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(battleStatTotal), 10) / 10) * (battleStatTotal >= 0 ? 10 : -10));
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
export class RemoveTypeAttr extends MoveEffectAttr {
|
|
|
|
private removedType: Type;
|
|
private messageCallback: ((user: Pokemon) => void) | undefined;
|
|
|
|
constructor(removedType: Type, messageCallback?: (user: Pokemon) => void) {
|
|
super(true, MoveEffectTrigger.POST_TARGET);
|
|
this.removedType = removedType;
|
|
this.messageCallback = messageCallback;
|
|
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
if (user.isTerastallized && user.getTeraType() === this.removedType) { // active tera types cannot be removed
|
|
return false;
|
|
}
|
|
|
|
const userTypes = user.getTypes(true);
|
|
const modifiedTypes = userTypes.filter(type => type !== this.removedType);
|
|
user.summonData.types = modifiedTypes;
|
|
user.updateInfo();
|
|
|
|
|
|
if (this.messageCallback) {
|
|
this.messageCallback(user);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class CopyTypeAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(false);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
user.summonData.types = target.getTypes(true);
|
|
user.updateInfo();
|
|
|
|
user.scene.queueMessage(getPokemonMessage(user, `'s type\nchanged to match ${getPokemonNameWithAffix(target)}'s!`));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => target.getTypes()[0] !== Type.UNKNOWN;
|
|
}
|
|
}
|
|
|
|
export class CopyBiomeTypeAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const biomeType = user.scene.arena.getTypeForBiome();
|
|
|
|
user.summonData.types = [ biomeType ];
|
|
user.updateInfo();
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoType", {pokemonName: getPokemonNameWithAffix(user), typeName: i18next.t(`pokemonInfo:Type.${Type[biomeType]}`)}));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class ChangeTypeAttr extends MoveEffectAttr {
|
|
private type: Type;
|
|
|
|
constructor(type: Type) {
|
|
super(false, MoveEffectTrigger.HIT);
|
|
|
|
this.type = type;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
target.summonData.types = [this.type];
|
|
target.updateInfo();
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoType", {pokemonName: getPokemonNameWithAffix(target), typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`)}));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !target.isTerastallized() && !target.hasAbility(Abilities.MULTITYPE) && !target.hasAbility(Abilities.RKS_SYSTEM) && !(target.getTypes().length === 1 && target.getTypes()[0] === this.type);
|
|
}
|
|
}
|
|
|
|
export class AddTypeAttr extends MoveEffectAttr {
|
|
private type: Type;
|
|
|
|
constructor(type: Type) {
|
|
super(false, MoveEffectTrigger.HIT);
|
|
|
|
this.type = type;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN); // TODO: Figure out some way to actually check if another version of this effect is already applied
|
|
if (this.type !== Type.UNKNOWN) {
|
|
types.push(this.type);
|
|
}
|
|
target.summonData.types = types;
|
|
target.updateInfo();
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:addType", {typeName: i18next.t(`pokemonInfo:Type.${Type[this.type]}`), pokemonName: getPokemonNameWithAffix(target)}));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !target.isTerastallized()&& !target.getTypes().includes(this.type);
|
|
}
|
|
}
|
|
|
|
export class FirstMoveTypeAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const firstMoveType = target.getMoveset()[0].getMove().type;
|
|
user.summonData.types = [ firstMoveType ];
|
|
user.scene.queueMessage(i18next.t("battle:transformedIntoType", {pokemonName: getPokemonNameWithAffix(user), type: i18next.t(`pokemonInfo:Type.${Type[firstMoveType]}`)}));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class RandomMovesetMoveAttr extends OverrideMoveEffectAttr {
|
|
private enemyMoveset: boolean;
|
|
|
|
constructor(enemyMoveset?: boolean) {
|
|
super();
|
|
|
|
this.enemyMoveset = enemyMoveset;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const moveset = (!this.enemyMoveset ? user : target).getMoveset();
|
|
const moves = moveset.filter(m => !m.getMove().hasFlag(MoveFlags.IGNORE_VIRTUAL));
|
|
if (moves.length) {
|
|
const move = moves[user.randSeedInt(moves.length)];
|
|
const moveIndex = moveset.findIndex(m => m.moveId === move.moveId);
|
|
const moveTargets = getMoveTargets(user, move.moveId);
|
|
if (!moveTargets.targets.length) {
|
|
return false;
|
|
}
|
|
let selectTargets: BattlerIndex[];
|
|
switch (true) {
|
|
case (moveTargets.multiple || moveTargets.targets.length === 1): {
|
|
selectTargets = moveTargets.targets;
|
|
break;
|
|
}
|
|
case (moveTargets.targets.indexOf(target.getBattlerIndex()) > -1): {
|
|
selectTargets = [ target.getBattlerIndex() ];
|
|
break;
|
|
}
|
|
default: {
|
|
moveTargets.targets.splice(moveTargets.targets.indexOf(user.getAlly().getBattlerIndex()));
|
|
selectTargets = [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ];
|
|
break;
|
|
}
|
|
}
|
|
const targets = selectTargets;
|
|
user.getMoveQueue().push({ move: move.moveId, targets: targets, ignorePP: true });
|
|
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, moveset[moveIndex], true));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class RandomMoveAttr extends OverrideMoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
const moveIds = Utils.getEnumValues(Moves).filter(m => !allMoves[m].hasFlag(MoveFlags.IGNORE_VIRTUAL) && !allMoves[m].name.endsWith(" (N)"));
|
|
const moveId = moveIds[user.randSeedInt(moveIds.length)];
|
|
|
|
const moveTargets = getMoveTargets(user, moveId);
|
|
if (!moveTargets.targets.length) {
|
|
resolve(false);
|
|
return;
|
|
}
|
|
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
|
? moveTargets.targets
|
|
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
|
? [ target.getBattlerIndex() ]
|
|
: [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ];
|
|
user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true });
|
|
user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true));
|
|
initMoveAnim(user.scene, moveId).then(() => {
|
|
loadMoveAnimAssets(user.scene, [ moveId ], true)
|
|
.then(() => resolve(true));
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export class NaturePowerAttr extends OverrideMoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
let moveId;
|
|
switch (user.scene.arena.getTerrainType()) {
|
|
// this allows terrains to 'override' the biome move
|
|
case TerrainType.NONE:
|
|
switch (user.scene.arena.biomeType) {
|
|
case Biome.TOWN:
|
|
moveId = Moves.ROUND;
|
|
break;
|
|
case Biome.METROPOLIS:
|
|
moveId = Moves.TRI_ATTACK;
|
|
break;
|
|
case Biome.SLUM:
|
|
moveId = Moves.SLUDGE_BOMB;
|
|
break;
|
|
case Biome.PLAINS:
|
|
moveId = Moves.SILVER_WIND;
|
|
break;
|
|
case Biome.GRASS:
|
|
moveId = Moves.GRASS_KNOT;
|
|
break;
|
|
case Biome.TALL_GRASS:
|
|
moveId = Moves.POLLEN_PUFF;
|
|
break;
|
|
case Biome.MEADOW:
|
|
moveId = Moves.GIGA_DRAIN;
|
|
break;
|
|
case Biome.FOREST:
|
|
moveId = Moves.BUG_BUZZ;
|
|
break;
|
|
case Biome.JUNGLE:
|
|
moveId = Moves.LEAF_STORM;
|
|
break;
|
|
case Biome.SEA:
|
|
moveId = Moves.HYDRO_PUMP;
|
|
break;
|
|
case Biome.SWAMP:
|
|
moveId = Moves.MUD_BOMB;
|
|
break;
|
|
case Biome.BEACH:
|
|
moveId = Moves.SCALD;
|
|
break;
|
|
case Biome.LAKE:
|
|
moveId = Moves.BUBBLE_BEAM;
|
|
break;
|
|
case Biome.SEABED:
|
|
moveId = Moves.BRINE;
|
|
break;
|
|
case Biome.ISLAND:
|
|
moveId = Moves.LEAF_TORNADO;
|
|
break;
|
|
case Biome.MOUNTAIN:
|
|
moveId = Moves.AIR_SLASH;
|
|
break;
|
|
case Biome.BADLANDS:
|
|
moveId = Moves.EARTH_POWER;
|
|
break;
|
|
case Biome.DESERT:
|
|
moveId = Moves.SCORCHING_SANDS;
|
|
break;
|
|
case Biome.WASTELAND:
|
|
moveId = Moves.DRAGON_PULSE;
|
|
break;
|
|
case Biome.CONSTRUCTION_SITE:
|
|
moveId = Moves.STEEL_BEAM;
|
|
break;
|
|
case Biome.CAVE:
|
|
moveId = Moves.POWER_GEM;
|
|
break;
|
|
case Biome.ICE_CAVE:
|
|
moveId = Moves.ICE_BEAM;
|
|
break;
|
|
case Biome.SNOWY_FOREST:
|
|
moveId = Moves.FROST_BREATH;
|
|
break;
|
|
case Biome.VOLCANO:
|
|
moveId = Moves.LAVA_PLUME;
|
|
break;
|
|
case Biome.GRAVEYARD:
|
|
moveId = Moves.SHADOW_BALL;
|
|
break;
|
|
case Biome.RUINS:
|
|
moveId = Moves.ANCIENT_POWER;
|
|
break;
|
|
case Biome.TEMPLE:
|
|
moveId = Moves.EXTRASENSORY;
|
|
break;
|
|
case Biome.DOJO:
|
|
moveId = Moves.FOCUS_BLAST;
|
|
break;
|
|
case Biome.FAIRY_CAVE:
|
|
moveId = Moves.ALLURING_VOICE;
|
|
break;
|
|
case Biome.ABYSS:
|
|
moveId = Moves.OMINOUS_WIND;
|
|
break;
|
|
case Biome.SPACE:
|
|
moveId = Moves.DRACO_METEOR;
|
|
break;
|
|
case Biome.FACTORY:
|
|
moveId = Moves.FLASH_CANNON;
|
|
break;
|
|
case Biome.LABORATORY:
|
|
moveId = Moves.ZAP_CANNON;
|
|
break;
|
|
case Biome.POWER_PLANT:
|
|
moveId = Moves.CHARGE_BEAM;
|
|
break;
|
|
case Biome.END:
|
|
moveId = Moves.ETERNABEAM;
|
|
break;
|
|
}
|
|
break;
|
|
case TerrainType.MISTY:
|
|
moveId = Moves.MOONBLAST;
|
|
break;
|
|
case TerrainType.ELECTRIC:
|
|
moveId = Moves.THUNDERBOLT;
|
|
break;
|
|
case TerrainType.GRASSY:
|
|
moveId = Moves.ENERGY_BALL;
|
|
break;
|
|
case TerrainType.PSYCHIC:
|
|
moveId = Moves.PSYCHIC;
|
|
break;
|
|
default:
|
|
// Just in case there's no match
|
|
moveId = Moves.TRI_ATTACK;
|
|
break;
|
|
}
|
|
|
|
user.getMoveQueue().push({ move: moveId, targets: [target.getBattlerIndex()], ignorePP: true });
|
|
user.scene.unshiftPhase(new MovePhase(user.scene, user, [target.getBattlerIndex()], new PokemonMove(moveId, 0, 0, true), true));
|
|
initMoveAnim(user.scene, moveId).then(() => {
|
|
loadMoveAnimAssets(user.scene, [ moveId ], true)
|
|
.then(() => resolve(true));
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
const lastMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
|
|
const copiableMove = user.scene.currentBattle.lastMove;
|
|
|
|
if (!copiableMove) {
|
|
return false;
|
|
}
|
|
|
|
if (allMoves[copiableMove].hasAttr(ChargeAttr)) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: Add last turn of Bide
|
|
|
|
return true;
|
|
};
|
|
|
|
export class CopyMoveAttr extends OverrideMoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const lastMove = user.scene.currentBattle.lastMove;
|
|
|
|
const moveTargets = getMoveTargets(user, lastMove);
|
|
if (!moveTargets.targets.length) {
|
|
return false;
|
|
}
|
|
|
|
const targets = moveTargets.multiple || moveTargets.targets.length === 1
|
|
? moveTargets.targets
|
|
: moveTargets.targets.indexOf(target.getBattlerIndex()) > -1
|
|
? [ target.getBattlerIndex() ]
|
|
: [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ];
|
|
user.getMoveQueue().push({ move: lastMove, targets: targets, ignorePP: true });
|
|
|
|
user.scene.unshiftPhase(new MovePhase(user.scene, user as PlayerPokemon, targets, new PokemonMove(lastMove, 0, 0, true), true));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return lastMoveCopiableCondition;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves that reduce PP of the target's last used move.
|
|
* Used for Spite.
|
|
*/
|
|
export class ReducePpMoveAttr extends MoveEffectAttr {
|
|
protected reduction: number;
|
|
constructor(reduction: number) {
|
|
super();
|
|
this.reduction = reduction;
|
|
}
|
|
|
|
/**
|
|
* Reduces the PP of the target's last-used move by an amount based on this attribute instance's {@linkcode reduction}.
|
|
*
|
|
* @param user {@linkcode Pokemon} that used the attack
|
|
* @param target {@linkcode Pokemon} targeted by the attack
|
|
* @param move {@linkcode Move} being used
|
|
* @param args N/A
|
|
* @returns {boolean} true
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
// Null checks can be skipped due to condition function
|
|
const lastMove = target.getLastXMoves().find(() => true);
|
|
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move);
|
|
const lastPpUsed = movesetMove.ppUsed;
|
|
movesetMove.ppUsed = Math.min(movesetMove.ppUsed + this.reduction, movesetMove.getMovePp());
|
|
|
|
const message = i18next.t("battle:ppReduced", {targetName: getPokemonNameWithAffix(target), moveName: movesetMove.getName(), reduction: movesetMove.ppUsed - lastPpUsed});
|
|
|
|
user.scene.queueMessage(message);
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
const lastMove = target.getLastXMoves().find(() => true);
|
|
if (lastMove) {
|
|
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move);
|
|
return !!movesetMove?.getPpRatio();
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
|
const lastMove = target.getLastXMoves().find(() => true);
|
|
if (lastMove) {
|
|
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move);
|
|
if (movesetMove) {
|
|
const maxPp = movesetMove.getMovePp();
|
|
const ppLeft = maxPp - movesetMove.ppUsed;
|
|
const value = -(8 - Math.ceil(Math.min(maxPp, 30) / 5));
|
|
if (ppLeft < 4) {
|
|
return (value / 4) * ppLeft;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves that damage target, and then reduce PP of the target's last used move.
|
|
* Used for Eerie Spell.
|
|
*/
|
|
export class AttackReducePpMoveAttr extends ReducePpMoveAttr {
|
|
constructor(reduction: number) {
|
|
super(reduction);
|
|
}
|
|
|
|
/**
|
|
* Checks if the target has used a move prior to the attack. PP-reduction is applied through the super class if so.
|
|
*
|
|
* @param user {@linkcode Pokemon} that used the attack
|
|
* @param target {@linkcode Pokemon} targeted by the attack
|
|
* @param move {@linkcode Move} being used
|
|
* @param args N/A
|
|
* @returns {boolean} true
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const lastMove = target.getLastXMoves().find(() => true);
|
|
if (lastMove) {
|
|
const movesetMove = target.getMoveset().find(m => m.moveId === lastMove.move);
|
|
if (Boolean(movesetMove?.getPpRatio())) {
|
|
super.apply(user, target, move, args);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Override condition function to always perform damage. Instead, perform pp-reduction condition check in apply function above
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => true;
|
|
}
|
|
}
|
|
|
|
// TODO: Review this
|
|
const targetMoveCopiableCondition: MoveConditionFunc = (user, target, move) => {
|
|
const targetMoves = target.getMoveHistory().filter(m => !m.virtual);
|
|
if (!targetMoves.length) {
|
|
return false;
|
|
}
|
|
|
|
const copiableMove = targetMoves[0];
|
|
|
|
if (!copiableMove.move) {
|
|
return false;
|
|
}
|
|
|
|
if (allMoves[copiableMove.move].hasAttr(ChargeAttr) && copiableMove.result === MoveResult.OTHER) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: Add last turn of Bide
|
|
|
|
return true;
|
|
};
|
|
|
|
export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const targetMoves = target.getMoveHistory().filter(m => !m.virtual);
|
|
if (!targetMoves.length) {
|
|
return false;
|
|
}
|
|
|
|
const copiedMove = allMoves[targetMoves[0].move];
|
|
|
|
const thisMoveIndex = user.getMoveset().findIndex(m => m.moveId === move.id);
|
|
|
|
if (thisMoveIndex === -1) {
|
|
return false;
|
|
}
|
|
|
|
user.summonData.moveset = user.getMoveset().slice(0);
|
|
user.summonData.moveset[thisMoveIndex] = new PokemonMove(copiedMove.id, 0, 0);
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:copiedMove", {pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name}));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return targetMoveCopiableCondition;
|
|
}
|
|
}
|
|
|
|
export class SketchAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const targetMove = target.getMoveHistory().filter(m => !m.virtual).at(-1);
|
|
if (!targetMove) {
|
|
return false;
|
|
}
|
|
|
|
const sketchedMove = allMoves[targetMove.move];
|
|
const sketchIndex = user.getMoveset().findIndex(m => m.moveId === move.id);
|
|
if (sketchIndex === -1) {
|
|
return false;
|
|
}
|
|
|
|
user.setMove(sketchIndex, sketchedMove.id);
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:sketchedMove", {pokemonName: getPokemonNameWithAffix(user), moveName: sketchedMove.name}));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
if (!targetMoveCopiableCondition(user, target, move)) {
|
|
return false;
|
|
}
|
|
|
|
const targetMoves = target.getMoveHistory().filter(m => !m.virtual);
|
|
if (!targetMoves.length) {
|
|
return false;
|
|
}
|
|
|
|
const sketchableMove = targetMoves[0];
|
|
|
|
if (user.getMoveset().find(m => m.moveId === sketchableMove.move)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
}
|
|
}
|
|
|
|
export class AbilityChangeAttr extends MoveEffectAttr {
|
|
public ability: Abilities;
|
|
|
|
constructor(ability: Abilities, selfTarget?: boolean) {
|
|
super(selfTarget, MoveEffectTrigger.HIT);
|
|
|
|
this.ability = ability;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
(this.selfTarget ? user : target).summonData.ability = this.ability;
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:acquiredAbility", {pokemonName: getPokemonNameWithAffix((this.selfTarget ? user : target)), abilityName: allAbilities[this.ability].name}));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !(this.selfTarget ? user : target).getAbility().hasAttr(UnsuppressableAbilityAbAttr) && (this.selfTarget ? user : target).getAbility().id !== this.ability;
|
|
}
|
|
}
|
|
|
|
export class AbilityCopyAttr extends MoveEffectAttr {
|
|
public copyToPartner: boolean;
|
|
|
|
constructor(copyToPartner: boolean = false) {
|
|
super(false, MoveEffectTrigger.HIT);
|
|
|
|
this.copyToPartner = copyToPartner;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
user.summonData.ability = target.getAbility().id;
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name}));
|
|
|
|
if (this.copyToPartner && user.scene.currentBattle?.double && user.getAlly().hp) {
|
|
user.getAlly().summonData.ability = target.getAbility().id;
|
|
user.getAlly().scene.queueMessage(i18next.t("moveTriggers:copiedTargetAbility", {pokemonName: getPokemonNameWithAffix(user.getAlly()), targetName: getPokemonNameWithAffix(target), abilityName: allAbilities[target.getAbility().id].name}));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
let ret = !target.getAbility().hasAttr(UncopiableAbilityAbAttr) && !user.getAbility().hasAttr(UnsuppressableAbilityAbAttr);
|
|
if (this.copyToPartner && user.scene.currentBattle?.double) {
|
|
ret = ret && (!user.getAlly().hp || !user.getAlly().getAbility().hasAttr(UnsuppressableAbilityAbAttr));
|
|
} else {
|
|
ret = ret && user.getAbility().id !== target.getAbility().id;
|
|
}
|
|
return ret;
|
|
};
|
|
}
|
|
}
|
|
|
|
export class AbilityGiveAttr extends MoveEffectAttr {
|
|
public copyToPartner: boolean;
|
|
|
|
constructor() {
|
|
super(false, MoveEffectTrigger.HIT);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
target.summonData.ability = user.getAbility().id;
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:acquiredAbility", {pokemonName: getPokemonNameWithAffix(target), abilityName: allAbilities[user.getAbility().id].name}));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !user.getAbility().hasAttr(UncopiableAbilityAbAttr) && !target.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && user.getAbility().id !== target.getAbility().id;
|
|
}
|
|
}
|
|
|
|
export class SwitchAbilitiesAttr extends MoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const tempAbilityId = user.getAbility().id;
|
|
user.summonData.ability = target.getAbility().id;
|
|
target.summonData.ability = tempAbilityId;
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", {pokemonName: getPokemonNameWithAffix(user)}));
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !user.getAbility().hasAttr(UnswappableAbilityAbAttr) && !target.getAbility().hasAttr(UnswappableAbilityAbAttr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for moves that suppress abilities like {@linkcode Moves.GASTRO_ACID}.
|
|
* A suppressed ability cannot be activated.
|
|
*
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode apply}
|
|
* @see {@linkcode getCondition}
|
|
*/
|
|
export class SuppressAbilitiesAttr extends MoveEffectAttr {
|
|
/** Sets ability suppression for the target pokemon and displays a message. */
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
target.summonData.abilitySuppressed = true;
|
|
|
|
target.scene.queueMessage(getPokemonMessage(target, "'s ability\nwas suppressed!"));
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Causes the effect to fail when the target's ability is unsupressable or already suppressed. */
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => !target.getAbility().hasAttr(UnsuppressableAbilityAbAttr) && !target.summonData.abilitySuppressed;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies the effects of {@linkcode SuppressAbilitiesAttr} if the target has already moved this turn.
|
|
* @extends MoveEffectAttr
|
|
* @see {@linkcode Moves.CORE_ENFORCER} (the move which uses this effect)
|
|
*/
|
|
export class SuppressAbilitiesIfActedAttr extends MoveEffectAttr {
|
|
/**
|
|
* If the target has already acted this turn, apply a {@linkcode SuppressAbilitiesAttr} effect unless the
|
|
* abillity cannot be suppressed. This is a secondary effect and has no bearing on the success or failure of the move.
|
|
*
|
|
* @returns True if the move occurred, otherwise false. Note that true will be returned even if the target has not
|
|
* yet moved or if the suppression failed to apply.
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
if (target.turnData.acted) {
|
|
const suppressAttr = new SuppressAbilitiesAttr();
|
|
if (suppressAttr.getCondition()(user, target, move)) {
|
|
suppressAttr.apply(user, target, move, args);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class TransformAttr extends MoveEffectAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
|
return new Promise(resolve => {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return resolve(false);
|
|
}
|
|
|
|
user.summonData.speciesForm = target.getSpeciesForm();
|
|
user.summonData.fusionSpeciesForm = target.getFusionSpeciesForm();
|
|
user.summonData.ability = target.getAbility().id;
|
|
user.summonData.gender = target.getGender();
|
|
user.summonData.fusionGender = target.getFusionGender();
|
|
user.summonData.stats = [ user.stats[Stat.HP] ].concat(target.stats.slice(1));
|
|
user.summonData.battleStats = target.summonData.battleStats.slice(0);
|
|
user.summonData.moveset = target.getMoveset().map(m => new PokemonMove(m.moveId, m.ppUsed, m.ppUp));
|
|
user.summonData.types = target.getTypes();
|
|
|
|
user.scene.queueMessage(i18next.t("moveTriggers:transformedIntoTarget", {pokemonName: getPokemonNameWithAffix(user), targetName: getPokemonNameWithAffix(target)}));
|
|
|
|
user.loadAssets(false).then(() => {
|
|
user.playAnim();
|
|
resolve(true);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export class DiscourageFrequentUseAttr extends MoveAttr {
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
const lastMoves = user.getLastXMoves(4);
|
|
console.log(lastMoves);
|
|
for (let m = 0; m < lastMoves.length; m++) {
|
|
if (lastMoves[m].move === move.id) {
|
|
return (4 - (m + 1)) * -10;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export class MoneyAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true, MoveEffectTrigger.HIT, true);
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
|
user.scene.currentBattle.moneyScattered += user.scene.getWaveMoneyAmount(0.2);
|
|
user.scene.queueMessage(i18next.t("moveTriggers:coinsScatteredEverywhere"));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies {@linkcode BattlerTagType.DESTINY_BOND} to the user.
|
|
*
|
|
* @extends MoveEffectAttr
|
|
*/
|
|
export class DestinyBondAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true, MoveEffectTrigger.PRE_APPLY);
|
|
}
|
|
|
|
/**
|
|
* Applies {@linkcode BattlerTagType.DESTINY_BOND} to the user.
|
|
* @param user {@linkcode Pokemon} that is having the tag applied to.
|
|
* @param target {@linkcode Pokemon} N/A
|
|
* @param move {@linkcode Move} {@linkcode Move.DESTINY_BOND}
|
|
* @param {any[]} args N/A
|
|
* @returns true
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
user.scene.queueMessage(`${i18next.t("moveTriggers:tryingToTakeFoeDown", {pokemonName: getPokemonNameWithAffix(user)})}`);
|
|
user.addTag(BattlerTagType.DESTINY_BOND, undefined, move.id, user.id);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class LastResortAttr extends MoveAttr {
|
|
getCondition(): MoveConditionFunc {
|
|
return (user: Pokemon, target: Pokemon, move: Move) => {
|
|
const uniqueUsedMoveIds = new Set<Moves>();
|
|
const movesetMoveIds = user.getMoveset().map(m => m.moveId);
|
|
user.getMoveHistory().map(m => {
|
|
if (m.move !== move.id && movesetMoveIds.find(mm => mm === m.move)) {
|
|
uniqueUsedMoveIds.add(m.move);
|
|
}
|
|
});
|
|
return uniqueUsedMoveIds.size >= movesetMoveIds.length - 1;
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* The move only works if the target has a transferable held item
|
|
* @extends MoveAttr
|
|
* @see {@linkcode getCondition}
|
|
*/
|
|
export class AttackedByItemAttr extends MoveAttr {
|
|
/**
|
|
* @returns the {@linkcode MoveConditionFunc} for this {@linkcode Move}
|
|
*/
|
|
getCondition(): MoveConditionFunc {
|
|
return (user: Pokemon, target: Pokemon, move: Move) => {
|
|
const heldItems = target.getHeldItems().filter(i => i.isTransferrable);
|
|
if (heldItems.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
const itemName = heldItems[0]?.type?.name ?? "item";
|
|
target.scene.queueMessage(i18next.t("moveTriggers:attackedByItem", {pokemonName: getPokemonNameWithAffix(target), itemName: itemName}));
|
|
|
|
return true;
|
|
};
|
|
}
|
|
}
|
|
|
|
export class VariableTargetAttr extends MoveAttr {
|
|
private targetChangeFunc: (user: Pokemon, target: Pokemon, move: Move) => number;
|
|
|
|
constructor(targetChange: (user: Pokemon, target: Pokemon, move: Move) => number) {
|
|
super();
|
|
|
|
this.targetChangeFunc = targetChange;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const targetVal = args[0] as Utils.NumberHolder;
|
|
targetVal.value = this.targetChangeFunc(user, target, move);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const failOnGravityCondition: MoveConditionFunc = (user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY);
|
|
|
|
const failOnBossCondition: MoveConditionFunc = (user, target, move) => !target.isBossImmune();
|
|
|
|
const failOnMaxCondition: MoveConditionFunc = (user, target, move) => !target.isMax();
|
|
|
|
const failIfDampCondition: MoveConditionFunc = (user, target, move) => {
|
|
const cancelled = new Utils.BooleanHolder(false);
|
|
user.scene.getField(true).map(p=>applyAbAttrs(FieldPreventExplosiveMovesAbAttr, p, cancelled));
|
|
// Queue a message if an ability prevented usage of the move
|
|
if (cancelled.value) {
|
|
user.scene.queueMessage(i18next.t("moveTriggers:cannotUseMove", {pokemonName: getPokemonNameWithAffix(user), moveName: move.name}));
|
|
}
|
|
return !cancelled.value;
|
|
};
|
|
|
|
const userSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => user.status?.effect === StatusEffect.SLEEP || user.hasAbility(Abilities.COMATOSE);
|
|
|
|
const targetSleptOrComatoseCondition: MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => target.status?.effect === StatusEffect.SLEEP || target.hasAbility(Abilities.COMATOSE);
|
|
|
|
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
|
|
|
|
function applyMoveAttrsInternal(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<void> {
|
|
return new Promise(resolve => {
|
|
const attrPromises: Promise<boolean>[] = [];
|
|
const moveAttrs = move.attrs.filter(a => attrFilter(a));
|
|
for (const attr of moveAttrs) {
|
|
const result = attr.apply(user, target, move, args);
|
|
if (result instanceof Promise) {
|
|
attrPromises.push(result);
|
|
}
|
|
}
|
|
Promise.allSettled(attrPromises).then(() => resolve());
|
|
});
|
|
}
|
|
|
|
export function applyMoveAttrs(attrType: Constructor<MoveAttr>, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> {
|
|
return applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
|
|
}
|
|
|
|
export function applyFilteredMoveAttrs(attrFilter: MoveAttrFilter, user: Pokemon, target: Pokemon, move: Move, ...args: any[]): Promise<void> {
|
|
return applyMoveAttrsInternal(attrFilter, user, target, move, args);
|
|
}
|
|
|
|
export class MoveCondition {
|
|
protected func: MoveConditionFunc;
|
|
|
|
constructor(func: MoveConditionFunc) {
|
|
this.func = func;
|
|
}
|
|
|
|
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
|
|
return this.func(user, target, move);
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export class FirstMoveCondition extends MoveCondition {
|
|
constructor() {
|
|
super((user, target, move) => user.battleSummonData?.turnCount === 1);
|
|
}
|
|
|
|
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
|
return this.apply(user, target, move) ? 10 : -20;
|
|
}
|
|
}
|
|
|
|
export class hitsSameTypeAttr extends VariableMoveTypeMultiplierAttr {
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
const multiplier = args[0] as Utils.NumberHolder;
|
|
if (!user.getTypes().some(type => target.getTypes().includes(type))) {
|
|
multiplier.value = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attribute used for Conversion 2, to convert the user's type to a random type that resists the target's last used move.
|
|
* Fails if the user already has ALL types that resist the target's last used move.
|
|
* Fails if the opponent has not used a move yet
|
|
* Fails if the type is unknown or stellar
|
|
*
|
|
* TODO:
|
|
* If a move has its type changed (e.g. {@linkcode Moves.HIDDEN_POWER}), it will check the new type.
|
|
*/
|
|
export class ResistLastMoveTypeAttr extends MoveEffectAttr {
|
|
constructor() {
|
|
super(true);
|
|
}
|
|
/**
|
|
* User changes its type to a random type that resists the target's last used move
|
|
* @param {Pokemon} user Pokemon that used the move and will change types
|
|
* @param {Pokemon} target Opposing pokemon that recently used a move
|
|
* @param {Move} move Move being used
|
|
* @param {any[]} args Unused
|
|
* @returns {boolean} true if the function succeeds
|
|
*/
|
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
|
if (!super.apply(user, target, move, args)) {
|
|
return false;
|
|
}
|
|
|
|
const [targetMove] = target.getLastXMoves(1); // target's most recent move
|
|
if (!targetMove) {
|
|
return false;
|
|
}
|
|
|
|
const moveData = allMoves[targetMove.move];
|
|
if (moveData.type === Type.STELLAR || moveData.type === Type.UNKNOWN) {
|
|
return false;
|
|
}
|
|
const userTypes = user.getTypes();
|
|
const validTypes = getTypeResistances(moveData.type).filter(t => !userTypes.includes(t)); // valid types are ones that are not already the user's types
|
|
if (!validTypes.length) {
|
|
return false;
|
|
}
|
|
const type = validTypes[user.randSeedInt(validTypes.length)];
|
|
user.summonData.types = [ type ];
|
|
user.scene.queueMessage(i18next.t("battle:transformedIntoType", {pokemonName: getPokemonNameWithAffix(user), type: Utils.toReadableString(Type[type])}));
|
|
user.updateInfo();
|
|
|
|
return true;
|
|
}
|
|
|
|
getCondition(): MoveConditionFunc {
|
|
return (user, target, move) => {
|
|
const moveHistory = target.getLastXMoves();
|
|
return moveHistory.length !== 0;
|
|
};
|
|
}
|
|
}
|
|
|
|
const unknownTypeCondition: MoveConditionFunc = (user, target, move) => !user.getTypes().includes(Type.UNKNOWN);
|
|
|
|
export type MoveTargetSet = {
|
|
targets: BattlerIndex[];
|
|
multiple: boolean;
|
|
};
|
|
|
|
export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
|
|
const variableTarget = new Utils.NumberHolder(0);
|
|
user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
|
|
|
|
const moveTarget = allMoves[move].hasAttr(VariableTargetAttr) ? variableTarget.value : move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
|
|
const opponents = user.getOpponents();
|
|
|
|
let set: Pokemon[] = [];
|
|
let multiple = false;
|
|
|
|
switch (moveTarget) {
|
|
case MoveTarget.USER:
|
|
case MoveTarget.PARTY:
|
|
set = [ user ];
|
|
break;
|
|
case MoveTarget.NEAR_OTHER:
|
|
case MoveTarget.OTHER:
|
|
case MoveTarget.ALL_NEAR_OTHERS:
|
|
case MoveTarget.ALL_OTHERS:
|
|
set = (opponents.concat([ user.getAlly() ]));
|
|
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS;
|
|
break;
|
|
case MoveTarget.NEAR_ENEMY:
|
|
case MoveTarget.ALL_NEAR_ENEMIES:
|
|
case MoveTarget.ALL_ENEMIES:
|
|
case MoveTarget.ENEMY_SIDE:
|
|
set = opponents;
|
|
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
|
|
break;
|
|
case MoveTarget.RANDOM_NEAR_ENEMY:
|
|
set = [ opponents[user.randSeedInt(opponents.length)] ];
|
|
break;
|
|
case MoveTarget.ATTACKER:
|
|
return { targets: [ -1 as BattlerIndex ], multiple: false };
|
|
case MoveTarget.NEAR_ALLY:
|
|
case MoveTarget.ALLY:
|
|
set = [ user.getAlly() ];
|
|
break;
|
|
case MoveTarget.USER_OR_NEAR_ALLY:
|
|
case MoveTarget.USER_AND_ALLIES:
|
|
case MoveTarget.USER_SIDE:
|
|
set = [ user, user.getAlly() ];
|
|
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
|
|
break;
|
|
case MoveTarget.ALL:
|
|
case MoveTarget.BOTH_SIDES:
|
|
set = [ user, user.getAlly() ].concat(opponents);
|
|
multiple = true;
|
|
break;
|
|
case MoveTarget.CURSE:
|
|
set = user.getTypes(true).includes(Type.GHOST) ? (opponents.concat([ user.getAlly() ])) : [ user ];
|
|
break;
|
|
}
|
|
|
|
return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple };
|
|
}
|
|
|
|
export const allMoves: Move[] = [
|
|
new SelfStatusMove(Moves.NONE, Type.NORMAL, MoveCategory.STATUS, -1, -1, 0, 1),
|
|
];
|
|
|
|
export const selfStatLowerMoves: Moves[] = [];
|
|
|
|
export function initMoves() {
|
|
allMoves.push(
|
|
new AttackMove(Moves.POUND, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
|
|
new AttackMove(Moves.KARATE_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 1)
|
|
.attr(HighCritAttr),
|
|
new AttackMove(Moves.DOUBLE_SLAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 10, -1, 0, 1)
|
|
.attr(MultiHitAttr),
|
|
new AttackMove(Moves.COMET_PUNCH, Type.NORMAL, MoveCategory.PHYSICAL, 18, 85, 15, -1, 0, 1)
|
|
.attr(MultiHitAttr)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.MEGA_PUNCH, Type.NORMAL, MoveCategory.PHYSICAL, 80, 85, 20, -1, 0, 1)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.PAY_DAY, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 1)
|
|
.attr(MoneyAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.FIRE_PUNCH, Type.FIRE, MoveCategory.PHYSICAL, 75, 100, 15, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.ICE_PUNCH, Type.ICE, MoveCategory.PHYSICAL, 75, 100, 15, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.THUNDER_PUNCH, Type.ELECTRIC, MoveCategory.PHYSICAL, 75, 100, 15, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.SCRATCH, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
|
|
new AttackMove(Moves.VISE_GRIP, Type.NORMAL, MoveCategory.PHYSICAL, 55, 100, 30, -1, 0, 1),
|
|
new AttackMove(Moves.GUILLOTINE, Type.NORMAL, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
|
|
.attr(OneHitKOAttr)
|
|
.attr(OneHitKOAccuracyAttr),
|
|
new AttackMove(Moves.RAZOR_WIND, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 1)
|
|
.attr(ChargeAttr, ChargeAnim.RAZOR_WIND_CHARGING, i18next.t("moveTriggers:whippedUpAWhirlwind", {pokemonName: "{USER}"}))
|
|
.attr(HighCritAttr)
|
|
.windMove()
|
|
.ignoresVirtual()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new SelfStatusMove(Moves.SWORDS_DANCE, Type.NORMAL, -1, 20, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 2, true)
|
|
.danceMove(),
|
|
new AttackMove(Moves.CUT, Type.NORMAL, MoveCategory.PHYSICAL, 50, 95, 30, -1, 0, 1)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.GUST, Type.FLYING, MoveCategory.SPECIAL, 40, 100, 35, -1, 0, 1)
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
|
|
.windMove(),
|
|
new AttackMove(Moves.WING_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, 0, 1),
|
|
new StatusMove(Moves.WHIRLWIND, Type.NORMAL, -1, 20, -1, -6, 1)
|
|
.attr(ForceSwitchOutAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
|
.hidesTarget()
|
|
.windMove(),
|
|
new AttackMove(Moves.FLY, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, -1, 0, 1)
|
|
.attr(ChargeAttr, ChargeAnim.FLY_CHARGING, i18next.t("moveTriggers:flewUpHigh", {pokemonName: "{USER}"}), BattlerTagType.FLYING)
|
|
.condition(failOnGravityCondition)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.BIND, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1)
|
|
.attr(TrapAttr, BattlerTagType.BIND),
|
|
new AttackMove(Moves.SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 80, 75, 20, -1, 0, 1),
|
|
new AttackMove(Moves.VINE_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 45, 100, 25, -1, 0, 1),
|
|
new AttackMove(Moves.STOMP, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 1)
|
|
.attr(MinimizeAccuracyAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.DOUBLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 30, 100, 30, -1, 0, 1)
|
|
.attr(MultiHitAttr, MultiHitType._2),
|
|
new AttackMove(Moves.MEGA_KICK, Type.NORMAL, MoveCategory.PHYSICAL, 120, 75, 5, -1, 0, 1),
|
|
new AttackMove(Moves.JUMP_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 1)
|
|
.attr(MissEffectAttr, crashDamageFunc)
|
|
.attr(NoEffectAttr, crashDamageFunc)
|
|
.condition(failOnGravityCondition)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.ROLLING_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 85, 15, 30, 0, 1)
|
|
.attr(FlinchAttr),
|
|
new StatusMove(Moves.SAND_ATTACK, Type.GROUND, 100, 15, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new AttackMove(Moves.HEADBUTT, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 15, 30, 0, 1)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.HORN_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 65, 100, 25, -1, 0, 1),
|
|
new AttackMove(Moves.FURY_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1)
|
|
.attr(MultiHitAttr),
|
|
new AttackMove(Moves.HORN_DRILL, Type.NORMAL, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
|
|
.attr(OneHitKOAttr)
|
|
.attr(OneHitKOAccuracyAttr),
|
|
new AttackMove(Moves.TACKLE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 35, -1, 0, 1),
|
|
new AttackMove(Moves.BODY_SLAM, Type.NORMAL, MoveCategory.PHYSICAL, 85, 100, 15, 30, 0, 1)
|
|
.attr(MinimizeAccuracyAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.WRAP, Type.NORMAL, MoveCategory.PHYSICAL, 15, 90, 20, -1, 0, 1)
|
|
.attr(TrapAttr, BattlerTagType.WRAP),
|
|
new AttackMove(Moves.TAKE_DOWN, Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, -1, 0, 1)
|
|
.attr(RecoilAttr)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.THRASH, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 1)
|
|
.attr(FrenzyAttr)
|
|
.attr(MissEffectAttr, frenzyMissFunc)
|
|
.attr(NoEffectAttr, frenzyMissFunc)
|
|
.target(MoveTarget.RANDOM_NEAR_ENEMY),
|
|
new AttackMove(Moves.DOUBLE_EDGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 1)
|
|
.attr(RecoilAttr, false, 0.33)
|
|
.recklessMove(),
|
|
new StatusMove(Moves.TAIL_WHIP, Type.NORMAL, 100, 30, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.POISON_STING, Type.POISON, MoveCategory.PHYSICAL, 15, 100, 35, 30, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.TWINEEDLE, Type.BUG, MoveCategory.PHYSICAL, 25, 100, 20, 20, 0, 1)
|
|
.attr(MultiHitAttr, MultiHitType._2)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.PIN_MISSILE, Type.BUG, MoveCategory.PHYSICAL, 25, 95, 20, -1, 0, 1)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false),
|
|
new StatusMove(Moves.LEER, Type.NORMAL, 100, 30, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.BITE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 1)
|
|
.attr(FlinchAttr)
|
|
.bitingMove(),
|
|
new StatusMove(Moves.GROWL, Type.NORMAL, 100, 40, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.ROAR, Type.NORMAL, -1, 20, -1, -6, 1)
|
|
.attr(ForceSwitchOutAttr)
|
|
.soundBased()
|
|
.hidesTarget(),
|
|
new StatusMove(Moves.SING, Type.NORMAL, 55, 15, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
|
.soundBased(),
|
|
new StatusMove(Moves.SUPERSONIC, Type.NORMAL, 55, 20, -1, 0, 1)
|
|
.attr(ConfuseAttr)
|
|
.soundBased(),
|
|
new AttackMove(Moves.SONIC_BOOM, Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, 0, 1)
|
|
.attr(FixedDamageAttr, 20),
|
|
new StatusMove(Moves.DISABLE, Type.NORMAL, 100, 20, -1, 0, 1)
|
|
.attr(DisableMoveAttr)
|
|
.condition(failOnMaxCondition),
|
|
new AttackMove(Moves.ACID, Type.POISON, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.EMBER, Type.FIRE, MoveCategory.SPECIAL, 40, 100, 25, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new AttackMove(Moves.FLAMETHROWER, Type.FIRE, MoveCategory.SPECIAL, 90, 100, 15, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new StatusMove(Moves.MIST, Type.ICE, -1, 30, -1, 0, 1)
|
|
.attr(AddArenaTagAttr, ArenaTagType.MIST, 5, true)
|
|
.target(MoveTarget.USER_SIDE),
|
|
new AttackMove(Moves.WATER_GUN, Type.WATER, MoveCategory.SPECIAL, 40, 100, 25, -1, 0, 1),
|
|
new AttackMove(Moves.HYDRO_PUMP, Type.WATER, MoveCategory.SPECIAL, 110, 80, 5, -1, 0, 1),
|
|
new AttackMove(Moves.SURF, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 1)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
|
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
|
|
new AttackMove(Moves.ICE_BEAM, Type.ICE, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.FREEZE),
|
|
new AttackMove(Moves.BLIZZARD, Type.ICE, MoveCategory.SPECIAL, 110, 70, 5, 10, 0, 1)
|
|
.attr(BlizzardAccuracyAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.PSYBEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1)
|
|
.attr(ConfuseAttr),
|
|
new AttackMove(Moves.BUBBLE_BEAM, Type.WATER, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
|
new AttackMove(Moves.AURORA_BEAM, Type.ICE, MoveCategory.SPECIAL, 65, 100, 20, 10, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new AttackMove(Moves.HYPER_BEAM, Type.NORMAL, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 1)
|
|
.attr(RechargeAttr),
|
|
new AttackMove(Moves.PECK, Type.FLYING, MoveCategory.PHYSICAL, 35, 100, 35, -1, 0, 1),
|
|
new AttackMove(Moves.DRILL_PECK, Type.FLYING, MoveCategory.PHYSICAL, 80, 100, 20, -1, 0, 1),
|
|
new AttackMove(Moves.SUBMISSION, Type.FIGHTING, MoveCategory.PHYSICAL, 80, 80, 20, -1, 0, 1)
|
|
.attr(RecoilAttr)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.LOW_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1)
|
|
.attr(WeightPowerAttr)
|
|
.condition(failOnMaxCondition),
|
|
new AttackMove(Moves.COUNTER, Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, -5, 1)
|
|
.attr(CounterDamageAttr, (move: Move) => move.category === MoveCategory.PHYSICAL, 2)
|
|
.target(MoveTarget.ATTACKER),
|
|
new AttackMove(Moves.SEISMIC_TOSS, Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 1)
|
|
.attr(LevelDamageAttr),
|
|
new AttackMove(Moves.STRENGTH, Type.NORMAL, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 1),
|
|
new AttackMove(Moves.ABSORB, Type.GRASS, MoveCategory.SPECIAL, 20, 100, 25, -1, 0, 1)
|
|
.attr(HitHealAttr)
|
|
.triageMove(),
|
|
new AttackMove(Moves.MEGA_DRAIN, Type.GRASS, MoveCategory.SPECIAL, 40, 100, 15, -1, 0, 1)
|
|
.attr(HitHealAttr)
|
|
.triageMove(),
|
|
new StatusMove(Moves.LEECH_SEED, Type.GRASS, 90, 10, -1, 0, 1)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.SEEDED)
|
|
.condition((user, target, move) => !target.getTag(BattlerTagType.SEEDED) && !target.isOfType(Type.GRASS)),
|
|
new SelfStatusMove(Moves.GROWTH, Type.NORMAL, -1, 20, -1, 0, 1)
|
|
.attr(GrowthStatChangeAttr),
|
|
new AttackMove(Moves.RAZOR_LEAF, Type.GRASS, MoveCategory.PHYSICAL, 55, 95, 25, -1, 0, 1)
|
|
.attr(HighCritAttr)
|
|
.makesContact(false)
|
|
.slicingMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.SOLAR_BEAM, Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 1)
|
|
.attr(SunlightChargeAttr, ChargeAnim.SOLAR_BEAM_CHARGING, i18next.t("moveTriggers:tookInSunlight", {pokemonName: "{USER}"}))
|
|
.attr(AntiSunlightPowerDecreaseAttr)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.POISON_POWDER, Type.POISON, 75, 35, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.powderMove(),
|
|
new StatusMove(Moves.STUN_SPORE, Type.GRASS, 75, 30, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.powderMove(),
|
|
new StatusMove(Moves.SLEEP_POWDER, Type.GRASS, 75, 15, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
|
.powderMove(),
|
|
new AttackMove(Moves.PETAL_DANCE, Type.GRASS, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 1)
|
|
.attr(FrenzyAttr)
|
|
.attr(MissEffectAttr, frenzyMissFunc)
|
|
.attr(NoEffectAttr, frenzyMissFunc)
|
|
.makesContact()
|
|
.danceMove()
|
|
.target(MoveTarget.RANDOM_NEAR_ENEMY),
|
|
new StatusMove(Moves.STRING_SHOT, Type.BUG, 95, 40, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -2)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.DRAGON_RAGE, Type.DRAGON, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 1)
|
|
.attr(FixedDamageAttr, 40),
|
|
new AttackMove(Moves.FIRE_SPIN, Type.FIRE, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 1)
|
|
.attr(TrapAttr, BattlerTagType.FIRE_SPIN),
|
|
new AttackMove(Moves.THUNDER_SHOCK, Type.ELECTRIC, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 90, 100, 15, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new StatusMove(Moves.THUNDER_WAVE, Type.ELECTRIC, 90, 20, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.attr(StatusMoveTypeImmunityAttr, Type.GROUND),
|
|
new AttackMove(Moves.THUNDER, Type.ELECTRIC, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.attr(ThunderAccuracyAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING, false),
|
|
new AttackMove(Moves.ROCK_THROW, Type.ROCK, MoveCategory.PHYSICAL, 50, 90, 15, -1, 0, 1)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.EARTHQUAKE, Type.GROUND, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 1)
|
|
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.FISSURE, Type.GROUND, MoveCategory.PHYSICAL, 200, 30, 5, -1, 0, 1)
|
|
.attr(OneHitKOAttr)
|
|
.attr(OneHitKOAccuracyAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, false)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.DIG, Type.GROUND, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1)
|
|
.attr(ChargeAttr, ChargeAnim.DIG_CHARGING, i18next.t("moveTriggers:dugAHole", {pokemonName: "{USER}"}), BattlerTagType.UNDERGROUND)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.TOXIC, Type.POISON, 90, 10, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.TOXIC)
|
|
.attr(ToxicAccuracyAttr),
|
|
new AttackMove(Moves.CONFUSION, Type.PSYCHIC, MoveCategory.SPECIAL, 50, 100, 25, 10, 0, 1)
|
|
.attr(ConfuseAttr),
|
|
new AttackMove(Moves.PSYCHIC, Type.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1),
|
|
new StatusMove(Moves.HYPNOSIS, Type.PSYCHIC, 60, 20, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP),
|
|
new SelfStatusMove(Moves.MEDITATE, Type.PSYCHIC, -1, 40, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 1, true),
|
|
new SelfStatusMove(Moves.AGILITY, Type.PSYCHIC, -1, 30, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 2, true),
|
|
new AttackMove(Moves.QUICK_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 1),
|
|
new AttackMove(Moves.RAGE, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, 0, 1)
|
|
.partial(),
|
|
new SelfStatusMove(Moves.TELEPORT, Type.PSYCHIC, -1, 20, -1, -6, 1)
|
|
.attr(ForceSwitchOutAttr, true)
|
|
.hidesUser(),
|
|
new AttackMove(Moves.NIGHT_SHADE, Type.GHOST, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1)
|
|
.attr(LevelDamageAttr),
|
|
new StatusMove(Moves.MIMIC, Type.NORMAL, -1, 10, -1, 0, 1)
|
|
.attr(MovesetCopyMoveAttr)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.SCREECH, Type.NORMAL, 85, 40, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -2)
|
|
.soundBased(),
|
|
new SelfStatusMove(Moves.DOUBLE_TEAM, Type.NORMAL, -1, 15, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.EVA, 1, true),
|
|
new SelfStatusMove(Moves.RECOVER, Type.NORMAL, -1, 5, -1, 0, 1)
|
|
.attr(HealAttr, 0.5)
|
|
.triageMove(),
|
|
new SelfStatusMove(Moves.HARDEN, Type.NORMAL, -1, 30, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 1, true),
|
|
new SelfStatusMove(Moves.MINIMIZE, Type.NORMAL, -1, 10, -1, 0, 1)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.MINIMIZED, true, false)
|
|
.attr(StatChangeAttr, BattleStat.EVA, 2, true),
|
|
new StatusMove(Moves.SMOKESCREEN, Type.NORMAL, 100, 20, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new StatusMove(Moves.CONFUSE_RAY, Type.GHOST, 100, 10, -1, 0, 1)
|
|
.attr(ConfuseAttr),
|
|
new SelfStatusMove(Moves.WITHDRAW, Type.WATER, -1, 40, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 1, true),
|
|
new SelfStatusMove(Moves.DEFENSE_CURL, Type.NORMAL, -1, 40, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 1, true),
|
|
new SelfStatusMove(Moves.BARRIER, Type.PSYCHIC, -1, 20, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 2, true),
|
|
new StatusMove(Moves.LIGHT_SCREEN, Type.PSYCHIC, -1, 30, -1, 0, 1)
|
|
.attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, true)
|
|
.target(MoveTarget.USER_SIDE),
|
|
new StatusMove(Moves.HAZE, Type.ICE, -1, 30, -1, 0, 1)
|
|
.target(MoveTarget.BOTH_SIDES)
|
|
.attr(ResetStatsAttr),
|
|
new StatusMove(Moves.REFLECT, Type.PSYCHIC, -1, 20, -1, 0, 1)
|
|
.attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, true)
|
|
.target(MoveTarget.USER_SIDE),
|
|
new SelfStatusMove(Moves.FOCUS_ENERGY, Type.NORMAL, -1, 30, -1, 0, 1)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, true, true),
|
|
new AttackMove(Moves.BIDE, Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 10, -1, 1, 1)
|
|
.ignoresVirtual()
|
|
.target(MoveTarget.USER)
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.METRONOME, Type.NORMAL, -1, 10, -1, 0, 1)
|
|
.attr(RandomMoveAttr)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.MIRROR_MOVE, Type.FLYING, -1, 20, -1, 0, 1)
|
|
.attr(CopyMoveAttr)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SELF_DESTRUCT, Type.NORMAL, MoveCategory.PHYSICAL, 200, 100, 5, -1, 0, 1)
|
|
.attr(SacrificialAttr)
|
|
.makesContact(false)
|
|
.condition(failIfDampCondition)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.EGG_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 100, 75, 10, -1, 0, 1)
|
|
.makesContact(false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.LICK, Type.GHOST, MoveCategory.PHYSICAL, 30, 100, 30, 30, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.SMOG, Type.POISON, MoveCategory.SPECIAL, 30, 70, 20, 40, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON),
|
|
new AttackMove(Moves.SLUDGE, Type.POISON, MoveCategory.SPECIAL, 65, 100, 20, 30, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON),
|
|
new AttackMove(Moves.BONE_CLUB, Type.GROUND, MoveCategory.PHYSICAL, 65, 85, 20, 10, 0, 1)
|
|
.attr(FlinchAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.FIRE_BLAST, Type.FIRE, MoveCategory.SPECIAL, 110, 85, 5, 10, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new AttackMove(Moves.WATERFALL, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 1)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.CLAMP, Type.WATER, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 1)
|
|
.attr(TrapAttr, BattlerTagType.CLAMP),
|
|
new AttackMove(Moves.SWIFT, Type.NORMAL, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.SKULL_BASH, Type.NORMAL, MoveCategory.PHYSICAL, 130, 100, 10, -1, 0, 1)
|
|
.attr(ChargeAttr, ChargeAnim.SKULL_BASH_CHARGING, i18next.t("moveTriggers:loweredItsHead", {pokemonName: "{USER}"}), null, true)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 1, true)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SPIKE_CANNON, Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 15, -1, 0, 1)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.CONSTRICT, Type.NORMAL, MoveCategory.PHYSICAL, 10, 100, 35, 10, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
|
new SelfStatusMove(Moves.AMNESIA, Type.PSYCHIC, -1, 20, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, 2, true),
|
|
new StatusMove(Moves.KINESIS, Type.PSYCHIC, 80, 15, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new SelfStatusMove(Moves.SOFT_BOILED, Type.NORMAL, -1, 5, -1, 0, 1)
|
|
.attr(HealAttr, 0.5)
|
|
.triageMove(),
|
|
new AttackMove(Moves.HIGH_JUMP_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 130, 90, 10, -1, 0, 1)
|
|
.attr(MissEffectAttr, crashDamageFunc)
|
|
.attr(NoEffectAttr, crashDamageFunc)
|
|
.condition(failOnGravityCondition)
|
|
.recklessMove(),
|
|
new StatusMove(Moves.GLARE, Type.NORMAL, 100, 30, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.DREAM_EATER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 15, -1, 0, 1)
|
|
.attr(HitHealAttr)
|
|
.condition(targetSleptOrComatoseCondition)
|
|
.triageMove(),
|
|
new StatusMove(Moves.POISON_GAS, Type.POISON, 90, 40, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.BARRAGE, Type.NORMAL, MoveCategory.PHYSICAL, 15, 85, 20, -1, 0, 1)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.LEECH_LIFE, Type.BUG, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 1)
|
|
.attr(HitHealAttr)
|
|
.triageMove(),
|
|
new StatusMove(Moves.LOVELY_KISS, Type.NORMAL, 75, 10, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP),
|
|
new AttackMove(Moves.SKY_ATTACK, Type.FLYING, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 1)
|
|
.attr(ChargeAttr, ChargeAnim.SKY_ATTACK_CHARGING, i18next.t("moveTriggers:isGlowing", {pokemonName: "{USER}"}))
|
|
.attr(HighCritAttr)
|
|
.attr(FlinchAttr)
|
|
.makesContact(false)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.TRANSFORM, Type.NORMAL, -1, 10, -1, 0, 1)
|
|
.attr(TransformAttr)
|
|
.ignoresProtect(),
|
|
new AttackMove(Moves.BUBBLE, Type.WATER, MoveCategory.SPECIAL, 40, 100, 30, 10, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.DIZZY_PUNCH, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, 20, 0, 1)
|
|
.attr(ConfuseAttr)
|
|
.punchingMove(),
|
|
new StatusMove(Moves.SPORE, Type.GRASS, 100, 15, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
|
.powderMove(),
|
|
new StatusMove(Moves.FLASH, Type.NORMAL, 100, 20, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new AttackMove(Moves.PSYWAVE, Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 15, -1, 0, 1)
|
|
.attr(RandomLevelDamageAttr),
|
|
new SelfStatusMove(Moves.SPLASH, Type.NORMAL, -1, 40, -1, 0, 1)
|
|
.condition(failOnGravityCondition),
|
|
new SelfStatusMove(Moves.ACID_ARMOR, Type.POISON, -1, 20, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 2, true),
|
|
new AttackMove(Moves.CRABHAMMER, Type.WATER, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 1)
|
|
.attr(HighCritAttr),
|
|
new AttackMove(Moves.EXPLOSION, Type.NORMAL, MoveCategory.PHYSICAL, 250, 100, 5, -1, 0, 1)
|
|
.condition(failIfDampCondition)
|
|
.attr(SacrificialAttr)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.FURY_SWIPES, Type.NORMAL, MoveCategory.PHYSICAL, 18, 80, 15, -1, 0, 1)
|
|
.attr(MultiHitAttr),
|
|
new AttackMove(Moves.BONEMERANG, Type.GROUND, MoveCategory.PHYSICAL, 50, 90, 10, -1, 0, 1)
|
|
.attr(MultiHitAttr, MultiHitType._2)
|
|
.makesContact(false),
|
|
new SelfStatusMove(Moves.REST, Type.PSYCHIC, -1, 5, -1, 0, 1)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP, true, 3, true)
|
|
.attr(HealAttr, 1, true)
|
|
.condition((user, target, move) => !user.isFullHp() && user.canSetStatus(StatusEffect.SLEEP, true, true))
|
|
.triageMove(),
|
|
new AttackMove(Moves.ROCK_SLIDE, Type.ROCK, MoveCategory.PHYSICAL, 75, 90, 10, 30, 0, 1)
|
|
.attr(FlinchAttr)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.HYPER_FANG, Type.NORMAL, MoveCategory.PHYSICAL, 80, 90, 15, 10, 0, 1)
|
|
.attr(FlinchAttr)
|
|
.bitingMove(),
|
|
new SelfStatusMove(Moves.SHARPEN, Type.NORMAL, -1, 30, -1, 0, 1)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 1, true),
|
|
new SelfStatusMove(Moves.CONVERSION, Type.NORMAL, -1, 30, -1, 0, 1)
|
|
.attr(FirstMoveTypeAttr),
|
|
new AttackMove(Moves.TRI_ATTACK, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, 20, 0, 1)
|
|
.attr(MultiStatusEffectAttr, [StatusEffect.BURN, StatusEffect.FREEZE, StatusEffect.PARALYSIS]),
|
|
new AttackMove(Moves.SUPER_FANG, Type.NORMAL, MoveCategory.PHYSICAL, -1, 90, 10, -1, 0, 1)
|
|
.attr(TargetHalfHpDamageAttr),
|
|
new AttackMove(Moves.SLASH, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 1)
|
|
.attr(HighCritAttr)
|
|
.slicingMove(),
|
|
new SelfStatusMove(Moves.SUBSTITUTE, Type.NORMAL, -1, 10, -1, 0, 1)
|
|
.attr(RecoilAttr)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.STRUGGLE, Type.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1)
|
|
.attr(RecoilAttr, true, 0.25, true)
|
|
.attr(TypelessAttr)
|
|
.ignoresVirtual()
|
|
.target(MoveTarget.RANDOM_NEAR_ENEMY),
|
|
new StatusMove(Moves.SKETCH, Type.NORMAL, -1, 1, -1, 0, 2)
|
|
.attr(SketchAttr)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.TRIPLE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 10, 90, 10, -1, 0, 2)
|
|
.attr(MultiHitAttr, MultiHitType._3)
|
|
.attr(MultiHitPowerIncrementAttr, 3)
|
|
.checkAllHits(),
|
|
new AttackMove(Moves.THIEF, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 2)
|
|
.attr(StealHeldItemChanceAttr, 0.3),
|
|
new StatusMove(Moves.SPIDER_WEB, Type.BUG, -1, 10, -1, 0, 2)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
|
new StatusMove(Moves.MIND_READER, Type.NORMAL, -1, 5, -1, 0, 2)
|
|
.attr(IgnoreAccuracyAttr),
|
|
new StatusMove(Moves.NIGHTMARE, Type.GHOST, 100, 15, -1, 0, 2)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.NIGHTMARE)
|
|
.condition(targetSleptOrComatoseCondition),
|
|
new AttackMove(Moves.FLAME_WHEEL, Type.FIRE, MoveCategory.PHYSICAL, 60, 100, 25, 10, 0, 2)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new AttackMove(Moves.SNORE, Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 15, 30, 0, 2)
|
|
.attr(BypassSleepAttr)
|
|
.attr(FlinchAttr)
|
|
.condition(userSleptOrComatoseCondition)
|
|
.soundBased(),
|
|
new StatusMove(Moves.CURSE, Type.GHOST, -1, 10, -1, 0, 2)
|
|
.attr(CurseAttr)
|
|
.ignoresProtect(true)
|
|
.target(MoveTarget.CURSE),
|
|
new AttackMove(Moves.FLAIL, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2)
|
|
.attr(LowHpPowerAttr),
|
|
new StatusMove(Moves.CONVERSION_2, Type.NORMAL, -1, 30, -1, 0, 2)
|
|
.attr(ResistLastMoveTypeAttr)
|
|
.partial(), // Checks the move's original typing and not if its type is changed through some other means
|
|
new AttackMove(Moves.AEROBLAST, Type.FLYING, MoveCategory.SPECIAL, 100, 95, 5, -1, 0, 2)
|
|
.windMove()
|
|
.attr(HighCritAttr),
|
|
new StatusMove(Moves.COTTON_SPORE, Type.GRASS, 100, 40, -1, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -2)
|
|
.powderMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.REVERSAL, Type.FIGHTING, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 2)
|
|
.attr(LowHpPowerAttr),
|
|
new StatusMove(Moves.SPITE, Type.GHOST, 100, 10, -1, 0, 2)
|
|
.attr(ReducePpMoveAttr, 4),
|
|
new AttackMove(Moves.POWDER_SNOW, Type.ICE, MoveCategory.SPECIAL, 40, 100, 25, 10, 0, 2)
|
|
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new SelfStatusMove(Moves.PROTECT, Type.NORMAL, -1, 10, -1, 4, 2)
|
|
.attr(ProtectAttr),
|
|
new AttackMove(Moves.MACH_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 2)
|
|
.punchingMove(),
|
|
new StatusMove(Moves.SCARY_FACE, Type.NORMAL, 100, 10, -1, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -2),
|
|
new AttackMove(Moves.FEINT_ATTACK, Type.DARK, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 2),
|
|
new StatusMove(Moves.SWEET_KISS, Type.FAIRY, 75, 10, -1, 0, 2)
|
|
.attr(ConfuseAttr),
|
|
new SelfStatusMove(Moves.BELLY_DRUM, Type.NORMAL, -1, 10, -1, 0, 2)
|
|
.attr(HalfHpStatMaxAttr, BattleStat.ATK),
|
|
new AttackMove(Moves.SLUDGE_BOMB, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 30, 0, 2)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.MUD_SLAP, Type.GROUND, MoveCategory.SPECIAL, 20, 100, 10, 100, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new AttackMove(Moves.OCTAZOOKA, Type.WATER, MoveCategory.SPECIAL, 65, 85, 10, 50, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1)
|
|
.ballBombMove(),
|
|
new StatusMove(Moves.SPIKES, Type.GROUND, -1, 20, -1, 0, 2)
|
|
.attr(AddArenaTrapTagAttr, ArenaTagType.SPIKES)
|
|
.target(MoveTarget.ENEMY_SIDE),
|
|
new AttackMove(Moves.ZAP_CANNON, Type.ELECTRIC, MoveCategory.SPECIAL, 120, 50, 5, 100, 0, 2)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.ballBombMove(),
|
|
new StatusMove(Moves.FORESIGHT, Type.NORMAL, -1, 40, -1, 0, 2)
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.DESTINY_BOND, Type.GHOST, -1, 5, -1, 0, 2)
|
|
.ignoresProtect()
|
|
.attr(DestinyBondAttr),
|
|
new StatusMove(Moves.PERISH_SONG, Type.NORMAL, -1, 5, -1, 0, 2)
|
|
.attr(FaintCountdownAttr)
|
|
.ignoresProtect()
|
|
.soundBased()
|
|
.condition(failOnBossCondition)
|
|
.target(MoveTarget.ALL),
|
|
new AttackMove(Moves.ICY_WIND, Type.ICE, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new SelfStatusMove(Moves.DETECT, Type.FIGHTING, -1, 5, -1, 4, 2)
|
|
.attr(ProtectAttr),
|
|
new AttackMove(Moves.BONE_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 2)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false),
|
|
new StatusMove(Moves.LOCK_ON, Type.NORMAL, -1, 5, -1, 0, 2)
|
|
.attr(IgnoreAccuracyAttr),
|
|
new AttackMove(Moves.OUTRAGE, Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 2)
|
|
.attr(FrenzyAttr)
|
|
.attr(MissEffectAttr, frenzyMissFunc)
|
|
.attr(NoEffectAttr, frenzyMissFunc)
|
|
.target(MoveTarget.RANDOM_NEAR_ENEMY),
|
|
new StatusMove(Moves.SANDSTORM, Type.ROCK, -1, 10, -1, 0, 2)
|
|
.attr(WeatherChangeAttr, WeatherType.SANDSTORM)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new AttackMove(Moves.GIGA_DRAIN, Type.GRASS, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 2)
|
|
.attr(HitHealAttr)
|
|
.triageMove(),
|
|
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
|
|
.attr(EndureAttr),
|
|
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -2),
|
|
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
|
|
.attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL),
|
|
new AttackMove(Moves.FALSE_SWIPE, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 2)
|
|
.attr(SurviveDamageAttr),
|
|
new StatusMove(Moves.SWAGGER, Type.NORMAL, 85, 15, -1, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 2)
|
|
.attr(ConfuseAttr),
|
|
new SelfStatusMove(Moves.MILK_DRINK, Type.NORMAL, -1, 5, -1, 0, 2)
|
|
.attr(HealAttr, 0.5)
|
|
.triageMove(),
|
|
new AttackMove(Moves.SPARK, Type.ELECTRIC, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 2)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.FURY_CUTTER, Type.BUG, MoveCategory.PHYSICAL, 40, 95, 20, -1, 0, 2)
|
|
.attr(ConsecutiveUseDoublePowerAttr, 3, true)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.STEEL_WING, Type.STEEL, MoveCategory.PHYSICAL, 70, 90, 25, 10, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 1, true),
|
|
new StatusMove(Moves.MEAN_LOOK, Type.NORMAL, -1, 5, -1, 0, 2)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
|
new StatusMove(Moves.ATTRACT, Type.NORMAL, 100, 15, -1, 0, 2)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.INFATUATED)
|
|
.condition((user, target, move) => user.isOppositeGender(target)),
|
|
new SelfStatusMove(Moves.SLEEP_TALK, Type.NORMAL, -1, 10, -1, 0, 2)
|
|
.attr(BypassSleepAttr)
|
|
.attr(RandomMovesetMoveAttr)
|
|
.condition(userSleptOrComatoseCondition)
|
|
.target(MoveTarget.ALL_ENEMIES)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.HEAL_BELL, Type.NORMAL, -1, 5, -1, 0, 2)
|
|
.attr(PartyStatusCureAttr, i18next.t("moveTriggers:bellChimed"), Abilities.SOUNDPROOF)
|
|
.soundBased()
|
|
.target(MoveTarget.PARTY),
|
|
new AttackMove(Moves.RETURN, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 2)
|
|
.attr(FriendshipPowerAttr),
|
|
new AttackMove(Moves.PRESENT, Type.NORMAL, MoveCategory.PHYSICAL, -1, 90, 15, -1, 0, 2)
|
|
.attr(PresentPowerAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.FRUSTRATION, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, 0, 2)
|
|
.attr(FriendshipPowerAttr, true),
|
|
new StatusMove(Moves.SAFEGUARD, Type.NORMAL, -1, 25, -1, 0, 2)
|
|
.target(MoveTarget.USER_SIDE)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.PAIN_SPLIT, Type.NORMAL, -1, 20, -1, 0, 2)
|
|
.attr(HpSplitAttr)
|
|
.condition(failOnBossCondition),
|
|
new AttackMove(Moves.SACRED_FIRE, Type.FIRE, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 2)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.MAGNITUDE, Type.GROUND, MoveCategory.PHYSICAL, -1, 100, 30, -1, 0, 2)
|
|
.attr(PreMoveMessageAttr, magnitudeMessageFunc)
|
|
.attr(MagnitudePowerAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.UNDERGROUND, true)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.DYNAMIC_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 50, 5, 100, 0, 2)
|
|
.attr(ConfuseAttr)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.MEGAHORN, Type.BUG, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 2),
|
|
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)
|
|
.hidesUser(),
|
|
new StatusMove(Moves.ENCORE, Type.NORMAL, 100, 5, -1, 0, 2)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, true)
|
|
.condition((user, target, move) => new EncoreTag(user.id).canAdd(target)),
|
|
new AttackMove(Moves.PURSUIT, Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 0, 2)
|
|
.partial(),
|
|
new AttackMove(Moves.RAPID_SPIN, Type.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, 100, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 1, true)
|
|
.attr(RemoveBattlerTagAttr, [
|
|
BattlerTagType.BIND,
|
|
BattlerTagType.WRAP,
|
|
BattlerTagType.FIRE_SPIN,
|
|
BattlerTagType.WHIRLPOOL,
|
|
BattlerTagType.CLAMP,
|
|
BattlerTagType.SAND_TOMB,
|
|
BattlerTagType.MAGMA_STORM,
|
|
BattlerTagType.SNAP_TRAP,
|
|
BattlerTagType.THUNDER_CAGE,
|
|
BattlerTagType.SEEDED,
|
|
BattlerTagType.INFESTATION
|
|
], true)
|
|
.attr(RemoveArenaTrapAttr),
|
|
new StatusMove(Moves.SWEET_SCENT, Type.NORMAL, 100, 20, -1, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.EVA, -2)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.IRON_TAIL, Type.STEEL, MoveCategory.PHYSICAL, 100, 75, 15, 30, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1),
|
|
new AttackMove(Moves.METAL_CLAW, Type.STEEL, MoveCategory.PHYSICAL, 50, 95, 35, 10, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 1, true),
|
|
new AttackMove(Moves.VITAL_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 70, -1, 10, -1, -1, 2),
|
|
new SelfStatusMove(Moves.MORNING_SUN, Type.NORMAL, -1, 5, -1, 0, 2)
|
|
.attr(PlantHealAttr)
|
|
.triageMove(),
|
|
new SelfStatusMove(Moves.SYNTHESIS, Type.GRASS, -1, 5, -1, 0, 2)
|
|
.attr(PlantHealAttr)
|
|
.triageMove(),
|
|
new SelfStatusMove(Moves.MOONLIGHT, Type.FAIRY, -1, 5, -1, 0, 2)
|
|
.attr(PlantHealAttr)
|
|
.triageMove(),
|
|
new AttackMove(Moves.HIDDEN_POWER, Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 2)
|
|
.attr(HiddenPowerTypeAttr),
|
|
new AttackMove(Moves.CROSS_CHOP, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 2)
|
|
.attr(HighCritAttr),
|
|
new AttackMove(Moves.TWISTER, Type.DRAGON, MoveCategory.SPECIAL, 40, 100, 20, 20, 0, 2)
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING, true)
|
|
.attr(FlinchAttr)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.RAIN_DANCE, Type.WATER, -1, 5, -1, 0, 2)
|
|
.attr(WeatherChangeAttr, WeatherType.RAIN)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new StatusMove(Moves.SUNNY_DAY, Type.FIRE, -1, 5, -1, 0, 2)
|
|
.attr(WeatherChangeAttr, WeatherType.SUNNY)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new AttackMove(Moves.CRUNCH, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 20, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
|
.bitingMove(),
|
|
new AttackMove(Moves.MIRROR_COAT, Type.PSYCHIC, MoveCategory.SPECIAL, -1, 100, 20, -1, -5, 2)
|
|
.attr(CounterDamageAttr, (move: Move) => move.category === MoveCategory.SPECIAL, 2)
|
|
.target(MoveTarget.ATTACKER),
|
|
new StatusMove(Moves.PSYCH_UP, Type.NORMAL, -1, 10, -1, 0, 2)
|
|
.attr(CopyStatsAttr),
|
|
new AttackMove(Moves.EXTREME_SPEED, Type.NORMAL, MoveCategory.PHYSICAL, 80, 100, 5, -1, 2, 2),
|
|
new AttackMove(Moves.ANCIENT_POWER, Type.ROCK, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 2)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true),
|
|
new AttackMove(Moves.SHADOW_BALL, Type.GHOST, MoveCategory.SPECIAL, 80, 100, 15, 20, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.FUTURE_SIGHT, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 2)
|
|
.attr(DelayedAttackAttr, ArenaTagType.FUTURE_SIGHT, ChargeAnim.FUTURE_SIGHT_CHARGING, i18next.t("moveTriggers:foresawAnAttack", {pokemonName: "{USER}"})),
|
|
new AttackMove(Moves.ROCK_SMASH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 15, 50, 0, 2)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1),
|
|
new AttackMove(Moves.WHIRLPOOL, Type.WATER, MoveCategory.SPECIAL, 35, 85, 15, -1, 0, 2)
|
|
.attr(TrapAttr, BattlerTagType.WHIRLPOOL)
|
|
.attr(HitsTagAttr, BattlerTagType.UNDERWATER, true),
|
|
new AttackMove(Moves.BEAT_UP, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 2)
|
|
.attr(MultiHitAttr, MultiHitType.BEAT_UP)
|
|
.attr(BeatUpAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.FAKE_OUT, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 10, 100, 3, 3)
|
|
.attr(FlinchAttr)
|
|
.condition(new FirstMoveCondition()),
|
|
new AttackMove(Moves.UPROAR, Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 3)
|
|
.ignoresVirtual()
|
|
.soundBased()
|
|
.target(MoveTarget.RANDOM_NEAR_ENEMY)
|
|
.partial(),
|
|
new SelfStatusMove(Moves.STOCKPILE, Type.NORMAL, -1, 20, -1, 0, 3)
|
|
.condition(user => (user.getTag(StockpilingTag)?.stockpiledCount ?? 0) < 3)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.STOCKPILING, true),
|
|
new AttackMove(Moves.SPIT_UP, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 10, -1, 0, 3)
|
|
.condition(hasStockpileStacksCondition)
|
|
.attr(SpitUpPowerAttr, 100)
|
|
.attr(RemoveBattlerTagAttr, [BattlerTagType.STOCKPILING], true),
|
|
new SelfStatusMove(Moves.SWALLOW, Type.NORMAL, -1, 10, -1, 0, 3)
|
|
.condition(hasStockpileStacksCondition)
|
|
.attr(SwallowHealAttr)
|
|
.attr(RemoveBattlerTagAttr, [BattlerTagType.STOCKPILING], true)
|
|
.triageMove(),
|
|
new AttackMove(Moves.HEAT_WAVE, Type.FIRE, MoveCategory.SPECIAL, 95, 90, 10, 10, 0, 3)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.HAIL, Type.ICE, -1, 10, -1, 0, 3)
|
|
.attr(WeatherChangeAttr, WeatherType.HAIL)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new StatusMove(Moves.TORMENT, Type.DARK, 100, 15, -1, 0, 3)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.FLATTER, Type.DARK, 100, 15, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 1)
|
|
.attr(ConfuseAttr),
|
|
new StatusMove(Moves.WILL_O_WISP, Type.FIRE, 85, 15, -1, 0, 3)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new StatusMove(Moves.MEMENTO, Type.DARK, 100, 10, -1, 0, 3)
|
|
.attr(SacrificialAttrOnHit)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -2),
|
|
new AttackMove(Moves.FACADE, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 3)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.status
|
|
&& (user.status.effect === StatusEffect.BURN || user.status.effect === StatusEffect.POISON || user.status.effect === StatusEffect.TOXIC || user.status.effect === StatusEffect.PARALYSIS) ? 2 : 1)
|
|
.attr(BypassBurnDamageReductionAttr),
|
|
new AttackMove(Moves.FOCUS_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 20, -1, -3, 3)
|
|
.punchingMove()
|
|
.ignoresVirtual()
|
|
.condition((user, target, move) => !user.turnData.attacksReceived.find(r => r.damage)),
|
|
new AttackMove(Moves.SMELLING_SALTS, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 3)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status?.effect === StatusEffect.PARALYSIS ? 2 : 1)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS),
|
|
new SelfStatusMove(Moves.FOLLOW_ME, Type.NORMAL, -1, 20, -1, 2, 3)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
|
|
new StatusMove(Moves.NATURE_POWER, Type.NORMAL, -1, 20, -1, 0, 3)
|
|
.attr(NaturePowerAttr)
|
|
.ignoresVirtual(),
|
|
new SelfStatusMove(Moves.CHARGE, Type.ELECTRIC, -1, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, 1, true)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.CHARGED, true, false),
|
|
new StatusMove(Moves.TAUNT, Type.DARK, 100, 20, -1, 0, 3)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.HELPING_HAND, Type.NORMAL, -1, 20, -1, 5, 3)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.HELPING_HAND)
|
|
.target(MoveTarget.NEAR_ALLY),
|
|
new StatusMove(Moves.TRICK, Type.PSYCHIC, 100, 10, -1, 0, 3)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.ROLE_PLAY, Type.PSYCHIC, -1, 10, -1, 0, 3)
|
|
.attr(AbilityCopyAttr),
|
|
new SelfStatusMove(Moves.WISH, Type.NORMAL, -1, 10, -1, 0, 3)
|
|
.triageMove()
|
|
.attr(AddArenaTagAttr, ArenaTagType.WISH, 2, true),
|
|
new SelfStatusMove(Moves.ASSIST, Type.NORMAL, -1, 20, -1, 0, 3)
|
|
.attr(RandomMovesetMoveAttr, true)
|
|
.ignoresVirtual(),
|
|
new SelfStatusMove(Moves.INGRAIN, Type.GRASS, -1, 20, -1, 0, 3)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.INGRAIN, true, true),
|
|
new AttackMove(Moves.SUPERPOWER, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 3)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1, true),
|
|
new SelfStatusMove(Moves.MAGIC_COAT, Type.PSYCHIC, -1, 15, -1, 4, 3)
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.RECYCLE, Type.NORMAL, -1, 10, -1, 0, 3)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.REVENGE, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, -4, 3)
|
|
.attr(TurnDamagedDoublePowerAttr),
|
|
new AttackMove(Moves.BRICK_BREAK, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 15, -1, 0, 3)
|
|
.attr(RemoveScreensAttr),
|
|
new StatusMove(Moves.YAWN, Type.NORMAL, -1, 10, -1, 0, 3)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.DROWSY, false, true)
|
|
.condition((user, target, move) => !target.status),
|
|
new AttackMove(Moves.KNOCK_OFF, Type.DARK, MoveCategory.PHYSICAL, 65, 100, 20, -1, 0, 3)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHeldItems().filter(i => i.isTransferrable).length > 0 ? 1.5 : 1)
|
|
.attr(RemoveHeldItemAttr, false),
|
|
new AttackMove(Moves.ENDEAVOR, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 3)
|
|
.attr(MatchHpAttr)
|
|
.condition(failOnBossCondition),
|
|
new AttackMove(Moves.ERUPTION, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 3)
|
|
.attr(HpPowerAttr)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.SKILL_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 3)
|
|
.attr(SwitchAbilitiesAttr),
|
|
new SelfStatusMove(Moves.IMPRISON, Type.PSYCHIC, -1, 10, -1, 0, 3)
|
|
.unimplemented(),
|
|
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)),
|
|
new SelfStatusMove(Moves.GRUDGE, Type.GHOST, -1, 5, -1, 0, 3)
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.SNATCH, Type.DARK, -1, 10, -1, 4, 3)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.SECRET_POWER, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, 30, 0, 3)
|
|
.makesContact(false)
|
|
.partial(),
|
|
new AttackMove(Moves.DIVE, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 3)
|
|
.attr(ChargeAttr, ChargeAnim.DIVE_CHARGING, i18next.t("moveTriggers:hidUnderwater", {pokemonName: "{USER}"}), BattlerTagType.UNDERWATER)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.ARM_THRUST, Type.FIGHTING, MoveCategory.PHYSICAL, 15, 100, 20, -1, 0, 3)
|
|
.attr(MultiHitAttr),
|
|
new SelfStatusMove(Moves.CAMOUFLAGE, Type.NORMAL, -1, 20, -1, 0, 3)
|
|
.attr(CopyBiomeTypeAttr),
|
|
new SelfStatusMove(Moves.TAIL_GLOW, Type.BUG, -1, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 3, true),
|
|
new AttackMove(Moves.LUSTER_PURGE, Type.PSYCHIC, MoveCategory.SPECIAL, 95, 100, 5, 50, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1),
|
|
new AttackMove(Moves.MIST_BALL, Type.PSYCHIC, MoveCategory.SPECIAL, 95, 100, 5, 50, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1)
|
|
.ballBombMove(),
|
|
new StatusMove(Moves.FEATHER_DANCE, Type.FLYING, 100, 15, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -2)
|
|
.danceMove(),
|
|
new StatusMove(Moves.TEETER_DANCE, Type.NORMAL, 100, 20, -1, 0, 3)
|
|
.attr(ConfuseAttr)
|
|
.danceMove()
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.BLAZE_KICK, Type.FIRE, MoveCategory.PHYSICAL, 85, 90, 10, 10, 0, 3)
|
|
.attr(HighCritAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new StatusMove(Moves.MUD_SPORT, Type.GROUND, -1, 15, -1, 0, 3)
|
|
.attr(AddArenaTagAttr, ArenaTagType.MUD_SPORT, 5)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new AttackMove(Moves.ICE_BALL, Type.ICE, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 3)
|
|
.attr(ConsecutiveUseDoublePowerAttr, 5, true, true, Moves.DEFENSE_CURL)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.NEEDLE_ARM, Type.GRASS, MoveCategory.PHYSICAL, 60, 100, 15, 30, 0, 3)
|
|
.attr(FlinchAttr),
|
|
new SelfStatusMove(Moves.SLACK_OFF, Type.NORMAL, -1, 5, -1, 0, 3)
|
|
.attr(HealAttr, 0.5)
|
|
.triageMove(),
|
|
new AttackMove(Moves.HYPER_VOICE, Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, 0, 3)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.POISON_FANG, Type.POISON, MoveCategory.PHYSICAL, 50, 100, 15, 50, 0, 3)
|
|
.attr(StatusEffectAttr, StatusEffect.TOXIC)
|
|
.bitingMove(),
|
|
new AttackMove(Moves.CRUSH_CLAW, Type.NORMAL, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1),
|
|
new AttackMove(Moves.BLAST_BURN, Type.FIRE, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3)
|
|
.attr(RechargeAttr),
|
|
new AttackMove(Moves.HYDRO_CANNON, Type.WATER, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3)
|
|
.attr(RechargeAttr),
|
|
new AttackMove(Moves.METEOR_MASH, Type.STEEL, MoveCategory.PHYSICAL, 90, 90, 10, 20, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 1, true)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.ASTONISH, Type.GHOST, MoveCategory.PHYSICAL, 30, 100, 15, 30, 0, 3)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.WEATHER_BALL, Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 3)
|
|
.attr(WeatherBallTypeAttr)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => [WeatherType.SUNNY, WeatherType.RAIN, WeatherType.SANDSTORM, WeatherType.HAIL, WeatherType.SNOW, WeatherType.FOG, WeatherType.HEAVY_RAIN, WeatherType.HARSH_SUN].includes(user.scene.arena.weather?.weatherType) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 2 : 1)
|
|
.ballBombMove(),
|
|
new StatusMove(Moves.AROMATHERAPY, Type.GRASS, -1, 5, -1, 0, 3)
|
|
.attr(PartyStatusCureAttr, i18next.t("moveTriggers:soothingAromaWaftedThroughArea"), Abilities.SAP_SIPPER)
|
|
.target(MoveTarget.PARTY),
|
|
new StatusMove(Moves.FAKE_TEARS, Type.DARK, 100, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -2),
|
|
new AttackMove(Moves.AIR_CUTTER, Type.FLYING, MoveCategory.SPECIAL, 60, 95, 25, -1, 0, 3)
|
|
.attr(HighCritAttr)
|
|
.slicingMove()
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.OVERHEAT, Type.FIRE, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -2, true)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE),
|
|
new StatusMove(Moves.ODOR_SLEUTH, Type.NORMAL, -1, 40, -1, 0, 3)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.ROCK_TOMB, Type.ROCK, MoveCategory.PHYSICAL, 60, 95, 15, 100, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.SILVER_WIND, Type.BUG, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 3)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
|
.windMove(),
|
|
new StatusMove(Moves.METAL_SOUND, Type.STEEL, 85, 40, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -2)
|
|
.soundBased(),
|
|
new StatusMove(Moves.GRASS_WHISTLE, Type.GRASS, 55, 15, -1, 0, 3)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
|
.soundBased(),
|
|
new StatusMove(Moves.TICKLE, Type.NORMAL, 100, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], -1),
|
|
new SelfStatusMove(Moves.COSMIC_POWER, Type.PSYCHIC, -1, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, true),
|
|
new AttackMove(Moves.WATER_SPOUT, Type.WATER, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 3)
|
|
.attr(HpPowerAttr)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.SIGNAL_BEAM, Type.BUG, MoveCategory.SPECIAL, 75, 100, 15, 10, 0, 3)
|
|
.attr(ConfuseAttr),
|
|
new AttackMove(Moves.SHADOW_PUNCH, Type.GHOST, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 3)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.EXTRASENSORY, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 20, 10, 0, 3)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.SKY_UPPERCUT, Type.FIGHTING, MoveCategory.PHYSICAL, 85, 90, 15, -1, 0, 3)
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.SAND_TOMB, Type.GROUND, MoveCategory.PHYSICAL, 35, 85, 15, -1, 0, 3)
|
|
.attr(TrapAttr, BattlerTagType.SAND_TOMB)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.SHEER_COLD, Type.ICE, MoveCategory.SPECIAL, 200, 20, 5, -1, 0, 3)
|
|
.attr(IceNoEffectTypeAttr)
|
|
.attr(OneHitKOAttr)
|
|
.attr(SheerColdAccuracyAttr),
|
|
new AttackMove(Moves.MUDDY_WATER, Type.WATER, MoveCategory.SPECIAL, 90, 85, 10, 30, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.BULLET_SEED, Type.GRASS, MoveCategory.PHYSICAL, 25, 100, 30, -1, 0, 3)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.AERIAL_ACE, Type.FLYING, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 3)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.ICICLE_SPEAR, Type.ICE, MoveCategory.PHYSICAL, 25, 100, 30, -1, 0, 3)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false),
|
|
new SelfStatusMove(Moves.IRON_DEFENSE, Type.STEEL, -1, 15, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 2, true),
|
|
new StatusMove(Moves.BLOCK, Type.NORMAL, -1, 5, -1, 0, 3)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, true, 1),
|
|
new StatusMove(Moves.HOWL, Type.NORMAL, -1, 40, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 1)
|
|
.soundBased()
|
|
.target(MoveTarget.USER_AND_ALLIES),
|
|
new AttackMove(Moves.DRAGON_CLAW, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 3),
|
|
new AttackMove(Moves.FRENZY_PLANT, Type.GRASS, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 3)
|
|
.attr(RechargeAttr),
|
|
new SelfStatusMove(Moves.BULK_UP, Type.FIGHTING, -1, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF ], 1, true),
|
|
new AttackMove(Moves.BOUNCE, Type.FLYING, MoveCategory.PHYSICAL, 85, 85, 5, 30, 0, 3)
|
|
.attr(ChargeAttr, ChargeAnim.BOUNCE_CHARGING, i18next.t("moveTriggers:sprangUp", {pokemonName: "{USER}"}), BattlerTagType.FLYING)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.condition(failOnGravityCondition)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MUD_SHOT, Type.GROUND, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
|
new AttackMove(Moves.POISON_TAIL, Type.POISON, MoveCategory.PHYSICAL, 50, 100, 25, 10, 0, 3)
|
|
.attr(HighCritAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON),
|
|
new AttackMove(Moves.COVET, Type.NORMAL, MoveCategory.PHYSICAL, 60, 100, 25, -1, 0, 3)
|
|
.attr(StealHeldItemChanceAttr, 0.3),
|
|
new AttackMove(Moves.VOLT_TACKLE, Type.ELECTRIC, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 3)
|
|
.attr(RecoilAttr, false, 0.33)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.MAGICAL_LEAF, Type.GRASS, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 3),
|
|
new StatusMove(Moves.WATER_SPORT, Type.WATER, -1, 15, -1, 0, 3)
|
|
.attr(AddArenaTagAttr, ArenaTagType.WATER_SPORT, 5)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new SelfStatusMove(Moves.CALM_MIND, Type.PSYCHIC, -1, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF ], 1, true),
|
|
new AttackMove(Moves.LEAF_BLADE, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 3)
|
|
.attr(HighCritAttr)
|
|
.slicingMove(),
|
|
new SelfStatusMove(Moves.DRAGON_DANCE, Type.DRAGON, -1, 20, -1, 0, 3)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPD ], 1, true)
|
|
.danceMove(),
|
|
new AttackMove(Moves.ROCK_BLAST, Type.ROCK, MoveCategory.PHYSICAL, 25, 90, 10, -1, 0, 3)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.SHOCK_WAVE, Type.ELECTRIC, MoveCategory.SPECIAL, 60, -1, 20, -1, 0, 3),
|
|
new AttackMove(Moves.WATER_PULSE, Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, 20, 0, 3)
|
|
.attr(ConfuseAttr)
|
|
.pulseMove(),
|
|
new AttackMove(Moves.DOOM_DESIRE, Type.STEEL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 3)
|
|
.attr(DelayedAttackAttr, ArenaTagType.DOOM_DESIRE, ChargeAnim.DOOM_DESIRE_CHARGING, i18next.t("moveTriggers:choseDoomDesireAsDestiny", {pokemonName: "{USER}"})),
|
|
new AttackMove(Moves.PSYCHO_BOOST, Type.PSYCHIC, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 3)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
|
new SelfStatusMove(Moves.ROOST, Type.FLYING, -1, 5, -1, 0, 4)
|
|
.attr(HealAttr, 0.5)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.ROOSTED, true, false)
|
|
.triageMove(),
|
|
new StatusMove(Moves.GRAVITY, Type.PSYCHIC, -1, 5, -1, 0, 4)
|
|
.attr(AddArenaTagAttr, ArenaTagType.GRAVITY, 5)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new StatusMove(Moves.MIRACLE_EYE, Type.PSYCHIC, -1, 40, -1, 0, 4)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.WAKE_UP_SLAP, Type.FIGHTING, MoveCategory.PHYSICAL, 70, 100, 10, -1, 0, 4)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => targetSleptOrComatoseCondition(user, target, move) ? 2 : 1)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.SLEEP),
|
|
new AttackMove(Moves.HAMMER_ARM, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1, true)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.GYRO_BALL, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
|
|
.attr(GyroBallPowerAttr)
|
|
.ballBombMove(),
|
|
new SelfStatusMove(Moves.HEALING_WISH, Type.PSYCHIC, -1, 10, -1, 0, 4)
|
|
.attr(SacrificialFullRestoreAttr)
|
|
.triageMove(),
|
|
new AttackMove(Moves.BRINE, Type.WATER, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 4)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getHpRatio() < 0.5 ? 2 : 1),
|
|
new AttackMove(Moves.NATURAL_GIFT, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, 0, 4)
|
|
.makesContact(false)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.FEINT, Type.NORMAL, MoveCategory.PHYSICAL, 30, 100, 10, -1, 2, 4)
|
|
.attr(RemoveBattlerTagAttr, [ BattlerTagType.PROTECTED ])
|
|
.attr(RemoveArenaTagsAttr, [ ArenaTagType.QUICK_GUARD, ArenaTagType.WIDE_GUARD, ArenaTagType.MAT_BLOCK, ArenaTagType.CRAFTY_SHIELD ], false)
|
|
.makesContact(false)
|
|
.ignoresProtect(),
|
|
new AttackMove(Moves.PLUCK, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4)
|
|
.attr(StealEatBerryAttr),
|
|
new StatusMove(Moves.TAILWIND, Type.FLYING, -1, 15, -1, 0, 4)
|
|
.windMove()
|
|
.attr(AddArenaTagAttr, ArenaTagType.TAILWIND, 4, true)
|
|
.target(MoveTarget.USER_SIDE),
|
|
new StatusMove(Moves.ACUPRESSURE, Type.NORMAL, -1, 30, -1, 0, 4)
|
|
.attr(AcupressureStatChangeAttr)
|
|
.target(MoveTarget.USER_OR_NEAR_ALLY),
|
|
new AttackMove(Moves.METAL_BURST, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 4)
|
|
.attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5)
|
|
.redirectCounter()
|
|
.makesContact(false)
|
|
.target(MoveTarget.ATTACKER),
|
|
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
|
|
.attr(ForceSwitchOutAttr, true, false),
|
|
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
|
|
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getLastXMoves(1).find(m => m.turn === target.scene.currentBattle.turn) || user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.BALL ? 2 : 1),
|
|
new AttackMove(Moves.ASSURANCE, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 4)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.turnData.damageTaken > 0 ? 2 : 1),
|
|
new StatusMove(Moves.EMBARGO, Type.DARK, 100, 15, -1, 0, 4)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.FLING, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 4)
|
|
.makesContact(false)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.PSYCHO_SHIFT, Type.PSYCHIC, 100, 10, -1, 0, 4)
|
|
.attr(PsychoShiftEffectAttr)
|
|
.condition((user, target, move) => (user.status?.effect === StatusEffect.BURN
|
|
|| user.status?.effect === StatusEffect.POISON
|
|
|| user.status?.effect === StatusEffect.TOXIC
|
|
|| user.status?.effect === StatusEffect.PARALYSIS
|
|
|| user.status?.effect === StatusEffect.SLEEP)
|
|
&& target.canSetStatus(user.status?.effect, false, false, user)
|
|
),
|
|
new AttackMove(Moves.TRUMP_CARD, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 5, -1, 0, 4)
|
|
.makesContact()
|
|
.attr(LessPPMorePowerAttr),
|
|
new StatusMove(Moves.HEAL_BLOCK, Type.PSYCHIC, 100, 15, -1, 0, 4)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.WRING_OUT, Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 4)
|
|
.attr(OpponentHighHpPowerAttr, 120)
|
|
.makesContact(),
|
|
new SelfStatusMove(Moves.POWER_TRICK, Type.PSYCHIC, -1, 10, -1, 0, 4)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4)
|
|
.attr(SuppressAbilitiesAttr),
|
|
new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.NO_CRIT, false, false, 5)
|
|
.target(MoveTarget.USER_SIDE)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.ME_FIRST, Type.NORMAL, -1, 20, -1, 0, 4)
|
|
.ignoresVirtual()
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.COPYCAT, Type.NORMAL, -1, 20, -1, 0, 4)
|
|
.attr(CopyMoveAttr)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.POWER_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.GUARD_SWAP, Type.PSYCHIC, -1, 10, 100, 0, 4)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.PUNISHMENT, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
|
|
.makesContact(true)
|
|
.attr(PunishmentPowerAttr),
|
|
new AttackMove(Moves.LAST_RESORT, Type.NORMAL, MoveCategory.PHYSICAL, 140, 100, 5, -1, 0, 4)
|
|
.attr(LastResortAttr),
|
|
new StatusMove(Moves.WORRY_SEED, Type.GRASS, 100, 10, -1, 0, 4)
|
|
.attr(AbilityChangeAttr, Abilities.INSOMNIA),
|
|
new AttackMove(Moves.SUCKER_PUNCH, Type.DARK, MoveCategory.PHYSICAL, 70, 100, 5, -1, 1, 4)
|
|
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS),
|
|
new StatusMove(Moves.TOXIC_SPIKES, Type.POISON, -1, 20, -1, 0, 4)
|
|
.attr(AddArenaTrapTagAttr, ArenaTagType.TOXIC_SPIKES)
|
|
.target(MoveTarget.ENEMY_SIDE),
|
|
new StatusMove(Moves.HEART_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 4)
|
|
.attr(SwapStatsAttr),
|
|
new SelfStatusMove(Moves.AQUA_RING, Type.WATER, -1, 20, -1, 0, 4)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.AQUA_RING, true, true),
|
|
new SelfStatusMove(Moves.MAGNET_RISE, Type.ELECTRIC, -1, 10, -1, 0, 4)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.MAGNET_RISEN, true, true)
|
|
.condition((user, target, move) => !user.scene.arena.getTag(ArenaTagType.GRAVITY) && [BattlerTagType.MAGNET_RISEN, BattlerTagType.IGNORE_FLYING, BattlerTagType.INGRAIN].every((tag) => !user.getTag(tag))),
|
|
new AttackMove(Moves.FLARE_BLITZ, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 15, 10, 0, 4)
|
|
.attr(RecoilAttr, false, 0.33)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.FORCE_PALM, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, 30, 0, 4)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.AURA_SPHERE, Type.FIGHTING, MoveCategory.SPECIAL, 80, -1, 20, -1, 0, 4)
|
|
.pulseMove()
|
|
.ballBombMove(),
|
|
new SelfStatusMove(Moves.ROCK_POLISH, Type.ROCK, -1, 20, -1, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 2, true),
|
|
new AttackMove(Moves.POISON_JAB, Type.POISON, MoveCategory.PHYSICAL, 80, 100, 20, 30, 0, 4)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON),
|
|
new AttackMove(Moves.DARK_PULSE, Type.DARK, MoveCategory.SPECIAL, 80, 100, 15, 20, 0, 4)
|
|
.attr(FlinchAttr)
|
|
.pulseMove(),
|
|
new AttackMove(Moves.NIGHT_SLASH, Type.DARK, MoveCategory.PHYSICAL, 70, 100, 15, -1, 0, 4)
|
|
.attr(HighCritAttr)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.AQUA_TAIL, Type.WATER, MoveCategory.PHYSICAL, 90, 90, 10, -1, 0, 4),
|
|
new AttackMove(Moves.SEED_BOMB, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 4)
|
|
.makesContact(false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.AIR_SLASH, Type.FLYING, MoveCategory.SPECIAL, 75, 95, 15, 30, 0, 4)
|
|
.attr(FlinchAttr)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.X_SCISSOR, Type.BUG, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 4)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.BUG_BUZZ, Type.BUG, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1)
|
|
.soundBased(),
|
|
new AttackMove(Moves.DRAGON_PULSE, Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 4)
|
|
.pulseMove(),
|
|
new AttackMove(Moves.DRAGON_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, 20, 0, 4)
|
|
.attr(MinimizeAccuracyAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.POWER_GEM, Type.ROCK, MoveCategory.SPECIAL, 80, 100, 20, -1, 0, 4),
|
|
new AttackMove(Moves.DRAIN_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 4)
|
|
.attr(HitHealAttr)
|
|
.punchingMove()
|
|
.triageMove(),
|
|
new AttackMove(Moves.VACUUM_WAVE, Type.FIGHTING, MoveCategory.SPECIAL, 40, 100, 30, -1, 1, 4),
|
|
new AttackMove(Moves.FOCUS_BLAST, Type.FIGHTING, MoveCategory.SPECIAL, 120, 70, 5, 10, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.ENERGY_BALL, Type.GRASS, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.BRAVE_BIRD, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 4)
|
|
.attr(RecoilAttr, false, 0.33)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.EARTH_POWER, Type.GROUND, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1),
|
|
new StatusMove(Moves.SWITCHEROO, Type.DARK, 100, 10, -1, 0, 4)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.GIGA_IMPACT, Type.NORMAL, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4)
|
|
.attr(RechargeAttr),
|
|
new SelfStatusMove(Moves.NASTY_PLOT, Type.DARK, -1, 20, -1, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 2, true),
|
|
new AttackMove(Moves.BULLET_PUNCH, Type.STEEL, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.AVALANCHE, Type.ICE, MoveCategory.PHYSICAL, 60, 100, 10, -1, -4, 4)
|
|
.attr(TurnDamagedDoublePowerAttr),
|
|
new AttackMove(Moves.ICE_SHARD, Type.ICE, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.SHADOW_CLAW, Type.GHOST, MoveCategory.PHYSICAL, 70, 100, 15, -1, 0, 4)
|
|
.attr(HighCritAttr),
|
|
new AttackMove(Moves.THUNDER_FANG, Type.ELECTRIC, MoveCategory.PHYSICAL, 65, 95, 15, 10, 0, 4)
|
|
.attr(FlinchAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.bitingMove(),
|
|
new AttackMove(Moves.ICE_FANG, Type.ICE, MoveCategory.PHYSICAL, 65, 95, 15, 10, 0, 4)
|
|
.attr(FlinchAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
|
.bitingMove(),
|
|
new AttackMove(Moves.FIRE_FANG, Type.FIRE, MoveCategory.PHYSICAL, 65, 95, 15, 10, 0, 4)
|
|
.attr(FlinchAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.bitingMove(),
|
|
new AttackMove(Moves.SHADOW_SNEAK, Type.GHOST, MoveCategory.PHYSICAL, 40, 100, 30, -1, 1, 4),
|
|
new AttackMove(Moves.MUD_BOMB, Type.GROUND, MoveCategory.SPECIAL, 65, 85, 10, 30, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.PSYCHO_CUT, Type.PSYCHIC, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
|
|
.attr(HighCritAttr)
|
|
.slicingMove()
|
|
.makesContact(false),
|
|
new AttackMove(Moves.ZEN_HEADBUTT, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 90, 15, 20, 0, 4)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.MIRROR_SHOT, Type.STEEL, MoveCategory.SPECIAL, 65, 85, 10, 30, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new AttackMove(Moves.FLASH_CANNON, Type.STEEL, MoveCategory.SPECIAL, 80, 100, 10, 10, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1),
|
|
new AttackMove(Moves.ROCK_CLIMB, Type.NORMAL, MoveCategory.PHYSICAL, 90, 85, 20, 20, 0, 4)
|
|
.attr(ConfuseAttr),
|
|
new StatusMove(Moves.DEFOG, Type.FLYING, -1, 15, -1, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.EVA, -1)
|
|
.attr(ClearWeatherAttr, WeatherType.FOG)
|
|
.attr(ClearTerrainAttr)
|
|
.attr(RemoveScreensAttr, false)
|
|
.attr(RemoveArenaTrapAttr, true),
|
|
new StatusMove(Moves.TRICK_ROOM, Type.PSYCHIC, -1, 5, -1, -7, 4)
|
|
.attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5)
|
|
.ignoresProtect()
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new AttackMove(Moves.DRACO_METEOR, Type.DRAGON, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
|
new AttackMove(Moves.DISCHARGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.LAVA_PLUME, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 4)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.LEAF_STORM, Type.GRASS, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
|
new AttackMove(Moves.POWER_WHIP, Type.GRASS, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 4),
|
|
new AttackMove(Moves.ROCK_WRECKER, Type.ROCK, MoveCategory.PHYSICAL, 150, 90, 5, -1, 0, 4)
|
|
.attr(RechargeAttr)
|
|
.makesContact(false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.CROSS_POISON, Type.POISON, MoveCategory.PHYSICAL, 70, 100, 20, 10, 0, 4)
|
|
.attr(HighCritAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.GUNK_SHOT, Type.POISON, MoveCategory.PHYSICAL, 120, 80, 5, 30, 0, 4)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.IRON_HEAD, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 15, 30, 0, 4)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.MAGNET_BOMB, Type.STEEL, MoveCategory.PHYSICAL, 60, -1, 20, -1, 0, 4)
|
|
.makesContact(false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.STONE_EDGE, Type.ROCK, MoveCategory.PHYSICAL, 100, 80, 5, -1, 0, 4)
|
|
.attr(HighCritAttr)
|
|
.makesContact(false),
|
|
new StatusMove(Moves.CAPTIVATE, Type.NORMAL, 100, 20, -1, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -2)
|
|
.condition((user, target, move) => target.isOppositeGender(user))
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.STEALTH_ROCK, Type.ROCK, -1, 20, -1, 0, 4)
|
|
.attr(AddArenaTrapTagAttr, ArenaTagType.STEALTH_ROCK)
|
|
.target(MoveTarget.ENEMY_SIDE),
|
|
new AttackMove(Moves.GRASS_KNOT, Type.GRASS, MoveCategory.SPECIAL, -1, 100, 20, -1, 0, 4)
|
|
.attr(WeightPowerAttr)
|
|
.makesContact()
|
|
.condition(failOnMaxCondition),
|
|
new AttackMove(Moves.CHATTER, Type.FLYING, MoveCategory.SPECIAL, 65, 100, 20, 100, 0, 4)
|
|
.attr(ConfuseAttr)
|
|
.soundBased(),
|
|
new AttackMove(Moves.JUDGMENT, Type.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 4)
|
|
.attr(FormChangeItemTypeAttr),
|
|
new AttackMove(Moves.BUG_BITE, Type.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 4)
|
|
.attr(StealEatBerryAttr),
|
|
new AttackMove(Moves.CHARGE_BEAM, Type.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 70, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 1, true),
|
|
new AttackMove(Moves.WOOD_HAMMER, Type.GRASS, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 4)
|
|
.attr(RecoilAttr, false, 0.33)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.AQUA_JET, Type.WATER, MoveCategory.PHYSICAL, 40, 100, 20, -1, 1, 4),
|
|
new AttackMove(Moves.ATTACK_ORDER, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 4)
|
|
.attr(HighCritAttr)
|
|
.makesContact(false),
|
|
new SelfStatusMove(Moves.DEFEND_ORDER, Type.BUG, -1, 10, -1, 0, 4)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, true),
|
|
new SelfStatusMove(Moves.HEAL_ORDER, Type.BUG, -1, 10, -1, 0, 4)
|
|
.attr(HealAttr, 0.5)
|
|
.triageMove(),
|
|
new AttackMove(Moves.HEAD_SMASH, Type.ROCK, MoveCategory.PHYSICAL, 150, 80, 5, -1, 0, 4)
|
|
.attr(RecoilAttr, false, 0.5)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.DOUBLE_HIT, Type.NORMAL, MoveCategory.PHYSICAL, 35, 90, 10, -1, 0, 4)
|
|
.attr(MultiHitAttr, MultiHitType._2),
|
|
new AttackMove(Moves.ROAR_OF_TIME, Type.DRAGON, MoveCategory.SPECIAL, 150, 90, 5, -1, 0, 4)
|
|
.attr(RechargeAttr),
|
|
new AttackMove(Moves.SPACIAL_REND, Type.DRAGON, MoveCategory.SPECIAL, 100, 95, 5, -1, 0, 4)
|
|
.attr(HighCritAttr),
|
|
new SelfStatusMove(Moves.LUNAR_DANCE, Type.PSYCHIC, -1, 10, -1, 0, 4)
|
|
.attr(SacrificialAttrOnHit)
|
|
.danceMove()
|
|
.triageMove()
|
|
.unimplemented(),
|
|
new AttackMove(Moves.CRUSH_GRIP, Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 5, -1, 0, 4)
|
|
.attr(OpponentHighHpPowerAttr, 120),
|
|
new AttackMove(Moves.MAGMA_STORM, Type.FIRE, MoveCategory.SPECIAL, 100, 75, 5, -1, 0, 4)
|
|
.attr(TrapAttr, BattlerTagType.MAGMA_STORM),
|
|
new StatusMove(Moves.DARK_VOID, Type.DARK, 50, 10, -1, 0, 4)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.SEED_FLARE, Type.GRASS, MoveCategory.SPECIAL, 120, 85, 5, 40, 0, 4)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -2),
|
|
new AttackMove(Moves.OMINOUS_WIND, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 5, 10, 0, 4)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
|
.windMove(),
|
|
new AttackMove(Moves.SHADOW_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
|
|
.attr(ChargeAttr, ChargeAnim.SHADOW_FORCE_CHARGING, i18next.t("moveTriggers:vanishedInstantly", {pokemonName: "{USER}"}), BattlerTagType.HIDDEN)
|
|
.ignoresProtect()
|
|
.ignoresVirtual(),
|
|
new SelfStatusMove(Moves.HONE_CLAWS, Type.DARK, -1, 15, -1, 0, 5)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.ACC ], 1, true),
|
|
new StatusMove(Moves.WIDE_GUARD, Type.ROCK, -1, 10, -1, 3, 5)
|
|
.target(MoveTarget.USER_SIDE)
|
|
.attr(AddArenaTagAttr, ArenaTagType.WIDE_GUARD, 1, true, true),
|
|
new StatusMove(Moves.GUARD_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.POWER_SPLIT, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.WONDER_ROOM, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
|
.ignoresProtect()
|
|
.target(MoveTarget.BOTH_SIDES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.PSYSHOCK, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
|
|
.attr(DefDefAttr),
|
|
new AttackMove(Moves.VENOSHOCK, Type.POISON, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 5)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1),
|
|
new SelfStatusMove(Moves.AUTOTOMIZE, Type.STEEL, -1, 15, -1, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 2, true)
|
|
.partial(),
|
|
new SelfStatusMove(Moves.RAGE_POWDER, Type.BUG, -1, 20, -1, 2, 5)
|
|
.powderMove()
|
|
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, true),
|
|
new StatusMove(Moves.TELEKINESIS, Type.PSYCHIC, -1, 15, -1, 0, 5)
|
|
.condition(failOnGravityCondition)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.MAGIC_ROOM, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
|
.ignoresProtect()
|
|
.target(MoveTarget.BOTH_SIDES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.SMACK_DOWN, Type.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
|
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.STORM_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
|
|
.attr(CritOnlyAttr),
|
|
new AttackMove(Moves.FLAME_BURST, Type.FIRE, MoveCategory.SPECIAL, 70, 100, 15, -1, 0, 5)
|
|
.partial(),
|
|
new AttackMove(Moves.SLUDGE_WAVE, Type.POISON, MoveCategory.SPECIAL, 95, 100, 10, 10, 0, 5)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new SelfStatusMove(Moves.QUIVER_DANCE, Type.BUG, -1, 20, -1, 0, 5)
|
|
.attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
|
.danceMove(),
|
|
new AttackMove(Moves.HEAVY_SLAM, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
|
.attr(MinimizeAccuracyAttr)
|
|
.attr(CompareWeightPowerAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
|
.condition(failOnMaxCondition),
|
|
new AttackMove(Moves.SYNCHRONOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 120, 100, 10, -1, 0, 5)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
|
.condition(unknownTypeCondition)
|
|
.attr(hitsSameTypeAttr),
|
|
new AttackMove(Moves.ELECTRO_BALL, Type.ELECTRIC, MoveCategory.SPECIAL, -1, 100, 10, -1, 0, 5)
|
|
.attr(ElectroBallPowerAttr)
|
|
.ballBombMove(),
|
|
new StatusMove(Moves.SOAK, Type.WATER, 100, 20, -1, 0, 5)
|
|
.attr(ChangeTypeAttr, Type.WATER),
|
|
new AttackMove(Moves.FLAME_CHARGE, Type.FIRE, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 1, true),
|
|
new SelfStatusMove(Moves.COIL, Type.POISON, -1, 20, -1, 0, 5)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.ACC ], 1, true),
|
|
new AttackMove(Moves.LOW_SWEEP, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 20, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
|
new AttackMove(Moves.ACID_SPRAY, Type.POISON, MoveCategory.SPECIAL, 40, 100, 20, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -2)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.FOUL_PLAY, Type.DARK, MoveCategory.PHYSICAL, 95, 100, 15, -1, 0, 5)
|
|
.attr(TargetAtkUserAtkAttr),
|
|
new StatusMove(Moves.SIMPLE_BEAM, Type.NORMAL, 100, 15, -1, 0, 5)
|
|
.attr(AbilityChangeAttr, Abilities.SIMPLE),
|
|
new StatusMove(Moves.ENTRAINMENT, Type.NORMAL, 100, 15, -1, 0, 5)
|
|
.attr(AbilityGiveAttr),
|
|
new StatusMove(Moves.AFTER_YOU, Type.NORMAL, -1, 15, -1, 0, 5)
|
|
.ignoresProtect()
|
|
.unimplemented(),
|
|
new AttackMove(Moves.ROUND, Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
|
.soundBased()
|
|
.partial(),
|
|
new AttackMove(Moves.ECHOED_VOICE, Type.NORMAL, MoveCategory.SPECIAL, 40, 100, 15, -1, 0, 5)
|
|
.attr(ConsecutiveUseMultiBasePowerAttr, 5, false)
|
|
.soundBased(),
|
|
new AttackMove(Moves.CHIP_AWAY, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 5)
|
|
.attr(IgnoreOpponentStatChangesAttr),
|
|
new AttackMove(Moves.CLEAR_SMOG, Type.POISON, MoveCategory.SPECIAL, 50, -1, 15, -1, 0, 5)
|
|
.attr(ResetStatsAttr),
|
|
new AttackMove(Moves.STORED_POWER, Type.PSYCHIC, MoveCategory.SPECIAL, 20, 100, 10, -1, 0, 5)
|
|
.attr(StatChangeCountPowerAttr),
|
|
new StatusMove(Moves.QUICK_GUARD, Type.FIGHTING, -1, 15, -1, 3, 5)
|
|
.target(MoveTarget.USER_SIDE)
|
|
.attr(AddArenaTagAttr, ArenaTagType.QUICK_GUARD, 1, true, true),
|
|
new SelfStatusMove(Moves.ALLY_SWITCH, Type.PSYCHIC, -1, 15, -1, 2, 5)
|
|
.ignoresProtect()
|
|
.unimplemented(),
|
|
new AttackMove(Moves.SCALD, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, 30, 0, 5)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.FREEZE)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new SelfStatusMove(Moves.SHELL_SMASH, Type.NORMAL, -1, 15, -1, 0, 5)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 2, true)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
|
|
new StatusMove(Moves.HEAL_PULSE, Type.PSYCHIC, -1, 10, -1, 0, 5)
|
|
.attr(HealAttr, 0.5, false, false)
|
|
.pulseMove()
|
|
.triageMove(),
|
|
new AttackMove(Moves.HEX, Type.GHOST, MoveCategory.SPECIAL, 65, 100, 10, -1, 0, 5)
|
|
.attr(
|
|
MovePowerMultiplierAttr,
|
|
(user, target, move) => target.status || target.hasAbility(Abilities.COMATOSE)? 2 : 1),
|
|
new AttackMove(Moves.SKY_DROP, Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 10, -1, 0, 5)
|
|
.attr(ChargeAttr, ChargeAnim.SKY_DROP_CHARGING, i18next.t("moveTriggers:tookTargetIntoSky", {pokemonName: "{USER}", targetName: "{TARGET}"}), BattlerTagType.FLYING) // TODO: Add 2nd turn message
|
|
.condition(failOnGravityCondition)
|
|
.ignoresVirtual(),
|
|
new SelfStatusMove(Moves.SHIFT_GEAR, Type.STEEL, -1, 10, -1, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 1, true)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 2, true),
|
|
new AttackMove(Moves.CIRCLE_THROW, Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
|
.attr(ForceSwitchOutAttr),
|
|
new AttackMove(Moves.INCINERATE, Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, 0, 5)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.attr(RemoveHeldItemAttr, true),
|
|
new StatusMove(Moves.QUASH, Type.DARK, 100, 15, -1, 0, 5)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.ACROBATICS, Type.FLYING, MoveCategory.PHYSICAL, 55, 100, 15, -1, 0, 5)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => Math.max(1, 2 - 0.2 * user.getHeldItems().filter(i => i.isTransferrable).reduce((v, m) => v + m.stackCount, 0))),
|
|
new StatusMove(Moves.REFLECT_TYPE, Type.NORMAL, -1, 15, -1, 0, 5)
|
|
.attr(CopyTypeAttr),
|
|
new AttackMove(Moves.RETALIATE, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 5, -1, 0, 5)
|
|
.partial(),
|
|
new AttackMove(Moves.FINAL_GAMBIT, Type.FIGHTING, MoveCategory.SPECIAL, -1, 100, 5, -1, 0, 5)
|
|
.attr(UserHpDamageAttr)
|
|
.attr(SacrificialAttrOnHit),
|
|
new StatusMove(Moves.BESTOW, Type.NORMAL, -1, 15, -1, 0, 5)
|
|
.ignoresProtect()
|
|
.unimplemented(),
|
|
new AttackMove(Moves.INFERNO, Type.FIRE, MoveCategory.SPECIAL, 100, 50, 5, 100, 0, 5)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new AttackMove(Moves.WATER_PLEDGE, Type.WATER, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
|
|
.partial(),
|
|
new AttackMove(Moves.FIRE_PLEDGE, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
|
|
.partial(),
|
|
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),
|
|
new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.BULLDOZE, Type.GROUND, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.FROST_BREATH, Type.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5)
|
|
.attr(CritOnlyAttr),
|
|
new AttackMove(Moves.DRAGON_TAIL, Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
|
|
.attr(ForceSwitchOutAttr),
|
|
new SelfStatusMove(Moves.WORK_UP, Type.NORMAL, -1, 30, -1, 0, 5)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 1, true),
|
|
new AttackMove(Moves.ELECTROWEB, Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.WILD_CHARGE, Type.ELECTRIC, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 5)
|
|
.attr(RecoilAttr)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.DRILL_RUN, Type.GROUND, MoveCategory.PHYSICAL, 80, 95, 10, -1, 0, 5)
|
|
.attr(HighCritAttr),
|
|
new AttackMove(Moves.DUAL_CHOP, Type.DRAGON, MoveCategory.PHYSICAL, 40, 90, 15, -1, 0, 5)
|
|
.attr(MultiHitAttr, MultiHitType._2),
|
|
new AttackMove(Moves.HEART_STAMP, Type.PSYCHIC, MoveCategory.PHYSICAL, 60, 100, 25, 30, 0, 5)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.HORN_LEECH, Type.GRASS, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 5)
|
|
.attr(HitHealAttr)
|
|
.triageMove(),
|
|
new AttackMove(Moves.SACRED_SWORD, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 5)
|
|
.attr(IgnoreOpponentStatChangesAttr)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.RAZOR_SHELL, Type.WATER, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.HEAT_CRASH, Type.FIRE, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 5)
|
|
.attr(MinimizeAccuracyAttr)
|
|
.attr(CompareWeightPowerAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
|
.condition(failOnMaxCondition),
|
|
new AttackMove(Moves.LEAF_TORNADO, Type.GRASS, MoveCategory.SPECIAL, 65, 90, 10, 50, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new AttackMove(Moves.STEAMROLLER, Type.BUG, MoveCategory.PHYSICAL, 65, 100, 20, 30, 0, 5)
|
|
.attr(FlinchAttr),
|
|
new SelfStatusMove(Moves.COTTON_GUARD, Type.GRASS, -1, 10, -1, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 3, true),
|
|
new AttackMove(Moves.NIGHT_DAZE, Type.DARK, MoveCategory.SPECIAL, 85, 95, 10, 40, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.ACC, -1),
|
|
new AttackMove(Moves.PSYSTRIKE, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 5)
|
|
.attr(DefDefAttr),
|
|
new AttackMove(Moves.TAIL_SLAP, Type.NORMAL, MoveCategory.PHYSICAL, 25, 85, 10, -1, 0, 5)
|
|
.attr(MultiHitAttr),
|
|
new AttackMove(Moves.HURRICANE, Type.FLYING, MoveCategory.SPECIAL, 110, 70, 10, 30, 0, 5)
|
|
.attr(ThunderAccuracyAttr)
|
|
.attr(ConfuseAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
|
.windMove(),
|
|
new AttackMove(Moves.HEAD_CHARGE, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 15, -1, 0, 5)
|
|
.attr(RecoilAttr)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.GEAR_GRIND, Type.STEEL, MoveCategory.PHYSICAL, 50, 85, 15, -1, 0, 5)
|
|
.attr(MultiHitAttr, MultiHitType._2),
|
|
new AttackMove(Moves.SEARING_SHOT, Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, 30, 0, 5)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.ballBombMove()
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.TECHNO_BLAST, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 5)
|
|
.attr(TechnoBlastTypeAttr),
|
|
new AttackMove(Moves.RELIC_SONG, Type.NORMAL, MoveCategory.SPECIAL, 75, 100, 10, 10, 0, 5)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.SECRET_SWORD, Type.FIGHTING, MoveCategory.SPECIAL, 85, 100, 10, -1, 0, 5)
|
|
.attr(DefDefAttr)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.GLACIATE, Type.ICE, MoveCategory.SPECIAL, 65, 95, 10, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.BOLT_STRIKE, Type.ELECTRIC, MoveCategory.PHYSICAL, 130, 85, 5, 20, 0, 5)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.BLUE_FLARE, Type.FIRE, MoveCategory.SPECIAL, 130, 85, 5, 20, 0, 5)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new AttackMove(Moves.FIERY_DANCE, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 50, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 1, true)
|
|
.danceMove(),
|
|
new AttackMove(Moves.FREEZE_SHOCK, Type.ICE, MoveCategory.PHYSICAL, 140, 90, 5, 30, 0, 5)
|
|
.attr(ChargeAttr, ChargeAnim.FREEZE_SHOCK_CHARGING, i18next.t("moveTriggers:becameCloakedInFreezingLight", {pokemonName: "{USER}"}))
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.ICE_BURN, Type.ICE, MoveCategory.SPECIAL, 140, 90, 5, 30, 0, 5)
|
|
.attr(ChargeAttr, ChargeAnim.ICE_BURN_CHARGING, i18next.t("moveTriggers:becameCloakedInFreezingAir", {pokemonName: "{USER}"}))
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SNARL, Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 100, 0, 5)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.ICICLE_CRASH, Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, 30, 0, 5)
|
|
.attr(FlinchAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.V_CREATE, Type.FIRE, MoveCategory.PHYSICAL, 180, 95, 5, -1, 0, 5)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF, BattleStat.SPD ], -1, true),
|
|
new AttackMove(Moves.FUSION_FLARE, Type.FIRE, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 5)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(LastMoveDoublePowerAttr, Moves.FUSION_BOLT),
|
|
new AttackMove(Moves.FUSION_BOLT, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 5)
|
|
.attr(LastMoveDoublePowerAttr, Moves.FUSION_FLARE)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.FLYING_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 95, 10, -1, 0, 6)
|
|
.attr(MinimizeAccuracyAttr)
|
|
.attr(FlyingTypeMultiplierAttr)
|
|
.attr(HitsTagAttr, BattlerTagType.MINIMIZED, true)
|
|
.condition(failOnGravityCondition),
|
|
new StatusMove(Moves.MAT_BLOCK, Type.FIGHTING, -1, 10, -1, 0, 6)
|
|
.target(MoveTarget.USER_SIDE)
|
|
.attr(AddArenaTagAttr, ArenaTagType.MAT_BLOCK, 1, true, true)
|
|
.condition(new FirstMoveCondition()),
|
|
new AttackMove(Moves.BELCH, Type.POISON, MoveCategory.SPECIAL, 120, 90, 10, -1, 0, 6)
|
|
.condition((user, target, move) => user.battleData.berriesEaten.length > 0),
|
|
new StatusMove(Moves.ROTOTILLER, Type.GROUND, -1, 10, -1, 0, 6)
|
|
.target(MoveTarget.ALL)
|
|
.condition((user,target,move) => {
|
|
// If any fielded pokémon is grass-type and grounded.
|
|
return [...user.scene.getEnemyParty(),...user.scene.getParty()].some((poke) => poke.isOfType(Type.GRASS) && poke.isGrounded());
|
|
})
|
|
.attr(StatChangeAttr, [BattleStat.ATK, BattleStat.SPATK], 1, false, (user, target, move) => target.isOfType(Type.GRASS) && target.isGrounded()),
|
|
new StatusMove(Moves.STICKY_WEB, Type.BUG, -1, 20, -1, 0, 6)
|
|
.attr(AddArenaTrapTagAttr, ArenaTagType.STICKY_WEB)
|
|
.target(MoveTarget.ENEMY_SIDE),
|
|
new AttackMove(Moves.FELL_STINGER, Type.BUG, MoveCategory.PHYSICAL, 50, 100, 25, -1, 0, 6)
|
|
.attr(PostVictoryStatChangeAttr, BattleStat.ATK, 3, true ),
|
|
new AttackMove(Moves.PHANTOM_FORCE, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
|
.attr(ChargeAttr, ChargeAnim.PHANTOM_FORCE_CHARGING, i18next.t("moveTriggers:vanishedInstantly", {pokemonName: "{USER}"}), BattlerTagType.HIDDEN)
|
|
.ignoresProtect()
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.TRICK_OR_TREAT, Type.GHOST, 100, 20, -1, 0, 6)
|
|
.attr(AddTypeAttr, Type.GHOST)
|
|
.partial(),
|
|
new StatusMove(Moves.NOBLE_ROAR, Type.NORMAL, 100, 30, -1, 0, 6)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1)
|
|
.soundBased(),
|
|
new StatusMove(Moves.ION_DELUGE, Type.ELECTRIC, -1, 25, -1, 1, 6)
|
|
.target(MoveTarget.BOTH_SIDES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.PARABOLIC_CHARGE, Type.ELECTRIC, MoveCategory.SPECIAL, 65, 100, 20, -1, 0, 6)
|
|
.attr(HitHealAttr)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
|
.triageMove(),
|
|
new StatusMove(Moves.FORESTS_CURSE, Type.GRASS, 100, 20, -1, 0, 6)
|
|
.attr(AddTypeAttr, Type.GRASS)
|
|
.partial(),
|
|
new AttackMove(Moves.PETAL_BLIZZARD, Type.GRASS, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 6)
|
|
.windMove()
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.FREEZE_DRY, Type.ICE, MoveCategory.SPECIAL, 70, 100, 20, 10, 0, 6)
|
|
.attr(StatusEffectAttr, StatusEffect.FREEZE)
|
|
.attr(WaterSuperEffectTypeMultiplierAttr),
|
|
new AttackMove(Moves.DISARMING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 40, -1, 15, -1, 0, 6)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
|
|
.attr(ForceSwitchOutAttr, true, false)
|
|
.soundBased(),
|
|
new StatusMove(Moves.TOPSY_TURVY, Type.DARK, -1, 20, -1, 0, 6)
|
|
.attr(InvertStatsAttr),
|
|
new AttackMove(Moves.DRAINING_KISS, Type.FAIRY, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 6)
|
|
.attr(HitHealAttr, 0.75)
|
|
.makesContact()
|
|
.triageMove(),
|
|
new StatusMove(Moves.CRAFTY_SHIELD, Type.FAIRY, -1, 10, -1, 3, 6)
|
|
.target(MoveTarget.USER_SIDE)
|
|
.attr(AddArenaTagAttr, ArenaTagType.CRAFTY_SHIELD, 1, true, true),
|
|
new StatusMove(Moves.FLOWER_SHIELD, Type.FAIRY, -1, 10, -1, 0, 6)
|
|
.target(MoveTarget.ALL)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 1, false, (user, target, move) => target.getTypes().includes(Type.GRASS) && !target.getTag(SemiInvulnerableTag)),
|
|
new StatusMove(Moves.GRASSY_TERRAIN, Type.GRASS, -1, 10, -1, 0, 6)
|
|
.attr(TerrainChangeAttr, TerrainType.GRASSY)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new StatusMove(Moves.MISTY_TERRAIN, Type.FAIRY, -1, 10, -1, 0, 6)
|
|
.attr(TerrainChangeAttr, TerrainType.MISTY)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new StatusMove(Moves.ELECTRIFY, Type.ELECTRIC, -1, 20, -1, 0, 6)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.PLAY_ROUGH, Type.FAIRY, MoveCategory.PHYSICAL, 90, 90, 10, 10, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new AttackMove(Moves.FAIRY_WIND, Type.FAIRY, MoveCategory.SPECIAL, 40, 100, 30, -1, 0, 6)
|
|
.windMove(),
|
|
new AttackMove(Moves.MOONBLAST, Type.FAIRY, MoveCategory.SPECIAL, 95, 100, 15, 30, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1),
|
|
new AttackMove(Moves.BOOMBURST, Type.NORMAL, MoveCategory.SPECIAL, 140, 100, 10, -1, 0, 6)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new StatusMove(Moves.FAIRY_LOCK, Type.FAIRY, -1, 10, -1, 0, 6)
|
|
.target(MoveTarget.BOTH_SIDES)
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.KINGS_SHIELD, Type.STEEL, -1, 10, -1, 4, 6)
|
|
.attr(ProtectAttr, BattlerTagType.KINGS_SHIELD),
|
|
new StatusMove(Moves.PLAY_NICE, Type.NORMAL, -1, 20, -1, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new StatusMove(Moves.CONFIDE, Type.NORMAL, -1, 20, -1, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1)
|
|
.soundBased(),
|
|
new AttackMove(Moves.DIAMOND_STORM, Type.ROCK, MoveCategory.PHYSICAL, 100, 95, 5, 50, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 2, true)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.STEAM_ERUPTION, Type.WATER, MoveCategory.SPECIAL, 110, 95, 5, 30, 0, 6)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new AttackMove(Moves.HYPERSPACE_HOLE, Type.PSYCHIC, MoveCategory.SPECIAL, 80, -1, 5, -1, 0, 6)
|
|
.ignoresProtect(),
|
|
new AttackMove(Moves.WATER_SHURIKEN, Type.WATER, MoveCategory.SPECIAL, 15, 100, 20, -1, 1, 6)
|
|
.attr(MultiHitAttr)
|
|
.attr(WaterShurikenPowerAttr)
|
|
.attr(WaterShurikenMultiHitTypeAttr),
|
|
new AttackMove(Moves.MYSTICAL_FIRE, Type.FIRE, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1),
|
|
new SelfStatusMove(Moves.SPIKY_SHIELD, Type.GRASS, -1, 10, -1, 4, 6)
|
|
.attr(ProtectAttr, BattlerTagType.SPIKY_SHIELD),
|
|
new StatusMove(Moves.AROMATIC_MIST, Type.FAIRY, -1, 20, -1, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, 1)
|
|
.target(MoveTarget.NEAR_ALLY),
|
|
new StatusMove(Moves.EERIE_IMPULSE, Type.ELECTRIC, 100, 15, -1, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -2),
|
|
new StatusMove(Moves.VENOM_DRENCH, Type.POISON, 100, 20, -1, 0, 6)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], -1, false, (user, target, move) => target.status?.effect === StatusEffect.POISON || target.status?.effect === StatusEffect.TOXIC)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.POWDER, Type.BUG, 100, 20, -1, 1, 6)
|
|
.powderMove()
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.GEOMANCY, Type.FAIRY, -1, 10, -1, 0, 6)
|
|
.attr(ChargeAttr, ChargeAnim.GEOMANCY_CHARGING, i18next.t("moveTriggers:isChargingPower", {pokemonName: "{USER}"}))
|
|
.attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true)
|
|
.ignoresVirtual(),
|
|
new StatusMove(Moves.MAGNETIC_FLUX, Type.ELECTRIC, -1, 20, -1, 0, 6)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false)))
|
|
.target(MoveTarget.USER_AND_ALLIES)
|
|
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))),
|
|
new StatusMove(Moves.HAPPY_HOUR, Type.NORMAL, -1, 30, -1, 0, 6) // No animation
|
|
.attr(AddArenaTagAttr, ArenaTagType.HAPPY_HOUR, null, true)
|
|
.target(MoveTarget.USER_SIDE),
|
|
new StatusMove(Moves.ELECTRIC_TERRAIN, Type.ELECTRIC, -1, 10, -1, 0, 6)
|
|
.attr(TerrainChangeAttr, TerrainType.ELECTRIC)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new AttackMove(Moves.DAZZLING_GLEAM, Type.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new SelfStatusMove(Moves.CELEBRATE, Type.NORMAL, -1, 40, -1, 0, 6),
|
|
new StatusMove(Moves.HOLD_HANDS, Type.NORMAL, -1, 40, -1, 0, 6)
|
|
.target(MoveTarget.NEAR_ALLY),
|
|
new StatusMove(Moves.BABY_DOLL_EYES, Type.FAIRY, 100, 30, -1, 1, 6)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new AttackMove(Moves.NUZZLE, Type.ELECTRIC, MoveCategory.PHYSICAL, 20, 100, 20, 100, 0, 6)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.HOLD_BACK, Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 6)
|
|
.attr(SurviveDamageAttr),
|
|
new AttackMove(Moves.INFESTATION, Type.BUG, MoveCategory.SPECIAL, 20, 100, 20, -1, 0, 6)
|
|
.makesContact()
|
|
.attr(TrapAttr, BattlerTagType.INFESTATION),
|
|
new AttackMove(Moves.POWER_UP_PUNCH, Type.FIGHTING, MoveCategory.PHYSICAL, 40, 100, 20, 100, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 1, true)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.OBLIVION_WING, Type.FLYING, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 6)
|
|
.attr(HitHealAttr, 0.75)
|
|
.triageMove(),
|
|
new AttackMove(Moves.THOUSAND_ARROWS, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
|
.attr(NeutralDamageAgainstFlyingTypeMultiplierAttr)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.IGNORE_FLYING, false, false, 1, 1, true)
|
|
.attr(HitsTagAttr, BattlerTagType.FLYING, false)
|
|
.attr(HitsTagAttr, BattlerTagType.MAGNET_RISEN, false)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
|
|
.attr(RemoveBattlerTagAttr, [BattlerTagType.FLYING, BattlerTagType.MAGNET_RISEN])
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.THOUSAND_WAVES, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.LANDS_WRATH, Type.GROUND, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 6)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.LIGHT_OF_RUIN, Type.FAIRY, MoveCategory.SPECIAL, 140, 90, 5, -1, 0, 6)
|
|
.attr(RecoilAttr, false, 0.5)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.ORIGIN_PULSE, Type.WATER, MoveCategory.SPECIAL, 110, 85, 10, -1, 0, 6)
|
|
.pulseMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.PRECIPICE_BLADES, Type.GROUND, MoveCategory.PHYSICAL, 120, 85, 10, -1, 0, 6)
|
|
.makesContact(false)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.DRAGON_ASCENT, Type.FLYING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 6)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
|
|
new AttackMove(Moves.HYPERSPACE_FURY, Type.DARK, MoveCategory.PHYSICAL, 100, -1, 5, -1, 0, 6)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1, true)
|
|
.makesContact(false)
|
|
.ignoresProtect(),
|
|
/* Unused */
|
|
new AttackMove(Moves.BREAKNECK_BLITZ__PHYSICAL, Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.BREAKNECK_BLITZ__SPECIAL, Type.NORMAL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.ALL_OUT_PUMMELING__PHYSICAL, Type.FIGHTING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.ALL_OUT_PUMMELING__SPECIAL, Type.FIGHTING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SUPERSONIC_SKYSTRIKE__PHYSICAL, Type.FLYING, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SUPERSONIC_SKYSTRIKE__SPECIAL, Type.FLYING, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.ACID_DOWNPOUR__PHYSICAL, Type.POISON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.ACID_DOWNPOUR__SPECIAL, Type.POISON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.TECTONIC_RAGE__PHYSICAL, Type.GROUND, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.TECTONIC_RAGE__SPECIAL, Type.GROUND, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.CONTINENTAL_CRUSH__PHYSICAL, Type.ROCK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.CONTINENTAL_CRUSH__SPECIAL, Type.ROCK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SAVAGE_SPIN_OUT__PHYSICAL, Type.BUG, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SAVAGE_SPIN_OUT__SPECIAL, Type.BUG, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.NEVER_ENDING_NIGHTMARE__PHYSICAL, Type.GHOST, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.NEVER_ENDING_NIGHTMARE__SPECIAL, Type.GHOST, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.CORKSCREW_CRASH__PHYSICAL, Type.STEEL, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.CORKSCREW_CRASH__SPECIAL, Type.STEEL, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.INFERNO_OVERDRIVE__PHYSICAL, Type.FIRE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.INFERNO_OVERDRIVE__SPECIAL, Type.FIRE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.HYDRO_VORTEX__PHYSICAL, Type.WATER, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.HYDRO_VORTEX__SPECIAL, Type.WATER, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.BLOOM_DOOM__PHYSICAL, Type.GRASS, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.BLOOM_DOOM__SPECIAL, Type.GRASS, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.GIGAVOLT_HAVOC__PHYSICAL, Type.ELECTRIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.GIGAVOLT_HAVOC__SPECIAL, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SHATTERED_PSYCHE__PHYSICAL, Type.PSYCHIC, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SHATTERED_PSYCHE__SPECIAL, Type.PSYCHIC, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SUBZERO_SLAMMER__PHYSICAL, Type.ICE, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SUBZERO_SLAMMER__SPECIAL, Type.ICE, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.DEVASTATING_DRAKE__PHYSICAL, Type.DRAGON, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.DEVASTATING_DRAKE__SPECIAL, Type.DRAGON, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.BLACK_HOLE_ECLIPSE__PHYSICAL, Type.DARK, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.BLACK_HOLE_ECLIPSE__SPECIAL, Type.DARK, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.TWINKLE_TACKLE__PHYSICAL, Type.FAIRY, MoveCategory.PHYSICAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.TWINKLE_TACKLE__SPECIAL, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.CATASTROPIKA, Type.ELECTRIC, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
/* End Unused */
|
|
new SelfStatusMove(Moves.SHORE_UP, Type.GROUND, -1, 5, -1, 0, 7)
|
|
.attr(SandHealAttr)
|
|
.triageMove(),
|
|
new AttackMove(Moves.FIRST_IMPRESSION, Type.BUG, MoveCategory.PHYSICAL, 90, 100, 10, -1, 2, 7)
|
|
.condition(new FirstMoveCondition()),
|
|
new SelfStatusMove(Moves.BANEFUL_BUNKER, Type.POISON, -1, 10, -1, 4, 7)
|
|
.attr(ProtectAttr, BattlerTagType.BANEFUL_BUNKER),
|
|
new AttackMove(Moves.SPIRIT_SHACKLE, Type.GHOST, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 7)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
|
|
.attr(IgnoreOpponentStatChangesAttr),
|
|
new AttackMove(Moves.SPARKLING_ARIA, Type.WATER, MoveCategory.SPECIAL, 90, 100, 10, 100, 0, 7)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.BURN)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.ICE_HAMMER, Type.ICE, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1, true)
|
|
.punchingMove(),
|
|
new StatusMove(Moves.FLORAL_HEALING, Type.FAIRY, -1, 10, -1, 0, 7)
|
|
.attr(BoostHealAttr, 0.5, 2/3, true, false, (user, target, move) => user.scene.arena.terrain?.terrainType === TerrainType.GRASSY)
|
|
.triageMove(),
|
|
new AttackMove(Moves.HIGH_HORSEPOWER, Type.GROUND, MoveCategory.PHYSICAL, 95, 95, 10, -1, 0, 7),
|
|
new StatusMove(Moves.STRENGTH_SAP, Type.GRASS, 100, 10, -1, 0, 7)
|
|
.attr(HitHealAttr, null, Stat.ATK)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1)
|
|
.condition((user, target, move) => target.summonData.battleStats[BattleStat.ATK] > -6)
|
|
.triageMove(),
|
|
new AttackMove(Moves.SOLAR_BLADE, Type.GRASS, MoveCategory.PHYSICAL, 125, 100, 10, -1, 0, 7)
|
|
.attr(SunlightChargeAttr, ChargeAnim.SOLAR_BLADE_CHARGING, i18next.t("moveTriggers:isGlowing", {pokemonName: "{USER}"}))
|
|
.attr(AntiSunlightPowerDecreaseAttr)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.LEAFAGE, Type.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 7)
|
|
.makesContact(false),
|
|
new StatusMove(Moves.SPOTLIGHT, Type.NORMAL, -1, 15, -1, 3, 7)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.CENTER_OF_ATTENTION, false),
|
|
new StatusMove(Moves.TOXIC_THREAD, Type.POISON, 100, 20, -1, 0, 7)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
|
new SelfStatusMove(Moves.LASER_FOCUS, Type.NORMAL, -1, 30, -1, 0, 7)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_CRIT, true, false),
|
|
new StatusMove(Moves.GEAR_UP, Type.STEEL, -1, 20, -1, 0, 7)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 1, false, (user, target, move) => !![ Abilities.PLUS, Abilities.MINUS].find(a => target.hasAbility(a, false)))
|
|
.target(MoveTarget.USER_AND_ALLIES)
|
|
.condition((user, target, move) => !![ user, user.getAlly() ].filter(p => p?.isActive()).find(p => !![ Abilities.PLUS, Abilities.MINUS].find(a => p.hasAbility(a, false)))),
|
|
new AttackMove(Moves.THROAT_CHOP, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7)
|
|
.partial(),
|
|
new AttackMove(Moves.POLLEN_PUFF, Type.BUG, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 7)
|
|
.attr(StatusCategoryOnAllyAttr)
|
|
.attr(HealOnAllyAttr, 0.5, true, false)
|
|
.ballBombMove(),
|
|
new AttackMove(Moves.ANCHOR_SHOT, Type.STEEL, MoveCategory.PHYSICAL, 80, 100, 20, 100, 0, 7)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true),
|
|
new StatusMove(Moves.PSYCHIC_TERRAIN, Type.PSYCHIC, -1, 10, -1, 0, 7)
|
|
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new AttackMove(Moves.LUNGE, Type.BUG, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new AttackMove(Moves.FIRE_LASH, Type.FIRE, MoveCategory.PHYSICAL, 80, 100, 15, 100, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1),
|
|
new AttackMove(Moves.POWER_TRIP, Type.DARK, MoveCategory.PHYSICAL, 20, 100, 10, -1, 0, 7)
|
|
.attr(StatChangeCountPowerAttr),
|
|
new AttackMove(Moves.BURN_UP, Type.FIRE, MoveCategory.SPECIAL, 130, 100, 5, -1, 0, 7)
|
|
.condition((user) => {
|
|
const userTypes = user.getTypes(true);
|
|
return userTypes.includes(Type.FIRE);
|
|
})
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(RemoveTypeAttr, Type.FIRE, (user) => {
|
|
user.scene.queueMessage(i18next.t("moveTriggers:burnedItselfOut", {pokemonName: getPokemonNameWithAffix(user)}));
|
|
}),
|
|
new StatusMove(Moves.SPEED_SWAP, Type.PSYCHIC, -1, 10, -1, 0, 7)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.SMART_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 7),
|
|
new StatusMove(Moves.PURIFY, Type.POISON, -1, 20, -1, 0, 7)
|
|
.condition(
|
|
(user: Pokemon, target: Pokemon, move: Move) => isNonVolatileStatusEffect(target.status?.effect))
|
|
.attr(HealAttr, 0.5)
|
|
.attr(HealStatusEffectAttr, false, ...getNonVolatileStatusEffects())
|
|
.triageMove(),
|
|
new AttackMove(Moves.REVELATION_DANCE, Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 15, -1, 0, 7)
|
|
.danceMove()
|
|
.attr(MatchUserTypeAttr),
|
|
new AttackMove(Moves.CORE_ENFORCER, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 10, -1, 0, 7)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.attr(SuppressAbilitiesIfActedAttr),
|
|
new AttackMove(Moves.TROP_KICK, Type.GRASS, MoveCategory.PHYSICAL, 70, 100, 15, 100, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new StatusMove(Moves.INSTRUCT, Type.PSYCHIC, -1, 15, -1, 0, 7)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.BEAK_BLAST, Type.FLYING, MoveCategory.PHYSICAL, 100, 100, 15, -1, 5, 7)
|
|
.attr(ChargeAttr, ChargeAnim.BEAK_BLAST_CHARGING, i18next.t("moveTriggers:startedHeatingUpBeak", {pokemonName: "{USER}"}), undefined, false, true, -3)
|
|
.ballBombMove()
|
|
.makesContact(false)
|
|
.partial(),
|
|
new AttackMove(Moves.CLANGING_SCALES, Type.DRAGON, MoveCategory.SPECIAL, 110, 100, 5, -1, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1, true)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.DRAGON_HAMMER, Type.DRAGON, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 7),
|
|
new AttackMove(Moves.BRUTAL_SWING, Type.DARK, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 7)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new StatusMove(Moves.AURORA_VEIL, Type.ICE, -1, 20, -1, 0, 7)
|
|
.condition((user, target, move) => (user.scene.arena.weather?.weatherType === WeatherType.HAIL || user.scene.arena.weather?.weatherType === WeatherType.SNOW) && !user.scene.arena.weather?.isEffectSuppressed(user.scene))
|
|
.attr(AddArenaTagAttr, ArenaTagType.AURORA_VEIL, 5, true)
|
|
.target(MoveTarget.USER_SIDE),
|
|
/* Unused */
|
|
new AttackMove(Moves.SINISTER_ARROW_RAID, Type.GHOST, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
|
|
.makesContact(false)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MALICIOUS_MOONSAULT, Type.DARK, MoveCategory.PHYSICAL, 180, -1, 1, -1, 0, 7)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.OCEANIC_OPERETTA, Type.WATER, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.GUARDIAN_OF_ALOLA, Type.FAIRY, MoveCategory.SPECIAL, -1, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SOUL_STEALING_7_STAR_STRIKE, Type.GHOST, MoveCategory.PHYSICAL, 195, -1, 1, -1, 0, 7)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.STOKED_SPARKSURFER, Type.ELECTRIC, MoveCategory.SPECIAL, 175, -1, 1, 100, 0, 7)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.PULVERIZING_PANCAKE, Type.NORMAL, MoveCategory.PHYSICAL, 210, -1, 1, -1, 0, 7)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
new SelfStatusMove(Moves.EXTREME_EVOBOOST, Type.NORMAL, -1, 1, -1, 0, 7)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 2, true)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.GENESIS_SUPERNOVA, Type.PSYCHIC, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
|
|
.attr(TerrainChangeAttr, TerrainType.PSYCHIC)
|
|
.ignoresVirtual(),
|
|
/* End Unused */
|
|
new AttackMove(Moves.SHELL_TRAP, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, -3, 7)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.partial(),
|
|
new AttackMove(Moves.FLEUR_CANNON, Type.FAIRY, MoveCategory.SPECIAL, 130, 90, 5, -1, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -2, true),
|
|
new AttackMove(Moves.PSYCHIC_FANGS, Type.PSYCHIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7)
|
|
.bitingMove()
|
|
.attr(RemoveScreensAttr),
|
|
new AttackMove(Moves.STOMPING_TANTRUM, Type.GROUND, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 7)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
|
|
new AttackMove(Moves.SHADOW_BONE, Type.GHOST, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.ACCELEROCK, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 20, -1, 1, 7),
|
|
new AttackMove(Moves.LIQUIDATION, Type.WATER, MoveCategory.PHYSICAL, 85, 100, 10, 20, 0, 7)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1),
|
|
new AttackMove(Moves.PRISMATIC_LASER, Type.PSYCHIC, MoveCategory.SPECIAL, 160, 100, 10, -1, 0, 7)
|
|
.attr(RechargeAttr),
|
|
new AttackMove(Moves.SPECTRAL_THIEF, Type.GHOST, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 7)
|
|
.partial(),
|
|
new AttackMove(Moves.SUNSTEEL_STRIKE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 7)
|
|
.ignoresAbilities()
|
|
.partial(),
|
|
new AttackMove(Moves.MOONGEIST_BEAM, Type.GHOST, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
|
|
.ignoresAbilities()
|
|
.partial(),
|
|
new StatusMove(Moves.TEARFUL_LOOK, Type.NORMAL, -1, 20, -1, 0, 7)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], -1),
|
|
new AttackMove(Moves.ZING_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 7)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.NATURES_MADNESS, Type.FAIRY, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 7)
|
|
.attr(TargetHalfHpDamageAttr),
|
|
new AttackMove(Moves.MULTI_ATTACK, Type.NORMAL, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 7)
|
|
.attr(FormChangeItemTypeAttr),
|
|
/* Unused */
|
|
new AttackMove(Moves.TEN_MILLION_VOLT_THUNDERBOLT, Type.ELECTRIC, MoveCategory.SPECIAL, 195, -1, 1, -1, 0, 7)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
/* End Unused */
|
|
new AttackMove(Moves.MIND_BLOWN, Type.FIRE, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 7)
|
|
.condition(failIfDampCondition)
|
|
.attr(HalfSacrificialAttr)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS),
|
|
new AttackMove(Moves.PLASMA_FISTS, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 100, 15, -1, 0, 7)
|
|
.punchingMove()
|
|
.partial(),
|
|
new AttackMove(Moves.PHOTON_GEYSER, Type.PSYCHIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 7)
|
|
.attr(PhotonGeyserCategoryAttr)
|
|
.ignoresAbilities()
|
|
.partial(),
|
|
/* Unused */
|
|
new AttackMove(Moves.LIGHT_THAT_BURNS_THE_SKY, Type.PSYCHIC, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7)
|
|
.attr(PhotonGeyserCategoryAttr)
|
|
.ignoresAbilities()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SEARING_SUNRAZE_SMASH, Type.STEEL, MoveCategory.PHYSICAL, 200, -1, 1, -1, 0, 7)
|
|
.ignoresAbilities()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MENACING_MOONRAZE_MAELSTROM, Type.GHOST, MoveCategory.SPECIAL, 200, -1, 1, -1, 0, 7)
|
|
.ignoresAbilities()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.LETS_SNUGGLE_FOREVER, Type.FAIRY, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SPLINTERED_STORMSHARDS, Type.ROCK, MoveCategory.PHYSICAL, 190, -1, 1, -1, 0, 7)
|
|
.attr(ClearTerrainAttr)
|
|
.makesContact(false)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.CLANGOROUS_SOULBLAZE, Type.DRAGON, MoveCategory.SPECIAL, 185, -1, 1, 100, 0, 7)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.partial()
|
|
.ignoresVirtual(),
|
|
/* End Unused */
|
|
new AttackMove(Moves.ZIPPY_ZAP, Type.ELECTRIC, MoveCategory.PHYSICAL, 80, 100, 10, 100, 2, 7)
|
|
.attr(StatChangeAttr, BattleStat.EVA, 1, true),
|
|
new AttackMove(Moves.SPLISHY_SPLASH, Type.WATER, MoveCategory.SPECIAL, 90, 100, 15, 30, 0, 7)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.FLOATY_FALL, Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, 30, 0, 7)
|
|
.attr(FlinchAttr),
|
|
new AttackMove(Moves.PIKA_PAPOW, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 20, -1, 0, 7)
|
|
.attr(FriendshipPowerAttr),
|
|
new AttackMove(Moves.BOUNCY_BUBBLE, Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, -1, 0, 7)
|
|
.attr(HitHealAttr, 1.0)
|
|
.triageMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.BUZZY_BUZZ, Type.ELECTRIC, MoveCategory.SPECIAL, 60, 100, 20, 100, 0, 7)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
|
new AttackMove(Moves.SIZZLY_SLIDE, Type.FIRE, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 7)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new AttackMove(Moves.GLITZY_GLOW, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7)
|
|
.attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, false, true),
|
|
new AttackMove(Moves.BADDY_BAD, Type.DARK, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7)
|
|
.attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, false, true),
|
|
new AttackMove(Moves.SAPPY_SEED, Type.GRASS, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 7)
|
|
.makesContact(false)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.SEEDED),
|
|
new AttackMove(Moves.FREEZY_FROST, Type.ICE, MoveCategory.SPECIAL, 100, 90, 10, -1, 0, 7)
|
|
.attr(ResetStatsAttr),
|
|
new AttackMove(Moves.SPARKLY_SWIRL, Type.FAIRY, MoveCategory.SPECIAL, 120, 85, 5, -1, 0, 7)
|
|
.attr(PartyStatusCureAttr, null, Abilities.NONE),
|
|
new AttackMove(Moves.VEEVEE_VOLLEY, Type.NORMAL, MoveCategory.PHYSICAL, -1, -1, 20, -1, 0, 7)
|
|
.attr(FriendshipPowerAttr),
|
|
new AttackMove(Moves.DOUBLE_IRON_BASH, Type.STEEL, MoveCategory.PHYSICAL, 60, 100, 5, 30, 0, 7)
|
|
.attr(MultiHitAttr, MultiHitType._2)
|
|
.attr(FlinchAttr)
|
|
.punchingMove(),
|
|
/* Unused */
|
|
new SelfStatusMove(Moves.MAX_GUARD, Type.NORMAL, -1, 10, -1, 4, 8)
|
|
.attr(ProtectAttr)
|
|
.ignoresVirtual(),
|
|
/* End Unused */
|
|
new AttackMove(Moves.DYNAMAX_CANNON, Type.DRAGON, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => {
|
|
// Move is only stronger against overleveled foes.
|
|
if (target.level > target.scene.getMaxExpLevel()) {
|
|
const dynamaxCannonPercentMarginBeforeFullDamage = 0.05; // How much % above MaxExpLevel of wave will the target need to be to take full damage.
|
|
// The move's power scales as the margin is approached, reaching double power when it does or goes over it.
|
|
return 1 + Math.min(1, (target.level - target.scene.getMaxExpLevel()) / (target.scene.getMaxExpLevel() * dynamaxCannonPercentMarginBeforeFullDamage));
|
|
} else {
|
|
return 1;
|
|
}
|
|
})
|
|
.attr(DiscourageFrequentUseAttr)
|
|
.ignoresVirtual(),
|
|
|
|
new AttackMove(Moves.SNIPE_SHOT, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 8)
|
|
.attr(HighCritAttr)
|
|
.attr(BypassRedirectAttr),
|
|
new AttackMove(Moves.JAW_LOCK, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, false, 1)
|
|
.bitingMove(),
|
|
new SelfStatusMove(Moves.STUFF_CHEEKS, Type.NORMAL, -1, 10, -1, 0, 8) // TODO: Stuff Cheeks should not be selectable when the user does not have a berry, see wiki
|
|
.attr(EatBerryAttr)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 2, true)
|
|
.condition((user) => {
|
|
const userBerries = user.scene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
|
|
return userBerries.length > 0;
|
|
})
|
|
.partial(),
|
|
new SelfStatusMove(Moves.NO_RETREAT, Type.FIGHTING, -1, 5, -1, 0, 8)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, true, true, 1),
|
|
new StatusMove(Moves.TAR_SHOT, Type.ROCK, 100, 15, -1, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.partial(),
|
|
new StatusMove(Moves.MAGIC_POWDER, Type.PSYCHIC, 100, 20, -1, 0, 8)
|
|
.attr(ChangeTypeAttr, Type.PSYCHIC)
|
|
.powderMove(),
|
|
new AttackMove(Moves.DRAGON_DARTS, Type.DRAGON, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 8)
|
|
.attr(MultiHitAttr, MultiHitType._2)
|
|
.makesContact(false)
|
|
.partial(),
|
|
new StatusMove(Moves.TEATIME, Type.NORMAL, -1, 10, -1, 0, 8)
|
|
.attr(EatBerryAttr)
|
|
.target(MoveTarget.ALL),
|
|
new StatusMove(Moves.OCTOLOCK, Type.FIGHTING, 100, 15, -1, 0, 8)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.OCTOLOCK, false, true, 1),
|
|
new AttackMove(Moves.BOLT_BEAK, Type.ELECTRIC, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
|
.attr(FirstAttackDoublePowerAttr),
|
|
new AttackMove(Moves.FISHIOUS_REND, Type.WATER, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 8)
|
|
.attr(FirstAttackDoublePowerAttr)
|
|
.bitingMove(),
|
|
new StatusMove(Moves.COURT_CHANGE, Type.NORMAL, 100, 10, -1, 0, 8)
|
|
.attr(SwapArenaTagsAttr, [ArenaTagType.AURORA_VEIL, ArenaTagType.LIGHT_SCREEN, ArenaTagType.MIST, ArenaTagType.REFLECT, ArenaTagType.SPIKES, ArenaTagType.STEALTH_ROCK, ArenaTagType.STICKY_WEB, ArenaTagType.TAILWIND, ArenaTagType.TOXIC_SPIKES]),
|
|
new AttackMove(Moves.MAX_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_FLUTTERBY, Type.BUG, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_LIGHTNING, Type.ELECTRIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_STRIKE, Type.NORMAL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_KNUCKLE, Type.FIGHTING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_PHANTASM, Type.GHOST, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_HAILSTORM, Type.ICE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_OOZE, Type.POISON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_GEYSER, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_AIRSTREAM, Type.FLYING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_STARFALL, Type.FAIRY, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_WYRMWIND, Type.DRAGON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_MINDSTORM, Type.PSYCHIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_ROCKFALL, Type.ROCK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_QUAKE, Type.GROUND, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_DARKNESS, Type.DARK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_OVERGROWTH, Type.GRASS, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.MAX_STEELSPIKE, Type.STEEL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.NEAR_ENEMY)
|
|
.unimplemented()
|
|
.ignoresVirtual(),
|
|
/* End Unused */
|
|
new SelfStatusMove(Moves.CLANGOROUS_SOUL, Type.DRAGON, 100, 5, -1, 0, 8)
|
|
.attr(CutHpStatBoostAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, 3)
|
|
.soundBased()
|
|
.danceMove(),
|
|
new AttackMove(Moves.BODY_PRESS, Type.FIGHTING, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 8)
|
|
.attr(DefAtkAttr),
|
|
new StatusMove(Moves.DECORATE, Type.FAIRY, -1, 15, -1, 0, 8)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 2),
|
|
new AttackMove(Moves.DRUM_BEATING, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.SNAP_TRAP, Type.GRASS, MoveCategory.PHYSICAL, 35, 100, 15, -1, 0, 8)
|
|
.attr(TrapAttr, BattlerTagType.SNAP_TRAP),
|
|
new AttackMove(Moves.PYRO_BALL, Type.FIRE, MoveCategory.PHYSICAL, 120, 90, 5, 10, 0, 8)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.ballBombMove()
|
|
.makesContact(false),
|
|
new AttackMove(Moves.BEHEMOTH_BLADE, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 8)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.BEHEMOTH_BASH, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 8),
|
|
new AttackMove(Moves.AURA_WHEEL, Type.ELECTRIC, MoveCategory.PHYSICAL, 110, 100, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 1, true)
|
|
.makesContact(false)
|
|
.attr(AuraWheelTypeAttr)
|
|
.condition((user, target, move) => [user.species.speciesId, user.fusionSpecies?.speciesId].includes(Species.MORPEKO)), // Missing custom fail message
|
|
new AttackMove(Moves.BREAKING_SWIPE, Type.DRAGON, MoveCategory.PHYSICAL, 60, 100, 15, 100, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new AttackMove(Moves.BRANCH_POKE, Type.GRASS, MoveCategory.PHYSICAL, 40, 100, 40, -1, 0, 8),
|
|
new AttackMove(Moves.OVERDRIVE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8)
|
|
.soundBased()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.APPLE_ACID, Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -1),
|
|
new AttackMove(Moves.GRAV_APPLE, Type.GRASS, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTag(ArenaTagType.GRAVITY) ? 1.5 : 1)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.SPIRIT_BREAK, Type.FAIRY, MoveCategory.PHYSICAL, 75, 100, 15, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1),
|
|
new AttackMove(Moves.STRANGE_STEAM, Type.FAIRY, MoveCategory.SPECIAL, 90, 95, 10, 20, 0, 8)
|
|
.attr(ConfuseAttr),
|
|
new StatusMove(Moves.LIFE_DEW, Type.WATER, -1, 10, -1, 0, 8)
|
|
.attr(HealAttr, 0.25, true, false)
|
|
.target(MoveTarget.USER_AND_ALLIES)
|
|
.ignoresProtect(),
|
|
new SelfStatusMove(Moves.OBSTRUCT, Type.DARK, 100, 10, -1, 4, 8)
|
|
.attr(ProtectAttr, BattlerTagType.OBSTRUCT),
|
|
new AttackMove(Moves.FALSE_SURRENDER, Type.DARK, MoveCategory.PHYSICAL, 80, -1, 10, -1, 0, 8),
|
|
new AttackMove(Moves.METEOR_ASSAULT, Type.FIGHTING, MoveCategory.PHYSICAL, 150, 100, 5, -1, 0, 8)
|
|
.attr(RechargeAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.ETERNABEAM, Type.DRAGON, MoveCategory.SPECIAL, 160, 90, 5, -1, 0, 8)
|
|
.attr(RechargeAttr),
|
|
new AttackMove(Moves.STEEL_BEAM, Type.STEEL, MoveCategory.SPECIAL, 140, 95, 5, -1, 0, 8)
|
|
.attr(HalfSacrificialAttr),
|
|
new AttackMove(Moves.EXPANDING_FORCE, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 8)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 1.5 : 1)
|
|
.attr(VariableTargetAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.PSYCHIC && user.isGrounded() ? 6 : 3),
|
|
new AttackMove(Moves.STEEL_ROLLER, Type.STEEL, MoveCategory.PHYSICAL, 130, 100, 5, -1, 0, 8)
|
|
.attr(ClearTerrainAttr)
|
|
.condition((user, target, move) => !!user.scene.arena.terrain),
|
|
new AttackMove(Moves.SCALE_SHOT, Type.DRAGON, MoveCategory.PHYSICAL, 25, 90, 20, -1, 0, 8)
|
|
//.attr(StatChangeAttr, BattleStat.SPD, 1, true) // TODO: Have boosts only apply at end of move, not after every hit
|
|
//.attr(StatChangeAttr, BattleStat.DEF, -1, true)
|
|
.attr(MultiHitAttr)
|
|
.makesContact(false)
|
|
.partial(),
|
|
new AttackMove(Moves.METEOR_BEAM, Type.ROCK, MoveCategory.SPECIAL, 120, 90, 10, 100, 0, 8)
|
|
.attr(ChargeAttr, ChargeAnim.METEOR_BEAM_CHARGING, i18next.t("moveTriggers:isOverflowingWithSpacePower", {pokemonName: "{USER}"}), null, true)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 1, true)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.SHELL_SIDE_ARM, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8)
|
|
.attr(ShellSideArmCategoryAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.partial(),
|
|
new AttackMove(Moves.MISTY_EXPLOSION, Type.FAIRY, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 8)
|
|
.attr(SacrificialAttr)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.MISTY && user.isGrounded() ? 1.5 : 1)
|
|
.condition(failIfDampCondition)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.GRASSY_GLIDE, Type.GRASS, MoveCategory.PHYSICAL, 55, 100, 20, -1, 0, 8)
|
|
.attr(IncrementMovePriorityAttr,(user,target,move) =>user.scene.arena.getTerrainType()===TerrainType.GRASSY&&user.isGrounded()),
|
|
new AttackMove(Moves.RISING_VOLTAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 8)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && target.isGrounded() ? 2 : 1),
|
|
new AttackMove(Moves.TERRAIN_PULSE, Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 10, -1, 0, 8)
|
|
.attr(TerrainPulseTypeAttr)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() !== TerrainType.NONE && user.isGrounded() ? 2 : 1)
|
|
.pulseMove(),
|
|
new AttackMove(Moves.SKITTER_SMACK, Type.BUG, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1),
|
|
new AttackMove(Moves.BURNING_JEALOUSY, Type.FIRE, MoveCategory.SPECIAL, 70, 100, 5, 100, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.partial(),
|
|
new AttackMove(Moves.LASH_OUT, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8)
|
|
.partial(),
|
|
new AttackMove(Moves.POLTERGEIST, Type.GHOST, MoveCategory.PHYSICAL, 110, 90, 5, -1, 0, 8)
|
|
.attr(AttackedByItemAttr)
|
|
.makesContact(false),
|
|
new StatusMove(Moves.CORROSIVE_GAS, Type.POISON, 100, 40, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_OTHERS)
|
|
.unimplemented(),
|
|
new StatusMove(Moves.COACHING, Type.FIGHTING, -1, 10, -1, 0, 8)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.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),
|
|
new AttackMove(Moves.TRIPLE_AXEL, Type.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8)
|
|
.attr(MultiHitAttr, MultiHitType._3)
|
|
.attr(MultiHitPowerIncrementAttr, 3)
|
|
.checkAllHits(),
|
|
new AttackMove(Moves.DUAL_WINGBEAT, Type.FLYING, MoveCategory.PHYSICAL, 40, 90, 10, -1, 0, 8)
|
|
.attr(MultiHitAttr, MultiHitType._2),
|
|
new AttackMove(Moves.SCORCHING_SANDS, Type.GROUND, MoveCategory.SPECIAL, 70, 100, 10, 30, 0, 8)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN),
|
|
new StatusMove(Moves.JUNGLE_HEALING, Type.GRASS, -1, 10, -1, 0, 8)
|
|
.attr(HealAttr, 0.25, true, false)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP)
|
|
.target(MoveTarget.USER_AND_ALLIES),
|
|
new AttackMove(Moves.WICKED_BLOW, Type.DARK, MoveCategory.PHYSICAL, 75, 100, 5, -1, 0, 8)
|
|
.attr(CritOnlyAttr)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.SURGING_STRIKES, Type.WATER, MoveCategory.PHYSICAL, 25, 100, 5, -1, 0, 8)
|
|
.attr(MultiHitAttr, MultiHitType._3)
|
|
.attr(CritOnlyAttr)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.THUNDER_CAGE, Type.ELECTRIC, MoveCategory.SPECIAL, 80, 90, 15, -1, 0, 8)
|
|
.attr(TrapAttr, BattlerTagType.THUNDER_CAGE),
|
|
new AttackMove(Moves.DRAGON_ENERGY, Type.DRAGON, MoveCategory.SPECIAL, 150, 100, 5, -1, 0, 8)
|
|
.attr(HpPowerAttr)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.FREEZING_GLARE, Type.PSYCHIC, MoveCategory.SPECIAL, 90, 100, 10, 10, 0, 8)
|
|
.attr(StatusEffectAttr, StatusEffect.FREEZE),
|
|
new AttackMove(Moves.FIERY_WRATH, Type.DARK, MoveCategory.SPECIAL, 90, 100, 10, 20, 0, 8)
|
|
.attr(FlinchAttr)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.THUNDEROUS_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1),
|
|
new AttackMove(Moves.GLACIAL_LANCE, Type.ICE, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.ASTRAL_BARRAGE, Type.GHOST, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.EERIE_SPELL, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 5, 100, 0, 8)
|
|
.attr(AttackReducePpMoveAttr, 3)
|
|
.soundBased(),
|
|
new AttackMove(Moves.DIRE_CLAW, Type.POISON, MoveCategory.PHYSICAL, 80, 100, 15, 50, 0, 8)
|
|
.attr(MultiStatusEffectAttr, [StatusEffect.POISON, StatusEffect.PARALYSIS, StatusEffect.SLEEP]),
|
|
new AttackMove(Moves.PSYSHIELD_BASH, Type.PSYCHIC, MoveCategory.PHYSICAL, 70, 90, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 1, true),
|
|
new SelfStatusMove(Moves.POWER_SHIFT, Type.NORMAL, -1, 10, -1, 0, 8)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.STONE_AXE, Type.ROCK, MoveCategory.PHYSICAL, 65, 90, 15, 100, 0, 8)
|
|
.attr(AddArenaTrapTagHitAttr, ArenaTagType.STEALTH_ROCK)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.SPRINGTIDE_STORM, Type.FAIRY, MoveCategory.SPECIAL, 100, 80, 5, 30, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.MYSTICAL_POWER, Type.PSYCHIC, MoveCategory.SPECIAL, 70, 90, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 1, true),
|
|
new AttackMove(Moves.RAGING_FURY, Type.FIRE, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 8)
|
|
.makesContact(false)
|
|
.attr(FrenzyAttr)
|
|
.attr(MissEffectAttr, frenzyMissFunc)
|
|
.attr(NoEffectAttr, frenzyMissFunc)
|
|
.target(MoveTarget.RANDOM_NEAR_ENEMY),
|
|
new AttackMove(Moves.WAVE_CRASH, Type.WATER, MoveCategory.PHYSICAL, 120, 100, 10, -1, 0, 8)
|
|
.attr(RecoilAttr, false, 0.33)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.CHLOROBLAST, Type.GRASS, MoveCategory.SPECIAL, 150, 95, 5, -1, 0, 8)
|
|
.attr(RecoilAttr, true, 0.5),
|
|
new AttackMove(Moves.MOUNTAIN_GALE, Type.ICE, MoveCategory.PHYSICAL, 100, 85, 10, 30, 0, 8)
|
|
.makesContact(false)
|
|
.attr(FlinchAttr),
|
|
new SelfStatusMove(Moves.VICTORY_DANCE, Type.FIGHTING, -1, 10, -1, 0, 8)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPD ], 1, true)
|
|
.danceMove(),
|
|
new AttackMove(Moves.HEADLONG_RUSH, Type.GROUND, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 8)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.BARB_BARRAGE, Type.POISON, MoveCategory.PHYSICAL, 60, 100, 10, 50, 0, 8)
|
|
.makesContact(false)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status && (target.status.effect === StatusEffect.POISON || target.status.effect === StatusEffect.TOXIC) ? 2 : 1)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON),
|
|
new AttackMove(Moves.ESPER_WING, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 8)
|
|
.attr(HighCritAttr)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 1, true),
|
|
new AttackMove(Moves.BITTER_MALICE, Type.GHOST, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new SelfStatusMove(Moves.SHELTER, Type.STEEL, -1, 10, 100, 0, 8)
|
|
.attr(StatChangeAttr, BattleStat.DEF, 2, true),
|
|
new AttackMove(Moves.TRIPLE_ARROWS, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
|
|
.makesContact(false)
|
|
.attr(HighCritAttr)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -1)
|
|
.attr(FlinchAttr)
|
|
.partial(),
|
|
new AttackMove(Moves.INFERNAL_PARADE, Type.GHOST, MoveCategory.SPECIAL, 60, 100, 15, 30, 0, 8)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.status ? 2 : 1),
|
|
new AttackMove(Moves.CEASELESS_EDGE, Type.DARK, MoveCategory.PHYSICAL, 65, 90, 15, 100, 0, 8)
|
|
.attr(AddArenaTrapTagHitAttr, ArenaTagType.SPIKES)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.BLEAKWIND_STORM, Type.FLYING, MoveCategory.SPECIAL, 100, 80, 10, 30, 0, 8)
|
|
.attr(ThunderAccuracyAttr)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.WILDBOLT_STORM, Type.ELECTRIC, MoveCategory.SPECIAL, 100, 80, 10, 20, 0, 8)
|
|
.attr(ThunderAccuracyAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.SANDSEAR_STORM, Type.GROUND, MoveCategory.SPECIAL, 100, 80, 10, 20, 0, 8)
|
|
.attr(ThunderAccuracyAttr)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.windMove()
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.LUNAR_BLESSING, Type.PSYCHIC, -1, 5, -1, 0, 8)
|
|
.attr(HealAttr, 0.25)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP)
|
|
.target(MoveTarget.USER_AND_ALLIES)
|
|
.triageMove(),
|
|
new SelfStatusMove(Moves.TAKE_HEART, Type.PSYCHIC, -1, 10, -1, 0, 8)
|
|
.attr(StatChangeAttr, [ BattleStat.SPATK, BattleStat.SPDEF ], 1, true)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.PARALYSIS, StatusEffect.POISON, StatusEffect.TOXIC, StatusEffect.BURN, StatusEffect.SLEEP),
|
|
/* Unused
|
|
new AttackMove(Moves.G_MAX_WILDFIRE, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_BEFUDDLE, Type.BUG, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_VOLT_CRASH, Type.ELECTRIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_GOLD_RUSH, Type.NORMAL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_CHI_STRIKE, Type.FIGHTING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_TERROR, Type.GHOST, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_RESONANCE, Type.ICE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_CUDDLE, Type.NORMAL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_REPLENISH, Type.NORMAL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_MALODOR, Type.POISON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_STONESURGE, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_WIND_RAGE, Type.FLYING, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_STUN_SHOCK, Type.ELECTRIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_FINALE, Type.FAIRY, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_DEPLETION, Type.DRAGON, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_GRAVITAS, Type.PSYCHIC, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_VOLCALITH, Type.ROCK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_SANDBLAST, Type.GROUND, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_SNOOZE, Type.DARK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_TARTNESS, Type.GRASS, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_SWEETNESS, Type.GRASS, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_SMITE, Type.FAIRY, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_STEELSURGE, Type.STEEL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_MELTDOWN, Type.STEEL, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_FOAM_BURST, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_CENTIFERNO, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_VINE_LASH, Type.GRASS, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_CANNONADE, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_DRUM_SOLO, Type.GRASS, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_FIREBALL, Type.FIRE, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_HYDROSNIPE, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_ONE_BLOW, Type.DARK, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
new AttackMove(Moves.G_MAX_RAPID_FLOW, Type.WATER, MoveCategory.PHYSICAL, 10, -1, 10, -1, 0, 8)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.unimplemented(),
|
|
End Unused */
|
|
new AttackMove(Moves.TERA_BLAST, Type.NORMAL, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
|
|
.attr(TeraBlastCategoryAttr)
|
|
.unimplemented(),
|
|
new SelfStatusMove(Moves.SILK_TRAP, Type.BUG, -1, 10, -1, 4, 9)
|
|
.attr(ProtectAttr, BattlerTagType.SILK_TRAP),
|
|
new AttackMove(Moves.AXE_KICK, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 90, 10, 30, 0, 9)
|
|
.attr(MissEffectAttr, crashDamageFunc)
|
|
.attr(NoEffectAttr, crashDamageFunc)
|
|
.attr(ConfuseAttr)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.LAST_RESPECTS, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => 1 + Math.min(user.isPlayer() ? user.scene.currentBattle.playerFaints : user.scene.currentBattle.enemyFaints, 100))
|
|
.makesContact(false),
|
|
new AttackMove(Moves.LUMINA_CRASH, Type.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.SPDEF, -2),
|
|
new AttackMove(Moves.ORDER_UP, Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
|
|
.makesContact(false)
|
|
.partial(),
|
|
new AttackMove(Moves.JET_PUNCH, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
|
|
.punchingMove(),
|
|
new StatusMove(Moves.SPICY_EXTRACT, Type.GRASS, -1, 15, -1, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.ATK, 2)
|
|
.attr(StatChangeAttr, BattleStat.DEF, -2),
|
|
new AttackMove(Moves.SPIN_OUT, Type.STEEL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -2, true),
|
|
new AttackMove(Moves.POPULATION_BOMB, Type.NORMAL, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 9)
|
|
.attr(MultiHitAttr, MultiHitType._10)
|
|
.slicingMove()
|
|
.checkAllHits(),
|
|
new AttackMove(Moves.ICE_SPINNER, Type.ICE, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
|
|
.attr(ClearTerrainAttr),
|
|
new AttackMove(Moves.GLAIVE_RUSH, Type.DRAGON, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 9)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.ALWAYS_GET_HIT, true, false, 0, 0, true)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.RECEIVE_DOUBLE_DAMAGE, true, false, 0, 0, true),
|
|
new StatusMove(Moves.REVIVAL_BLESSING, Type.NORMAL, -1, 1, -1, 0, 9)
|
|
.triageMove()
|
|
.attr(RevivalBlessingAttr)
|
|
.target(MoveTarget.USER),
|
|
new AttackMove(Moves.SALT_CURE, Type.ROCK, MoveCategory.PHYSICAL, 40, 100, 15, 100, 0, 9)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.SALT_CURED)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.TRIPLE_DIVE, Type.WATER, MoveCategory.PHYSICAL, 30, 95, 10, -1, 0, 9)
|
|
.attr(MultiHitAttr, MultiHitType._3),
|
|
new AttackMove(Moves.MORTAL_SPIN, Type.POISON, MoveCategory.PHYSICAL, 30, 100, 15, 100, 0, 9)
|
|
.attr(LapseBattlerTagAttr, [
|
|
BattlerTagType.BIND,
|
|
BattlerTagType.WRAP,
|
|
BattlerTagType.FIRE_SPIN,
|
|
BattlerTagType.WHIRLPOOL,
|
|
BattlerTagType.CLAMP,
|
|
BattlerTagType.SAND_TOMB,
|
|
BattlerTagType.MAGMA_STORM,
|
|
BattlerTagType.SNAP_TRAP,
|
|
BattlerTagType.THUNDER_CAGE,
|
|
BattlerTagType.SEEDED,
|
|
BattlerTagType.INFESTATION
|
|
], true)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.attr(RemoveArenaTrapAttr)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new StatusMove(Moves.DOODLE, Type.NORMAL, 100, 10, -1, 0, 9)
|
|
.attr(AbilityCopyAttr, true),
|
|
new SelfStatusMove(Moves.FILLET_AWAY, Type.NORMAL, -1, 10, -1, 0, 9)
|
|
.attr(CutHpStatBoostAttr, [ BattleStat.ATK, BattleStat.SPATK, BattleStat.SPD ], 2, 2),
|
|
new AttackMove(Moves.KOWTOW_CLEAVE, Type.DARK, MoveCategory.PHYSICAL, 85, -1, 10, -1, 0, 9)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.FLOWER_TRICK, Type.GRASS, MoveCategory.PHYSICAL, 70, -1, 10, 100, 0, 9)
|
|
.attr(CritOnlyAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.TORCH_SONG, Type.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, 1, true)
|
|
.soundBased(),
|
|
new AttackMove(Moves.AQUA_STEP, Type.WATER, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 1, true)
|
|
.danceMove(),
|
|
new AttackMove(Moves.RAGING_BULL, Type.NORMAL, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 9)
|
|
.attr(RagingBullTypeAttr)
|
|
.attr(RemoveScreensAttr),
|
|
new AttackMove(Moves.MAKE_IT_RAIN, Type.STEEL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
|
.attr(MoneyAttr)
|
|
.attr(StatChangeAttr, BattleStat.SPATK, -1, true, null, true, false, MoveEffectTrigger.HIT, true)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
|
new AttackMove(Moves.PSYBLADE, Type.PSYCHIC, MoveCategory.PHYSICAL, 80, 100, 15, -1, 0, 9)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.scene.arena.getTerrainType() === TerrainType.ELECTRIC && user.isGrounded() ? 1.5 : 1)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.HYDRO_STEAM, Type.WATER, MoveCategory.SPECIAL, 80, 100, 15, -1, 0, 9)
|
|
.attr(IgnoreWeatherTypeDebuffAttr, WeatherType.SUNNY)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => [WeatherType.SUNNY, WeatherType.HARSH_SUN].includes(user.scene.arena.weather?.weatherType) && !user.scene.arena.weather?.isEffectSuppressed(user.scene) ? 1.5 : 1),
|
|
new AttackMove(Moves.RUINATION, Type.DARK, MoveCategory.SPECIAL, -1, 90, 10, -1, 0, 9)
|
|
.attr(TargetHalfHpDamageAttr),
|
|
new AttackMove(Moves.COLLISION_COURSE, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461/4096 : 1),
|
|
new AttackMove(Moves.ELECTRO_DRIFT, Type.ELECTRIC, MoveCategory.SPECIAL, 100, 100, 5, -1, 0, 9)
|
|
.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(),
|
|
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
|
.attr(WeatherChangeAttr, WeatherType.SNOW)
|
|
.attr(ForceSwitchOutAttr, true, false)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
|
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPD ], 1, true, null, true, true)
|
|
.attr(RemoveArenaTrapAttr, true),
|
|
new StatusMove(Moves.SNOWSCAPE, Type.ICE, -1, 10, -1, 0, 9)
|
|
.attr(WeatherChangeAttr, WeatherType.SNOW)
|
|
.target(MoveTarget.BOTH_SIDES),
|
|
new AttackMove(Moves.POUNCE, Type.BUG, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1),
|
|
new AttackMove(Moves.TRAILBLAZE, Type.GRASS, MoveCategory.PHYSICAL, 50, 100, 20, 100, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.SPD, 1, true),
|
|
new AttackMove(Moves.CHILLING_WATER, Type.WATER, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.ATK, -1),
|
|
new AttackMove(Moves.HYPER_DRILL, Type.NORMAL, MoveCategory.PHYSICAL, 100, 100, 5, -1, 0, 9)
|
|
.ignoresProtect(),
|
|
new AttackMove(Moves.TWIN_BEAM, Type.PSYCHIC, MoveCategory.SPECIAL, 40, 100, 10, -1, 0, 9)
|
|
.attr(MultiHitAttr, MultiHitType._2),
|
|
new AttackMove(Moves.RAGE_FIST, Type.GHOST, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 9)
|
|
.attr(HitCountPowerAttr)
|
|
.punchingMove(),
|
|
new AttackMove(Moves.ARMOR_CANNON, Type.FIRE, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
|
.attr(StatChangeAttr, [ BattleStat.DEF, BattleStat.SPDEF ], -1, true),
|
|
new AttackMove(Moves.BITTER_BLADE, Type.FIRE, MoveCategory.PHYSICAL, 90, 100, 10, -1, 0, 9)
|
|
.attr(HitHealAttr)
|
|
.slicingMove()
|
|
.triageMove(),
|
|
new AttackMove(Moves.DOUBLE_SHOCK, Type.ELECTRIC, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 9)
|
|
.condition((user) => {
|
|
const userTypes = user.getTypes(true);
|
|
return userTypes.includes(Type.ELECTRIC);
|
|
})
|
|
.attr(RemoveTypeAttr, Type.ELECTRIC, (user) => {
|
|
user.scene.queueMessage(i18next.t("moveTriggers:usedUpAllElectricity", {pokemonName: getPokemonNameWithAffix(user)}));
|
|
}),
|
|
new AttackMove(Moves.GIGATON_HAMMER, Type.STEEL, MoveCategory.PHYSICAL, 160, 100, 5, -1, 0, 9)
|
|
.makesContact(false)
|
|
.condition((user, target, move) => {
|
|
const turnMove = user.getLastXMoves(1);
|
|
return !turnMove.length || turnMove[0].move !== move.id || turnMove[0].result !== MoveResult.SUCCESS;
|
|
}), // TODO Add Instruct/Encore interaction
|
|
new AttackMove(Moves.COMEUPPANCE, Type.DARK, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
|
|
.attr(CounterDamageAttr, (move: Move) => (move.category === MoveCategory.PHYSICAL || move.category === MoveCategory.SPECIAL), 1.5)
|
|
.redirectCounter()
|
|
.target(MoveTarget.ATTACKER),
|
|
new AttackMove(Moves.AQUA_CUTTER, Type.WATER, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 9)
|
|
.attr(HighCritAttr)
|
|
.slicingMove()
|
|
.makesContact(false),
|
|
new AttackMove(Moves.BLAZING_TORQUE, Type.FIRE, MoveCategory.PHYSICAL, 80, 100, 10, 30, 0, 9)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.WICKED_TORQUE, Type.DARK, MoveCategory.PHYSICAL, 80, 100, 10, 10, 0, 9)
|
|
.attr(StatusEffectAttr, StatusEffect.SLEEP)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.NOXIOUS_TORQUE, Type.POISON, MoveCategory.PHYSICAL, 100, 100, 10, 30, 0, 9)
|
|
.attr(StatusEffectAttr, StatusEffect.POISON)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.COMBAT_TORQUE, Type.FIGHTING, MoveCategory.PHYSICAL, 100, 100, 10, 30, 0, 9)
|
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.MAGICAL_TORQUE, Type.FAIRY, MoveCategory.PHYSICAL, 100, 100, 10, 30, 0, 9)
|
|
.attr(ConfuseAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.BLOOD_MOON, Type.NORMAL, MoveCategory.SPECIAL, 140, 100, 5, -1, 0, 9)
|
|
.condition((user, target, move) => {
|
|
const turnMove = user.getLastXMoves(1);
|
|
return !turnMove.length || turnMove[0].move !== move.id || turnMove[0].result !== MoveResult.SUCCESS;
|
|
}), // TODO Add Instruct/Encore interaction
|
|
new AttackMove(Moves.MATCHA_GOTCHA, Type.GRASS, MoveCategory.SPECIAL, 80, 90, 15, 20, 0, 9)
|
|
.attr(HitHealAttr)
|
|
.attr(HealStatusEffectAttr, true, StatusEffect.FREEZE)
|
|
.attr(HealStatusEffectAttr, false, StatusEffect.FREEZE)
|
|
.attr(StatusEffectAttr, StatusEffect.BURN)
|
|
.target(MoveTarget.ALL_NEAR_ENEMIES)
|
|
.triageMove(),
|
|
new AttackMove(Moves.SYRUP_BOMB, Type.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9)
|
|
.attr(StatChangeAttr, BattleStat.SPD, -1) //Temporary
|
|
.ballBombMove()
|
|
.partial(),
|
|
new AttackMove(Moves.IVY_CUDGEL, Type.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9)
|
|
.attr(IvyCudgelTypeAttr)
|
|
.attr(HighCritAttr)
|
|
.makesContact(false),
|
|
new AttackMove(Moves.ELECTRO_SHOT, Type.ELECTRIC, MoveCategory.SPECIAL, 130, 100, 10, 100, 0, 9)
|
|
.attr(ElectroShotChargeAttr)
|
|
.ignoresVirtual(),
|
|
new AttackMove(Moves.TERA_STARSTORM, Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
|
|
.attr(TeraBlastCategoryAttr)
|
|
.partial(),
|
|
new AttackMove(Moves.FICKLE_BEAM, Type.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9)
|
|
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
|
|
.attr(DoublePowerChanceAttr),
|
|
new SelfStatusMove(Moves.BURNING_BULWARK, Type.FIRE, -1, 10, -1, 4, 9)
|
|
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK),
|
|
new AttackMove(Moves.THUNDERCLAP, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 5, -1, 1, 9)
|
|
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS),
|
|
new AttackMove(Moves.MIGHTY_CLEAVE, Type.ROCK, MoveCategory.PHYSICAL, 95, 100, 5, -1, 0, 9)
|
|
.slicingMove()
|
|
.ignoresProtect(),
|
|
new AttackMove(Moves.TACHYON_CUTTER, Type.STEEL, MoveCategory.SPECIAL, 50, -1, 10, -1, 0, 9)
|
|
.attr(MultiHitAttr, MultiHitType._2)
|
|
.slicingMove(),
|
|
new AttackMove(Moves.HARD_PRESS, Type.STEEL, MoveCategory.PHYSICAL, -1, 100, 10, -1, 0, 9)
|
|
.attr(OpponentHighHpPowerAttr, 100),
|
|
new StatusMove(Moves.DRAGON_CHEER, Type.DRAGON, -1, 15, -1, 0, 9)
|
|
.attr(AddBattlerTagAttr, BattlerTagType.CRIT_BOOST, false, true)
|
|
.target(MoveTarget.NEAR_ALLY)
|
|
.partial(),
|
|
new AttackMove(Moves.ALLURING_VOICE, Type.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
|
|
.soundBased()
|
|
.partial(),
|
|
new AttackMove(Moves.TEMPER_FLARE, Type.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
|
|
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
|
|
new AttackMove(Moves.SUPERCELL_SLAM, Type.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9)
|
|
.attr(MissEffectAttr, crashDamageFunc)
|
|
.attr(NoEffectAttr, crashDamageFunc)
|
|
.recklessMove(),
|
|
new AttackMove(Moves.PSYCHIC_NOISE, Type.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 9)
|
|
.soundBased()
|
|
.partial(),
|
|
new AttackMove(Moves.UPPER_HAND, Type.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)
|
|
.attr(FlinchAttr)
|
|
.condition((user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.FIGHT && !target.turnData.acted && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].category !== MoveCategory.STATUS && allMoves[user.scene.currentBattle.turnCommands[target.getBattlerIndex()].move.move].priority > 0 )
|
|
//TODO: Should also apply when target move priority increased by ability ex. gale wings
|
|
.partial(),
|
|
new AttackMove(Moves.MALIGNANT_CHAIN, Type.POISON, MoveCategory.SPECIAL, 100, 100, 5, 50, 0, 9)
|
|
.attr(StatusEffectAttr, StatusEffect.TOXIC)
|
|
);
|
|
allMoves.map(m => {
|
|
if (m.getAttrs(StatChangeAttr).some(a => a.selfTarget && a.levels < 0)) {
|
|
selfStatLowerMoves.push(m.id);
|
|
}
|
|
});
|
|
}
|